LFLeagueFrenzy

LeagueFrenzy blog

How We Learn Your League's Scoring Rules

Your league's scoring settings are written in a dialect. A receiving touchdown is RC on MFL, rec_td on Sleeper, category 44 on Fleaflicker, stat_43 on ESPN, and stat_13 on Yahoo — five platforms, five spellings, one play. Before the value engine can build a board from your rules, it has to translate every spelling your commissioner priced into an event it can actually project.

Most of the time it can. This post is about the times it cannot: what happens when a league arrives priced in a code we have never seen, how that code is observed, triaged, and then mapped, built, or refused — and why "refused" is a verdict we write down, not a failure we hide. The worked example is real: the triage backlog exactly as it stood in production on 2026-08-10, all 26 rows of it.

One vocabulary, five dialects

Internally there is exactly one scoring vocabulary. When we captured the backlog on 2026-08-10, the definitions table behind it named 83 events — PASS_YD, REC_TD, TACKLE_SOLO, DST_POINTS_ALLOWED, and so on — each one something the engine knows how to rate from real historical stats. Every platform parser owns a translation table from its own dialect onto those events, and nothing downstream ever sees a platform code: your league's rules arrive as platform spellings and leave the parser as canonical events with point values and position scopes attached.

The catch is that commissioners can price nearly anything. MFL's own 2026 rules catalog names 153 codes, and the five dialects grow independently. So the parsers are built to fail honestly: a code with no translation is never guessed at, and it is never silently dropped either. It gets recorded.

The ledger

Every unknown code an adapter meets goes into a ledger: the platform, the code, the platform's own label for it, sample point values, and when it was first and last seen. Recording is best-effort at the feed boundary — it never fails a league fetch. Each open row carries one of three reasons:

  • No mapping. No parser maps the code onto an event. We have not yet taught the engine what this is.
  • Mapped, no stats. We know exactly what the code means — and nobody can score it, because no per-player stat source and no rate table exist for the event. A real gap, held open on purpose (more below).
  • Resolvable, pending sweep. The code maps and the engine can score it; the row is open only because no reconcile has run since support shipped.

The lifecycle closes itself. On every deploy, a sweep re-checks the ledger against what the parsers now understand and retires each row the engine can score. That makes the ledger a triage backlog, not a history — an empty ledger means nothing is outstanding. And retirement is reversible by evidence: a row is only ever recorded when a parser could not score the code, so if a retired code shows up in a fresh observation, that observation reopens the row.

The coverage chip and its invariant

Every board ships with a receipt: how many of your league's scoring rules the engine actually used, plus one consolidated warning naming the rules it could not. "Used" is a strict word here. A rule counts as covered only when it parsed and the engine can score it — a rule we can name but not score counts as uncovered, because it moves no ranks.

Behind the rule count sits a harsher number: the share of expected season points the dropped rules would have carried. Every rule is weighed in the same unit — expected season points — and a rule we cannot score is weighed at its point value times a fixed nominal season rate, a deliberate stand-in so it always registers. That rate is a constant on purpose: it used to be the median of our own rate tables, which meant adding rates for some event your league does not even score could nudge your coverage number. Your receipt should move when your league's scoring changes, or when we get better at scoring it — nothing else. Team-level events cut the other way: a defense's return touchdown projects off a rate table, but no ingested player row can ever earn it, so it counts as a covered rule while its expected points stay on the dropped side. When the dropped share crosses 10% of expected season points, the board is flagged degraded, in the product, where you can see it.

One invariant keeps all of this honest: mapping a code must never, by itself, improve the coverage number — only gaining the ability to score it may. Teaching a parser to recognize def_2pt upgrades an admin row from "never seen this" to "known gap". It does not change your board, so it is not allowed to change your board's receipt.

The gaps we keep on purpose

Two standing rows in the ledger show why refusal has to be a first-class verdict.

Defensive two-point conversion returns — Sleeper def_2pt, MFL D2, Fleaflicker category 125. The play happened three times in the 2020-2024 regular seasons, about 0.6 a year across the entire NFL. Worse, the play-by-play source records it as a play-level flag with no scorer attribution: the returner's name appears only in the free-text play description, and parsing prose to invent a stat owner is a line we do not cross. So the event exists in the vocabulary purely to be recognized — the ledger row reads "known gap" instead of "never seen" — and it scores zero for everyone, which is very nearly the truth.

Long offensive fumble-recovery touchdowns — Fleaflicker category 119. This one was measured and rejected. Over the same five seasons, the base play — an offensive player recovering a teammate's fumble for a touchdown — happened 13 times, to 13 different players; nobody did it twice. A rate table cannot carry a skill signal the data says does not exist, and at 6 points a seeded rate would be worth about 0.009 projected points a season — while flipping the code to "supported" and improving the coverage chip with the model no smarter. The long-distance variant is emptier still: of those 13, exactly one traveled 40 or more yards and none reached 80, and ten were zero-yard recoveries — the phenomenon is a goal-line pile-up, not a return. A committed script re-derives every one of those counts from a fresh play-by-play pull, so the refusal stays a measurement, not a folk memory.

The 2026-08-10 batch: 26 rules, three verdicts

On 2026-08-10 the ledger held 26 rows across three platforms. We triaged all of them: 9 mapped onto events the engine already scores, 2 built as new events with new measurement behind them, and 15 refused with the reason written down.

PlatformCodeWhat it isVerdict
MFLTKDDefensive tacklesMapped — solo tackles; the 0.75/.5 form is 0.75 points per 0.5 tackles
MFLTPATotal points allowedMapped — the per-game points-allowed ladder
MFLFCDFumble recoveries on defenseMapped — fumble recovery
MFLFCSFumble recoveries on special teamsMapped — ST fumble recovery; absorbed by FCD when a league prices both
MFLFUOFumbles on offenseMapped — fumbles
MFLRCYRushing plus receiving yardsMapped — fanned onto both yardage events; exact, yards sum linearly
Sleeperpass_td_40pPassing TDs of 40+ yardsMapped — fanned onto the 40-79 and 80-plus bands
Sleeperrush_td_40pRushing TDs of 40+ yardsMapped — same fan-out
Sleeperrec_td_40pReceiving TDs of 40+ yardsMapped — same fan-out
MFLTSYQB sacked yardageBuilt — new event; measured mean 6.6713 yards per sack
Sleeperdef_st_tdReturn-unit special-teams TDsBuilt — new event; measured 0.375 per team-season
Sleeperpass_td_50pPassing TDs of 50+ yardsRefused — a 50-yard edge sits strictly inside our measured 40/80 bands
Sleeperrush_td_50pRushing TDs of 50+ yardsRefused — same edge problem
Sleeperrec_td_50pReceiving TDs of 50+ yardsRefused — same edge problem
MFLP30Long passing plays, 30+ yardsRefused — no stat column at that threshold; we do not invent bands
MFLP50Long passing plays, 50+ yardsRefused — no stat column at that threshold; we do not invent bands
MFLSF1One-point safetiesRefused — the play occurs roughly once a decade
MFL#FTFumbles lost returned for a TDRefused — one observed league; zero is within noise of the truth
MFLHBEExtra points had blockedRefused — blocked XPs already live inside the missed-XP stat; splitting them would change every league's scoring
MFLPIPunts inside the 20Refused — punters are not in the product; the rule would score for nobody
MFLPNYPunt yardsRefused — punters are not in the product; the rule would score for nobody
MFLSTTASpecial-teams TDs allowedRefused — no source carries the opponent side of return TDs
MFLD2Defensive conversion returnsRefused — recognized, but no scorer attribution exists (standing decision)
Sleeperdef_2ptDefensive 2-point returnsRefused — same standing decision
Fleaflicker125Defensive 2-point returnsRefused — same standing decision
Fleaflicker119Long offensive fumble-recovery TDsRefused — measured and rejected: 1 of 13 in five seasons went 40+ yards

No league identifiers appear here on purpose; the codes are the story. A few of these verdicts deserve their receipts in full.

MFL TKD reads 0.75 and pays 1.5. MFL renders some rules in a per-unit fraction form: 0.75/.5 means 0.75 points per 0.5 tackles — 1.5 points per tackle. That reading was verified against the league's live rules export rather than assumed: the same export prices assists separately at 0.75, which rules out the tempting solo/assist-split interpretation. The assist sibling ASD was mapped in the same change. If tackles are your league's currency — and in IDP leagues they are — getting the denominator right doubles the price of the position's core stat.

MFL TPA is Sleeper scoring wearing an MFL code. It arrived as seven per-game brackets of points allowed — byte-for-byte the ladder Sleeper expresses as its pts_allow_* keys. It rides the same banded path onto the same points-allowed event, brackets and all.

Sleeper's long-TD thresholds stack, and that is what makes refusing half of them cheap. Our long-TD rates are measured on two disjoint distance bands: 40-79 yards and 80-plus. Sleeper prices open thresholds instead, so pass_td_40p fans exactly onto both bands. The 50p keys are the half we refused: a 50-yard edge sits strictly inside our band edges, and reusing the 40-79 rate would over-pay 40-49-yard touchdowns. But we verified against live Sleeper week stats that the thresholds are cumulative — every stat line with a 50-plus touchdown increments the 40p key too — so a league pricing both keys loses only the incremental 50-plus bonus to the refusal, never the whole long-TD payment.

Update, 2026-08-16. This refusal has since been reversed, and the reason is the point of writing them down. Re-reading it later, the blocker was not the data — it was our own band edges, which we had chosen. The distances were already in the play-by-play we ingest. So we split the 40-79 band at 50 and re-measured: the new 40-49 and 50-79 shares partition the old 40-79 share exactly, so no existing league's scoring moved, and the 50p keys now price. The table above is left as it stood on 2026-08-10 — a snapshot, not a running scoreboard. A refusal that survives re-reading is an answer; this one did not, and that is the loop working rather than failing.

Update, 2026-08-19. The same thing happened again, one table row over. MFL's P30 and P50 were refused above for want of a stat column at those thresholds — and that was true of the column we were reading. It was not true of the data. The counts were coming from season-long threshold totals nflverse publishes at 20 yards and 40 yards and nowhere else; the play-by-play underneath carries the length of every play. So we moved the derivation onto play-by-play, split the bands at 30 and 50, and now the whole P20/P30/P40/P50 ladder prices. We checked the swap rather than assuming it: counting per player, not just per league, the new source reproduces the old passing totals exactly in all ten seasons 2015-2024. Rushing and receiving differ by a handful of plays a season, and the difference is laterals — a shovel pass pitched onward splits its yards between two players in the season file, while the play-by-play records one distance for the whole play. We report that gap instead of closing it, because closing it would mean inventing an attribution the source does not make. Two refusals in this table have now been reversed by re-reading them, and both times the blocker turned out to be a choice of ours rather than a fact about football.

MFL FCS and FCD needed an absorption rule, not just a mapping. One real league prices defensive and special-teams fumble recoveries side by side. MFL pays each recovery once — the two codes split recoveries by phase — but our underlying stat column counts every recovery in one number, so two naive rules would pay each special-teams recovery twice. When both codes appear together, one absorbs the other, and the pair prices the column exactly once.

And the two builds:

Sack yardage (MFL TSY). The source column for QB sack yards is negative-signed in every published season we checked, 2019-2025, so we store magnitudes — a negative stat times a league's negative per-yard price would flip the penalty into a reward. The rate is the position's existing sack curve times the measured mean yards lost per sack: 6.6713 on the QB side, over 8,963 sacks. The defender-side sibling (MFL SKY, Sleeper idp_sack_yd) got its event in the same change, from the defender-side column: 6.6643 over 8,907 sacks, pooled across positions because yards per sack is a physical quantity — per-position means run 6.2 to 7.1. The two ledgers describe the same collisions from opposite sides and agree within 0.1%, which is the cross-check that both multipliers measure the same thing.

Return-unit touchdowns (Sleeper def_st_td). The team's kickoff- and punt-return scores, distinct from the individual returner's credit — Sleeper prices both in the same leagues, so the two must stack, and folding them into one event would double-count. Measured over 2020-2024 play-by-play: 60 return-unit touchdowns — 33 kickoff, 27 punt — a flat 0.375 per team-season, with blocked-punt returns excluded because they already belong to a different event. Flat on purpose: at that frequency, a percentile shape would be fit to noise.

Methodology

  • The measured rates above — the sack-yardage means, the return-touchdown rate, and the counts behind both refusals-by-measurement — are computed from public NFL season stats and play-by-play, data via nflverse, over the season windows stated inline (2019-2025 for sack yardage, 2020-2024 for the play-by-play counts).
  • Code semantics were verified against the platforms' live rules exports and live week stats, not documentation folklore — that is where the TKD fraction form and the cumulative 50p behavior come from.
  • The 26-row worked example is a capture of the production triage ledger from 2026-08-10. No customer league identifiers are published, here or anywhere.
  • Every number in this post is pinned in a committed fixture, and a build-time test fails if the prose and the fixture drift — the same discipline that keeps the projection scoreboard honest.

Once this batch deploys and the sweep runs, the backlog shrinks to exactly the rules we have chosen not to score — and the next unknown code a league brings starts the loop again: observed, recorded, triaged, and either scored or refused in writing.

If your league prices something weird, yours is exactly the league this was built for. Paste your league URL and read the board — and the receipt — the engine builds from your rules.

See what your rules do to the board

Connect your league — free

More reading: the LeagueFrenzy blog, value-based drafting, or all guides.