Published artifacts

Search Sporacle

Use the up and down arrow keys to move through results. Press Enter to open a result and Escape to close search.

Type to search published players, teams, and games.

Releases

1.9.0

All releases, newest first

For you

  • Every page starts drawing sooner, the weekly rankings most of all: the small per-section stylesheets travel with the page now instead of each having to arrive before anything could be drawn.
  • Nothing changes on the site. These are research measurements of the game forecast and player projection models, and no published number moves.
  • During a game, a team's win probability falls back to the pre-game number, labelled as a prior, whenever the live scoreboard does not say which team has the ball, instead of publishing a figure computed as though the away team had it: a guess presented as a reading.
  • The changelog page opens at a readable length. The newest releases render in full, and every older release keeps its own page, one click away from the list at the bottom.
  • Searching for weekly fantasy football rankings or a side by side player comparison lands better: the rankings and compare pages name what they are in their titles and descriptions, matched to how people search for tools like these. Nothing on the pages themselves changes.
  • A new page explains how to read the site's numbers: what a published likelihood, margin range, and projection mean, what calibration is, and why being right about how often we are right is a stronger claim than being usually right. The track record page links to it.

Under the hood

  • F1-C2, weighting a prior season's rating evidence by how much of that team's roster actually remains, is tested and VOID per the loop's power rule. Selected at gamma 0.5 on the 2022 season alone, it moved the game forecast's Brier score by a slight and unfavorable +0.00014 on the following three seasons, 816 games, with a 95 percent interval spanning zero and a detection floor about 39 times the size of that movement, so this sample could not tell the weighted candidate apart from the deployed forecast either way. Nothing deploys and no published number moves. The early season weeks, checked separately as the place this kind of effect should show up first, also read as no measurable movement at this sample size.
  • A second look at that F1-C2 test found its roster-turnover instrument mis-built: the roster table it reads is a seasonal snapshot, not a weekly one, so the weighting it applied discarded nearly every prior season's evidence instead of adjusting it. The result above is retired from evidence and F1-C2 goes back to untested; a corrected test against a real weekly roster source is planned as its own separate piece of work. Nothing deploys and no published number moves.
  • F1-C2, retested against a corrected, genuinely weekly roster source, is VOID per the loop's power rule. Selected at gamma 2.0 on the 2022 season alone, it moved the game forecast's Brier score by a slight amount in the favorable direction, -0.00114, on the following three seasons, 816 games, with a 95 percent interval spanning zero and a detection floor about 3.3 times the size of that movement, so this sample still could not tell the weighted candidate apart from the deployed forecast either way. Nothing deploys and no published number moves. The early season weeks, checked separately as the place this kind of effect should show up first, again read as no measurable movement at this sample size.
  • The depth chart archive seeds itself from R2 before every union, fetches every season since 2025 instead of only the current one, and refuses to write a file that would drop a known snapshot, so a fresh checkout or a season rollover can no longer shrink the durable copy.
  • astro.config.mjs sets inlineStylesheets to "auto" rather than "never": largest contentful paint on /rankings, mobile throttled, went from 1.7s to 1.5s against the 1500ms budget the site quality gate asserts. "always" reaches 1.4s but copies the shared stylesheet into every page; the fuller rationale sits in the config beside the setting.
  • A diagnostic sweep measures how much Engine 1 is willing to say: twelve grid cells refit the margin ladder at six climatology blend weights, calibrator off and on, scored on sharpness, calibration, Brier, band coverage and games callable at 0.7 or better. Nothing shipped changed; `docs/reports/dispersion_sweep.md` defers the decision to a pre-registered run on the 2026 season.
  • Every sweep cell reports the smallest Brier difference the sample could detect, so a flat result can be told apart from one the data cannot resolve. A stillness tripwire halts a run the swept weight fails to move.
  • The live poller gates the model path on possession being known, and the feature builder raises rather than defaulting unknown possession to zero. Zero encodes away-team possession at training time, so the serving path had no training analogue for the value it substituted.
  • The live scoreboard parser resolves possession against both clubs' identifiers; comparing against the home club alone turned an identifier matching neither into a definite claim about a field never read.
  • The preseason dry run attempts its four live links instead of reporting them unreachable from a fixed reason, running the real poller snapshot on games in progress and observing the deployed broadcast read-only.
  • The dry run's write to the live readings table asks the target database whether it holds any real reading before inserting anything. It fails closed: a probe that cannot be answered refuses the write.
  • That probe accepts a count of zero, and the production table stays empty until the first regular season poll, so an empty production read as disposable. The write now also reads the production database id from the live Worker's own wrangler.toml and refuses a matching target; a file that is missing, unparseable, or names no database refuses the write.
  • `sporacle readiness`, also `make readiness`, checks per table that the parquet master holds what the source serves, keyed on `season` or `dt` as the table demands, with an age tolerance derived from the schedule. Gate 1 checks code and artifacts; this is the first check of the data behind them. The Week 1 runbook mandates running it.
  • A readiness source probe that dies reports NOT CHECKED with its reason, never a pass, and the verdict counts it: dropping unanswered probes once let a run that reached no source print GO against a two season stale master. Exit codes: 0 only for a source verified pass, 1 for a blocking failure, 2 for a partial, including a run without `--probe-source`, which asks the source nothing.
  • Play by play for a season with no countable game played reads as a clean pass instead of NOT CHECKED. The 2026 file crashes the nflverse library rather than 404ing, so the probe said "not answered" on every healthy preseason run. The pass rests on the schedule, not the crash's shape, and withdraws itself the day a game is played.
  • The depth chart recency comparison parses both timestamps rather than ordering their stringified forms, and reports NOT CHECKED when either does not parse; a Timestamp against an ISO string read a level master as behind, a blocking false no-go.
  • A Week 1 manual ingest runbook (`docs/runbooks/2026-WEEK1-MANUAL-INGEST.md`) records which tables the cadence never advances, the verification after every step, and the depth chart archive deadline that waits for no one. It proves the checkout carries the fixed readiness by exercising the behaviour, not by asking git for ancestry.
  • /changelog splits at build time: the newest releases plus an archive list on the index, and one page per released version beneath /changelog. The HTML budget note in site/scripts/check-budget.mjs recorded the page's growth as a treadmill and named this split as the real answer; measured on an empty-artifact build, the heaviest page falls from 67,272 to 47,614 bytes, and it stays within the 68,000 byte cap with every open research branch's bullets counted in. Gate 2's path filter now includes CHANGELOG.md, because a changelog-only edit changes the built bytes that budget measures.
  • UI spec items 8 and 3a: rankings and compare titles, meta descriptions and social card copy are tool-shaped rather than generic labels, and docs/reports/crawlable_url_audit.md records the item 3a audit: every game, team and player already resolves to a real static URL, all three families ride the sitemap with lastmod where published, and no gaps need new page architecture.
  • /how-to-read assembles the live glossary entries from site/src/lib/glossary.ts, one wording source for the explainer and the in-context popovers, and renders its worked margin range in the side-plus-number convention the game pages use. A contract test pins the assembly, the corrected calibration copy, the range convention, the track record link, and the sitemap entry.
  • H21, the divisional rematch hypothesis, is tested and REJECTED. A binary `is_divisional_rematch` flag (second or later meeting of division opponents in the same season, derived from an underlying `meeting_number` ordered by kickoff instant, not week) moved the game model's Brier by +0.00072 with a 95 percent interval spanning zero, at a sample powered to detect 0.00126, and the direct effects on completed rematches lean opposite the sourced claim. Nothing deploys and no published number moves. The candidate module merges so `meeting_number` exists for any future second-versus-third test, and the hypothesis log records the method, the conflation caveat and the held-back refinements.
  • C10, route participation share, is CLOSED at stage 0, not on the signal but on serving. A new loader (`sporacle.ingest.nflverse.load_participation`) pulls nflverse's `pbp_participation` release directly, since the pinned `nfl_data_py` extra carries no wrapper for it, and it reaches further back than the candidate's own premise assumed: 2016 through 2025, joining the ingested play by play at a 100.0 percent dropback match rate in every one of the 2023 to 2025 test seasons. What closes it is publication cadence, read from the release's own update history rather than assumed: a season's file lands once that season has already finished, seven to nineteen months late for 2023 and 2024, not during it, so it cannot supply the strictly prior week routes-run share a live 2026 ranking needs. Nothing deploys, no screen ran, and no published number moves.
  • From the cold review of that work: the H21 method note now names the third case "a result on record" covers (a first meeting played but not yet ingested computes no flag, so serve-time correctness depends on ingest recency), and the builder fails with a KeyError naming `div_game` instead of an AttributeError from mid-arithmetic when a frame lacks the column.
  • A4, exponential recency decay for the team ratings, is tested and does not deploy; the verdict reads VOID per the loop's power rule (amended from REJECTED at the second cold review). The rating solve gains a weighted ridge and a `half_life` parameter whose `None` value is the deployed construction exactly (pinned by test); age counts distinct week ordinals so the offseason gap is one step, mutation-checked against raw ordinal arithmetic. The exploratory grid peaked at a half-life of 8 weeks (-0.0028 pooled), the pre-registered selection on 2022 picked 4, and the confirm on 2023-2025 came back -0.00163 with an interval spanning zero and a detection floor of 0.00522, 3.2 times the movement, so the sample could not have decided either way and `half_life` stays `None` in production. The confirm runner now prints that floor and a four-way reason beside every verdict, and the leakage probe covers the decay path, where a dataset-end age anchor is the mutation nothing else catches. The hypothesis log records the pre-registration, the disclosed contamination, the label's ADR-0003 consequence and the two gated retest triggers (2026 complete, or the deep-lookback round). `sporacle a4-sweep` reruns the grid; `sporacle a4-sweep --confirm` applies the pre-registered rule and reproduces the recorded numbers to the digit.
  • From the H6 cold review: the injury availability guard fails closed on undated rows. Every 2025 injury row ships with a null `date_modified` (2016-2024 are fully dated), and the old `isna()` escape admitted them all, so a row that cannot prove it predates kickoff now drops instead. This closes the H6 research sites only (`team_availability` and the retest's designation trigger); the same expression's other sites close in their own patches, and the one on the deployed path (`backfield.py` teammate absence) is recorded in #sporacle-decisions with its own owner. The recorded H6 numbers predate this and the flaw biased the affected arm toward confirming, which it still did not. The retest runner also takes `--json` instead of hardcoding its output path.
  • The pre-registered H6 retest ran both its arms and REJECTED both. Training on the 2016-2019 backfill does not clear the original construction (-0.00051, interval spanning zero), and a new continuous QB replacement-value differential (incumbent from prior-game dropback leader, pre-kickoff designation guard, replacement level estimated from prior seasons' sub-threshold passers) does not clear it either (-0.00065, interval spanning zero). A 2016-trained reference scores identically to the 2020-trained one, so more data alone was never the answer; the deployed rating features have absorbed most of what QB absence once carried. The direct effect stays real (four to five points of margin asymmetry in starter-change games) and the QB-out fact stays display context. Nothing deploys; the construction, tests and runner remain for the gated retests.
  • From the H5 cold reviews: `player_injury_status` now fails closed on undated injury rows for backtests (every 2025 row ships a null `date_modified`; the old escape admitted 29 percent of scored-era Questionable rows untimestamped), so an untimestamped season carries zero designation flags in a measurement rather than unverifiable ones. The fourth review caught that the same function also feeds the live player projections through `add_availability`, where a blanket closure had silently zeroed three live inputs for all of 2025; that caller now opts out explicitly (`require_dated=False`), keeping live behaviour byte-identical to what already ships, verified on the real frame. Undated-row policy for the live path stays an open decision in #sporacle-decisions (the backfield ADR). This change closes the H5 site's backtest guard only: any sibling site of the same `isna()` shape not closed by its own patch (S9's lineman count and the backfield teammate-absence builder each have a named owner there) remains open regardless of the order these patches merge. The recorded H5 numbers predate the fix and the reversal finding was re-verified by the reviewer on fully dated seasons alone. The retest runner takes `--json` instead of hardcoding its output path.
  • H5 refinement arm 1, the questionable flag weighted by trailing usage, is tested and REJECTED by its pre-registered rule (+0.0021 with an interval spanning zero, 16,266 held-out player weeks). The measurement that matters sits beneath the gate: questionable players' residuals on the current reference now run above projection rather than the 0.72 below that H5 originally recorded, because the deployed engine's questionable-aware bands have since absorbed the effect. Arm 2 (body-part and position return decay) is recorded as gated on a fresh premise measurement, a human call. Nothing deploys; the display risk flag is untouched.
  • Satori, the renderer behind the social cards, moves from 0.29 to 0.33 and routes text shaping through HarfBuzz, so card text is kerned slightly differently: the letterspaced eyebrow renders about 0.75% narrower. A sweep of title lengths found no wrapping or reflow change. A new test asserts that a rendered card is not byte identical to fallback.png, because the generator writes that fallback in place of any card it cannot render and every other assertion in the file holds for a fallback copy. `@types/node` and happy-dom move with it, both test only.
  • The stored social card images are regenerated with satori 0.33, so the checked-in renders match what the generator now produces. Every card keeps its 1200 by 630 size and its wording; only the letter spacing shifts.
  • C17, era-similarity weighted training for Engine 1's 2016-2019 backfill, is tested and does not deploy; the verdict reads VOID per the loop's power rule. A per-season Gaussian weight (league pass rate and points per team-game, z-scored against the fold's own anchor season) lets a dissimilar pre-2020 season contribute less to the fit instead of being dropped outright, which is what E-51 already rejected as an unweighted admission. The pre-registered selection on 2022 alone picked bandwidth 0.5, and the confirm on 2023-2025 (816 games) came back +0.00171 with an interval spanning zero and a detection floor of 0.00584, 3.41 times the movement, so the sample could not have decided either way and `TRAIN_FIRST_SEASON` stays 2020 in production. The hypothesis log records the pre-registration, the authorizing decision that narrowed ADR-0004's objection, and the confirm numbers; `docs/model_research_candidates.md` moves C17 to Killed.
  • A4 round two, deep lookback (unlimited history back to 2016) crossed with half-life 4 and 8, is tested and does not deploy; the verdict reads VOID per the loop's power rule. Round one found the deployed 68-ordinal window a hard cutoff worth deepening; dropping it and decaying the full available history instead does not clear the deployed construction either. The pre-registered selection on 2022 alone picked half-life 4, and the confirm on 2023-2025 (816 games) came back -0.00152 with an interval spanning zero and a detection floor of 0.00521, 3.43 times the movement, so the sample could not have decided either way and the rating lookback stays at `LOOKBACK_WEEKS` (68 ordinals) in production. The hypothesis log records the pre-registration, the reproduced run, and the confirm numbers; round one's own season-end retest, gated on 2026 completing, is a different arm and stands unaffected.
  • B5, quarterback rated separately from team offence, is tested and does not deploy; the verdict reads VOID per the loop's power rule. The rating solve gains an optional QB block (`solve_ratings`' `qb_col`/`lambda_qb`, `attach_ratings`' matching keyword arguments) whose default reproduces the deployed construction exactly (pinned by test); a passer below career dropback replacement level pools into one shared column rather than getting a personal one, and the expected starter for an upcoming game follows the H6-retest incumbent-plus-injury-designation rule, its higher precision half only. Selection on 2022 alone picked lambda_qb=12 (Brier 0.2329 against a control of 0.2344), and the confirm on 2023-2025 came back -0.00085 with a 95 percent interval spanning zero and a detection floor of 0.00384, 4.5 times the movement, so the sample could not have decided either way and `lambda_qb` stays `None` in production. The hypothesis log records the pre-registration, the result and a dated note on the candidate tree; the runner (`python -m sporacle.experiments.b5_qb_rating`) reruns the grid and confirm and reproduces the recorded numbers.