A new way to score fantasy football defenses — based on what happens on each drive rather than end-of-game counting stats. Kill a drive, get points (more for better field position handed to your offense); allow a score, lose a little. Every down matters and the score climbs all game instead of spiking on splash plays.
→ PITCH.md has the full case: the scoring table, calibration methodology, VAR correlations vs Yahoo, rank-movement analysis, and the 2025 out-of-sample validation.
Defined canonically in src/scoring.js. Per opponent drive:
stops score +1 / +1.5 / +2.5 / +3.5 by where your offense takes over (inside own
20 / out to midfield / past midfield / inside their 20), stop ending a half +1.5,
TD allowed −1, FG allowed −0.5, def/ST TD +6, safety +2 plus the takeover tier
(a safety is a stop and 2 real points and possession).
Headline numbers: weekly VAR Spearman vs Yahoo ≈ 0.79–0.82, season VAR ≈ 0.83–0.90, negative-score weeks 7.5% (Yahoo: 7.6%). Calibrated on 2022–24, held up out-of-sample on 2025.
src/
scoring.js THE scoring system (config + per-drive scorer)
espn.js ESPN API helpers (fetch, yardline conversion)
fetch-season.js download a season of game JSONs → data/raw/<year>/
fetch-week.js download a single week (in-season use)
extract-drives.js raw JSONs → data/drives.csv
extract-scores.js raw JSONs → data/game-scores.csv
smoke-test.js fetch one known game, eyeball the parsing
eval-lib.js analysis core: Yahoo baseline, scorers, metrics
evaluate.js report card for the final config (vs unconstrained optimum)
var-analysis.js VAR correlations, value-by-rank, rank movement
validate.js bootstrap CIs, parameter sensitivity, EP grounding
search.js the broad grid search that found this family (~87k configs)
clean-search.js the "memorable values" constrained search that picked G1b
data/
drives.csv one row per drive, 2022–25 regular seasons (~23.6k drives)
game-scores.csv one row per game: final scores + team sacks
raw/<year>/ cached ESPN game JSONs (gitignored, ~880 MB)
npm run extract # rebuild the CSVs from data/raw
npm run evaluate # full report card
npm run var # VAR deep-dive (optionally: node src/var-analysis.js 2025)
npm run validate # robustness checks
node src/fetch-season.js 2026 # when the time comesyl100coordinate: yards from the possessing team's scoring end zone. 0 = about to score, 100 = own goal line.next_start_yl100on a stop = where the defense's own offense takes over — that's the field-position signal scoring uses. It's empty when possession didn't change or the half ended.drives.csvcolumns: season, week, game_id, home/away_team, pos_team, def_team, drive_num, result (normalized), result_raw (ESPN's string — distinguishes pick-six vs return TD), start/end/next_start_yl100, end_period, end_clock.- Known blind spots in ESPN drive data: kickoff-return TDs are buried in ordinary "TD" drives (~5–7/season league-wide), and special-teams fumbles don't appear as drive results. Both only matter for Yahoo-baseline measurement, not for the system.
Scoring is final (June 2026). Next: applying it to a Sleeper league in real time during the season — Sleeper has no public write API, so this will take creativity (likely: compute scores from live ESPN drive data and surface them to the league via bot/page/spreadsheet rather than writing into Sleeper itself).