Releases
1.9.8
For you
- Correction, 2026-09-18: the week 2 player projections this site has shown since about 03:36 UTC on 2026-09-18 were wrong, and are replaced. They showed veterans at roughly a third of their usual projection, with Josh Allen at 7.0 points and two rookie quarterbacks ranked first and second. The model was reading every veteran as a player who took none of his team's snaps, because snap share for a week not yet played was left empty and then counted as zero. Veterans now carry the snap share from the weeks they actually played: on the 2026-09-18 data Josh Allen projects at about 20 points and Jared Goff at about 16. The replacement includes players whose games had already kicked off, Josh Allen's 7.0 among them, so those numbers change after their kickoff; this entry is the record of what they were. A second fault never reached this site's pages but did reach the rankings tool for assistants, which from about 15:35 UTC on 2026-09-18 returned 76 players instead of about 336: once Thursday's game was in the data, only that game's players and the rookies were projected. Every player who has played this season is projected again, along with rookies, and a player whose game has already been played is projected from the same weeks as before his kickoff; his own box score is never an input. A player who has not yet played this season is still not projected.
- Nothing changes for you yet. The twice daily data refresh behind every forecast now starts on the minute it is scheduled for. The old scheduler started the morning refresh as much as five hours and 39 minutes late in the last week, so news from overnight could miss the midday forecast update.
- Team chips in the rankings table are readable again in light mode. The text colour chosen for each club was always correct, but a table wide link style replaced it with the page's default ink, so dark club colours such as the Rams, Browns and Vikings showed near black text on a near black chip.
- Nothing changes for you yet. The injury report's format now has a place, and a matching status colour, for a player who is inactive or suspended, though nothing publishes either one yet.
- Nothing changes for you yet. Behind the scenes, the official weekly injury report is set up to retire a faster designation once it covers the same moment or a later one, and to keep every disagreement between the two for review. That starts only once the official report carries its own report times, which it does not today.
- The rankings page and the four full value ranking pages are much lighter to download. Every value in those tables still names where it came from and which version produced it; what each kind of source means is now explained once below each table instead of again in every cell.
- The player comparison page now offers the players our rankings tables publish, the top 50 at each position in any scoring format, instead of every ranked player, which made it one of the heaviest pages on the site.
- Player projections and game forecasts now use this season's games instead of only last season's.
- Rankings show players' full names, such as Josh Allen rather than J.Allen, so searching the site for a full name finds the player. Rookies already had full names; veterans had the short form that play by play uses.
- Nothing changes for you while the data is healthy. If this season's play by play or player stats ever fail to load again after games have been played, the twice daily data refresh now keeps the data it already had and raises an alert, instead of reporting success, so forecasts are not quietly rebuilt from last season's numbers.
- The track record claimed results through week 18 when only week 1 had been played, which the site's own checks reject, and it listed every forecast for a game not yet played beside the finished ones. It now counts a week once every game in it has finished, lists only games that have been played, and shows player projections for a finished week whose player stats have not arrived as waiting, rather than as players who did not play. A tied game now reads as a tie. The confidence tier no longer shows a count of calls not yet played, which could only ever read zero once unplayed games stopped being listed; a call on a game still to be played stands on its game card.
- A finished game's live win likelihood history on its game card now shows the last stretch of the game, its latest in game readings and the final result, rather than the final result repeated. The live service had been saving a fresh copy of every game's pregame number and final result on every update, more than two thousand copies for one game, and a history request returned only the newest of them.
Under the hood
- In season projections are built for every player from the weeks before the one being projected. The cadence gave each player a row for the target week only when no row for that week existed at all, so the Thursday game's 15 box score rows switched it off and the 2026-09-18 refresh ranked 76 players; it now sets aside any actual row for the target week or later and gives every player who has played this season a row built from earlier weeks, so a Thursday player's projection is identical before and after his box score is ingested. Snap share and red zone volume are joined from their own tables on the row's own week, which does not exist for a week not yet played, so those rows now take each value as of the player's last played week; rows for completed weeks are untouched, and a player with no snap history at all is still left empty. On the 2026-09-18 master the fix projects 303 players from the model across all 32 clubs, plus 34 rookies from draft capital, and the training data it builds is identical to the data from before Thursday's game was ingested. A new publish guard refuses a rankings file with fewer than 80 percent of the players in the last rankings published for the same season and week: the run stops before writing anything, uploads nothing, triggers no site rebuild and exits with an error, so the previous publication stays in place, game cards included.
- The scheduled ingest moves from GitHub Actions to two Cloudflare cron triggers on the cadence Worker, at the same two times, 11:17 and 21:04 UTC. Across 13 scheduled runs from 2026-09-10 to 2026-09-16 the GitHub schedule started the morning run 2 hours 48 minutes to 5 hours 39 minutes late and the evening run 1 hour 48 minutes to 2 hours 38 minutes late, and Actions billing blocked two runs that were never retried. The ingest runs in its own container image, built by the same deploy as the forecast image and only after the quality gate passes, so it now runs checked code instead of whatever the development branch held at run time; the forecast image's dependencies are unchanged. The same seven steps run in the same order: the liveness ping still follows the master upload and comes before every database step, and the readiness check still runs whatever happened before it. A failure now also signals the external health check directly, and an incomplete readiness verification is logged there without marking the check as failing, so a recurring partial result cannot silence the alarm for a run that never starts. The GitHub workflow stays as a manual fallback without its schedule, which stops firing when this release reaches main. A run stuck on one step can no longer hold the schedule: once the container is told to stop, that step gets ten minutes to finish, and is then ended and reported as a failure. The agent publish guard now also refuses the ingest module and the other commands that write production data, namely the master upload, the depth chart archive upload and the official facts database load, and the dash scan now reads the new image file.
- A styleguide page that renders every site component in each of its states now exists for development builds only; it emits nothing into the published site. A new script generates the design token file from the site's own stylesheet and team colours, and fails on any colour that has no named primitive, so the design library follows the code instead of drifting from it.
- The injury report contract moves to 1.1.0: an optional confidence field, where absent means official, and two new designations, Inactive and Suspension, both shown with the ruled out status colour. One committed table now maps each source's wording onto the contract's values, and the pipeline, the scheduled injury worker and the site are each checked against it by a test so the three cannot drift apart. The fast source's rows that list a player with no game status are no longer candidates to serve: they are kept only as the evidence that retires an earlier designation, and a wording the table does not know is counted and reported loudly rather than guessed at.
- The nightly ingest now copies each player's newest dated official designation into the shared database, and the scheduled injury worker retires a faster designation only when that official record is dated the same moment or later, never one reported after it, and keeps doing so while the fast source is refusing requests. Where the two disagree at that point the official value wins and the disagreement is written to a review table. An official row with no report time of its own is left out rather than stamped with the time of the run, because a borrowed time would let a stale report retire a fresher one. The crosswalk load now records a stamp once a load completes; the worker reprocesses the whole feed when it sees that stamp move, so a crosswalk filled after the fact resolves the rows that failed to match without waiting for the feed itself to change.
- The site quality gate had been testing an empty site. It built with no forecast data, because no step pointed it at any and no storage credentials exist in that workflow, while its own comment said it tested a real build. A populated build failed two of its checks while the gate stayed green: the rankings page was 410,799 bytes against a 68,000 byte limit, and a set of team chips failed the contrast check. The gate now builds from a committed, clearly synthetic test data set that puts all 32 clubs on a data page and carries the empty, partial and full state of each surface, and it fails if that build ever comes out empty again. Pages that list data now have their own byte budget, an allowance plus a measured cost per listed row with hidden rows counted, capped at the row limit each page declares, so a page that starts listing everyone fails even when each row is cheap. Every number in that budget was measured, after the two heaviest pages were fixed rather than the limits raised. The accessibility check now also scans a player page, game pages in each state, a club with no roster and a full position page. The internal link check was resolving in page section links against the home page instead of the page that carried them, and now checks the right page.
- The home page no longer sometimes misses its loading speed target. On the simulated phone connection the speed check uses, it asked for eight files at once, and in some runs a font waited behind a script for one extra round trip, which landed the main content just past the 1.5 second limit. The brand mark is now drawn inside the page instead of fetched as an image, the game pages load one script instead of three, and the search script ships as one file instead of two; eighteen home page runs in a row measured about 1.35 seconds.
- Two ways the site's unit tests could break each other when run at the same time are fixed. Every test page build used to stage its work in one shared folder that each build deleted on its way out, and the social card tests rewrote the site's real card images while other builds were copying them. Each fix has a test that watches for the problem while the builds run, rather than checking afterwards, when the evidence is already gone. The tests still run one at a time in the hosted checks, because running them together bought no speed there: on the two cores those machines have, four at a time took 407 seconds and two took 385, against 290 one at a time.
- The play by play loader asked nflverse for an optional file it only publishes after the season ends. The archived library this pipeline depends on turned the resulting missing file into a plain error that the ingest read as no 2026 data yet. Every ingest since 2026-09-13 therefore skipped 2026 play by play and the weekly stats derived from it while still reporting success.
- Player names in the rankings and value rankings now come from the season's roster before the play by play short form. The roster's name had been merged into a column nothing read, which left 389 of 450 ranked names abbreviated on 2026-09-17. The same fix covers in season projections, whose rows for the week being projected carry no name of their own: a reproduction on that day's data with week 1 stats added published every one of 312 veterans as "nan", because a missing value was read as a name. A player with no usable name now falls back to the player id, as it always was meant to.
- The readiness check no longer reads the play by play library's undefined name crash as proof that a season's file is unpublished. That crash came from a missing participation file, not the play by play file, and it let readiness pass while the master held no 2026 play by play. Readiness now blocks when a season whose first final score is at least two days old has no play by play or player stat rows in the master, warns inside those two days, and checks the player stats table against the play by play it is derived from, because nflverse retired the old weekly file. The ingest refuses to upload a master missing either table for such a season. Weekly player stats keep coming from play by play rather than nflverse's new player stats file: on 2026 week 1 the derivation matched it on all 14 compared counting stats for 314 player weeks, with one receiving yards difference, and the 49 skill position rows it does not produce carry no offensive stats.
- A change to the changelog alone no longer starts the six minute site build and test run in the code quality gate, which ran on nearly every pull request because nearly every one adds a changelog entry. What a changelog edit can actually break is now checked on every change without a build: the site's own reader must accept the file, every value the changelog pages and the sitemap draw from it must resolve, and every entry must keep the public voice and avoid the wording the built pages refuse.
- The track record's through_week now comes from the schedule's final scores: the last week in which every game is final, where a game still unscored after a later week has results does not hold it back. It used to be the furthest week any published game card forecast, which is 18 once every run prices the whole season. Game calls list played games only, graded on the call that stood at kickoff, following the practice of grading only forecasts made before a decided event; in the record run 2026-02-inactives-57563b1e generated at 00:10 UTC on 2026-09-18, the 256 unplayed calls were 49,223 of its 56,150 compact bytes. The record is regenerated on every run, so the time matters as much as the run. Engine 2 reads weeks up to through_week only, and a finished week with no player stats rows in the master is reported in a new optional engine2.pending block instead of as did not play, which in a record published before 15:20 UTC on 2026-09-17 had reported 612 week 1 player weeks as absent when the stats had simply not been ingested. The pipeline now refuses to write a record whose through_week is past its run's week, the same rule the site applies, and a refused run carries the previous record forward marked stale instead of publishing none; the manual command's week is now checked the same way rather than forcing through_week, and the MCP track record tool states its scope when a played game sits past through_week.
- The live poller now stores a reading only while a game is in progress, plus one final per game, which the database insert enforces so a restarted container cannot write a second. A game the live scoreboard marks as ended without having been completed, such as a postponed game, is not stored or shown as a final, so it cannot take the place of the real result when the game is played. On 2026-09-17 the table held 36,063 rows from 2,255 polls, and one game alone had 1,978 pregame copies, 208 in game readings and 68 finals. The assistant tools' live snapshot now returns a game's in game readings and its first final, oldest first, 120 to a page with a cursor, and a finished game that has left the current board can still be asked for by the live scoreboard's own event id, though not by the schedule's game id, returning its history with the scoreboard fields left out; the live history route applies the same reading filter while keeping its own order and paging, so any rows written before this change that remain are skipped. Where those older rows hold the pregame number stored against a finished game ahead of its final, both now return the final. The stored history was cleaned up on 2026-09-17, reducing the table to 1,180 rows, with a backup kept, and the recorded week 1 data still carries two finals per game from before this change. The schedule facts also keep the time a final was first confirmed once nflverse also reports the same score, rather than erasing it the moment nflverse reported any final score at all; a score nflverse disagrees with is treated the same as a game nflverse alone had reported.
- A new command, sporacle live-backfill, rebuilds missing live win likelihood readings for a finished week by running our live model on nflverse play by play game states, and never copies another source's probability. It writes nothing until its rebuilt values agree with the readings the live service actually recorded, within 0.02 on at least 90 percent of matched readings and 80 percent in every game, and it fills only stretches of recorded history longer than 135 seconds, never replacing a recorded reading. Rebuilt rows would carry the source backfill and the model version, and the assistant tools, the live history route and the live game card would treat them accordingly: the label on every reading, left off the game card, ignored by the freshness check. Run once, for week 1 of this season on 2026-09-18, it did not clear that bar: 679 of 858 matched readings agreed, a share of 0.7914 against the required 0.90, so no rows were written. Week 1, including the season opener, still has no rebuilt trajectory.
- The assistant tools' player comparison still finds a player by the short form the rankings used before full names, such as J.Allen, by shortening each published full name the same way, and it still names every candidate instead of picking one when the short form fits more than one player. The rankings index was measured on a real build with full names against its byte budget before release.