Case Study: The Ravens’ Option Run Game with Lamar Jackson (2019–2025)

How Baltimore turned a quarterback’s legs into an extra blocker on every snap, what it won, what defenses did about it, and how the era ended

Show the Python (shared helpers for this chapter’s diagrams)
import logging
logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR)
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Ellipse, FancyArrowPatch, Rectangle, RegularPolygon
from gridiron import Play, Player, Field, offense, technique, style

OL_NAMES = {"LT": "LT", "LG": "LG", "C": "C", "RG": "RG", "RT": "RT"}
GREEN = "#0f7a55"           # darker step of the aqua highlight, for text on the light field
PURPLE = style.SERIES[6]    # a ring colour that is neither offense blue nor defense orange


def named_line(players):
    """Label the five offensive linemen so the reader can follow each one."""
    return [p.moved(label=OL_NAMES[p.key]) if p.key in OL_NAMES else p for p in players]


def O(key, pos, d, w, label):
    return Player(key, pos, "off", d, w, label=label)


def D(key, pos, d, w, label):
    return Player(key, pos, "def", d, w, label=label)


def line5():
    return [q for q in named_line(offense("gun_2x2")) if q.key in OL_NAMES]


def pistol(extra=(), te=("Y", "U")):
    """The pistol box: five linemen, the quarterback 4 yards deep, the back 7 yards deep directly behind him,
    and inline tight ends (Y on the right, U on the left). Receivers are left out unless passed in `extra`."""
    tes = []
    if "Y" in te:
        tes.append(O("Y", "TE", -0.7, 4.8, "Y"))
    if "U" in te:
        tes.append(O("U", "TE", -0.7, -4.8, "U"))
    return line5() + [O("QB", "QB", -4.0, 0.0, "Q"), O("RB", "RB", -7.0, 0.0, "R"), *tes, *extra]


def front62(fs=False):
    """Eight in the box against two tight ends: a 9-technique outside each tight end, a 5 on each tackle,
    a 1 and a 3 inside, and two linebackers (a 6-2 look). Optional free safety in the deep middle."""
    dfn = [D("BE", "OLB", 1.0, -6.3, "9"), D("B5", "DE", 1.0, -4.0, "5"), D("DT1", "DT", 1.0, -0.7, "1"),
           D("DT3", "DT", 1.0, 2.4, "3"), D("P5", "DE", 1.0, 4.0, "5"), D("PE", "OLB", 1.0, 6.3, "9"),
           D("MIKE", "MIKE", 4.5, 1.8, "M"), D("WILL", "WILL", 4.5, -2.0, "W")]
    if fs:
        dfn.append(D("FS", "FS", 12.5, 0.0, "FS"))
    return dfn


def check_legal(players):
    """Seven offensive players on the line, and only the two at its ends may be eligible receivers."""
    off = [q for q in players if q.side == "off"]
    on = sorted([q for q in off if q.d > -1.0], key=lambda q: q.w)
    assert len(on) == 7, [q.key for q in on]
    for q in on[1:-1]:
        assert q.key in OL_NAMES, f"{q.key} is covered up but not a lineman"


def go_to(p, key, end, kind="block", delay=0.0, speed=None, via=()):
    """Move `key` through absolute (d, w) points `via` to `end`, as a block, path, run or pull."""
    start = np.array(p._cur(key))
    pts = [tuple(np.array(q) - start) for q in (*via, end)]
    if kind == "block":
        return p.block(key, pts=pts, delay=delay, speed=speed)
    return {"path": p.path, "run": p.run, "pull": p.pull}[kind](key, pts, delay=delay, speed=speed)


def _P(fld, p, d, w):
    return fld.xy(*p.to_abs(d, w))


def branch(fld, p, pts, color=style.THIRD, label=None, label_at=None, lw=2.0, fontsize=7.5,
           dash=(0, (3, 2.2)), zorder=6):
    """A dashed 'option' path through absolute (d, w) points, with an arrowhead."""
    xs, ys = _P(fld, p, np.array([q[0] for q in pts]), np.array([q[1] for q in pts]))
    fld.ax.plot(xs[:-1], ys[:-1], color=color, lw=lw, ls=dash, zorder=zorder, solid_capstyle="round")
    fld.ax.add_patch(FancyArrowPatch((xs[-2], ys[-2]), (xs[-1], ys[-1]), arrowstyle="-|>",
                                     mutation_scale=11, color=color, lw=lw, zorder=zorder))
    if label:
        x, y = _P(fld, p, *(label_at or pts[-1]))
        fld.ax.text(x, y, label, fontsize=fontsize, color=color, fontweight="bold", ha="center",
                    va="center", zorder=9,
                    bbox=dict(boxstyle="round,pad=0.2", fc=style.SURFACE, ec=color, lw=0.8))


def read_line(fld, p, a, b, color=style.THIRD):
    """The quarterback's eyes: a dashed green line between two (d, w) spots."""
    (x0, y0), (x1, y1) = _P(fld, p, *a), _P(fld, p, *b)
    fld.ax.plot([x0, x1], [y0, y1], color=color, lw=1.3, ls=(0, (4, 2)), zorder=4)


def say(fld, p, d, w, text, color=style.INK_2, fontsize=7.5, ha="center", weight="normal"):
    """A short on-field label at (d, w)."""
    x, y = _P(fld, p, d, w)
    fld.ax.text(x, y, text, fontsize=fontsize, color=color, ha=ha, va="center", zorder=9, fontweight=weight,
                bbox=dict(boxstyle="round,pad=0.18", fc=style.SURFACE, ec="none", alpha=0.9))


def point_to(fld, p, text_at, target, text, color=style.INK_2, fontsize=7, shrink=7, weight="normal"):
    """A label with a thin leader line to the player or spot it names."""
    (xt, yt), (xp, yp) = _P(fld, p, *text_at), _P(fld, p, *target)
    fld.ax.annotate(text, (float(xp), float(yp)), xytext=(float(xt), float(yt)), fontsize=fontsize,
                    color=color, ha="center", va="center", zorder=9, fontweight=weight,
                    arrowprops=dict(arrowstyle="-", color=color, lw=0.7, shrinkA=2, shrinkB=shrink),
                    bbox=dict(boxstyle="round,pad=0.18", fc=style.SURFACE, ec="none", alpha=0.9))


def tackle_x(fld, p, d, w, color=style.DEFENSE_DARK):
    x, y = _P(fld, p, d, w)
    fld.ax.plot([x], [y], marker="X", ms=10, color=color, mec="white", mew=0.8, zorder=12)


def box_shade(fld, p, d0, d1, w0, w1, color=GREEN):
    """Shade the box (alignment coordinates) to count defenders against blockers."""
    (x0, y0), (x1, y1) = _P(fld, p, d0, w0), _P(fld, p, d1, w1)
    fld.ax.add_patch(Rectangle((min(x0, x1), min(y0, y1)), abs(x1 - x0), abs(y1 - y0), fc=color, alpha=0.07,
                               ec=color, lw=0.8, ls=(0, (3, 2)), zorder=1))


def clean(ax):
    """Box-zoomed diagrams: drop hash ticks and yard numbers, clip anything outside the window."""
    for ln in list(ax.lines):
        xs, ys = ln.get_data()
        if len(xs) == 2 and ln.get_linewidth() == 0.5 and np.hypot(xs[1] - xs[0], ys[1] - ys[0]) < 1.0:
            ln.remove()
    for t in list(ax.texts):
        if t.get_text().isdigit() and t.get_alpha() == 0.55:
            t.remove()
        else:
            t.set_clip_on(True)


def draw(p, width_in=5.8, ax=None, **kw):
    """Play.draw, sized for the page, cleaned for a box-sized window."""
    fld = p.draw(width_in=width_in, ax=ax, **kw)
    clean(fld.ax)
    return fld


def pdf_build():
    return os.environ.get("RTG_FORMAT", "").lower() in ("pdf", "typst", "print")


# ---------------------------------------------------------------- frame strips and videos (as in 03-04)
MARK_R = 0.6   # frame-strip marker radius in yards


def tracking(play):
    df = play.to_tracking()
    df["_id"] = df["displayName"].astype(str)
    return df


def carrier(play, t):
    """Who holds the ball at time t (QB until the first handoff or pitch arrives)."""
    holder = "QB"
    for kind, to, te in sorted(play.ball_events, key=lambda e: e[2]):
        if t >= te + (0.25 if kind == "pitch" else 0.0):
            holder = to
    return holder


def panel(ax, play, df, t, lateral, window, rings=()):
    """One moment of a simulated play: markers, trails, the ball, rings (and a green ring on the ball carrier)."""
    tmap = df.groupby("frameId")["time"].first()
    fid = int((tmap - t).abs().idxmin())
    ylo, yhi = sorted((play.ball_y - lateral[0], play.ball_y - lateral[1]))
    fld = Field(ax=ax, orient="vertical", los=play.los, window=window, numbers=False, hashes=False,
                lateral=(ylo, yhi), to_go=play.to_go)
    cur = df[df["frameId"] == fid]
    shown = cur[cur["x"].between(play.los + window[0] + 0.4, play.los + window[1] - 0.4)
                & cur["y"].between(ylo + 0.4, yhi - 0.4)]
    ids = set(shown["_id"])
    trails = df[(df["frameId"] <= fid) & df["_id"].isin(ids) & (df["side"] != "ball")
                & df["x"].between(play.los + window[0], play.los + window[1])]
    for (_, side), g in trails.groupby(["_id", "side"]):
        xs, ys = fld.xy(g["x"].values, g["y"].values)
        ax.plot(xs, ys, color=style.OFFENSE if side == "off" else style.DEFENSE, lw=0.9, alpha=0.4, zorder=3)
    ppy = fld.pts_per_yard()
    for _, r in shown.iterrows():
        x, y = (float(v) for v in fld.xy(r["x"], r["y"]))
        if r["side"] == "ball":
            ax.add_patch(Ellipse((x, y + 0.5), 0.75, 0.45, fc=style.BALL, ec="white", lw=0.5, zorder=11))
            continue
        lab = str(r.get("label", "") or "")
        if r["side"] == "off":
            ax.add_patch(Circle((x, y), MARK_R, fc=style.OFFENSE, ec="white", lw=0.6, zorder=8))
            tc = "white"
        else:
            ax.add_patch(RegularPolygon((x, y), 4, radius=MARK_R * 1.3, orientation=np.pi / 4,
                                        fc=style.DEFENSE_TINT, ec=style.DEFENSE, lw=1.0, zorder=8))
            tc = style.INK
        fs = max(4.0, min(8.0, ppy * (0.8 if len(lab) <= 1 else 0.6)))
        ax.text(x, y, lab, ha="center", va="center", fontsize=fs, color=tc, fontweight="bold", zorder=10)
    for key, col in list(rings) + [(carrier(play, t), style.THIRD)]:
        rr = cur[cur["displayName"] == key]
        if len(rr):
            x, y = fld.xy(rr["x"].iloc[0], rr["y"].iloc[0])
            ax.add_patch(Circle((float(x), float(y)), 1.05, fc="none", ec=col, lw=1.7, zorder=7))
    return fld


def strip(play, times, lateral, window, notes=None, rings=(), width_in=6.5, ncols=2):
    """Frame strip of one simulated play, wrapped over rows, with a caption line under each panel."""
    df = tracking(play)
    nrows = int(np.ceil(len(times) / ncols))
    pw = width_in / ncols
    span_w, span_d = lateral[1] - lateral[0], window[1] - window[0]
    fig, axes = plt.subplots(nrows, ncols, squeeze=False,
                             figsize=(width_in, nrows * (pw * span_d / span_w + (0.6 if notes else 0.25))))
    for ax in axes.flat[len(times):]:
        ax.axis("off")
    for k, (ax, t) in enumerate(zip(axes.flat, times)):
        panel(ax, play, df, t, lateral, window, rings)
        ax.set_title(f"{k + 1}.  " + ("snap" if abs(t) < 1e-6 else f"{t:+.1f} s"), fontsize=8,
                     color=style.INK_2, loc="left")
        if notes:
            ax.text(0.5, -0.03, notes[k], transform=ax.transAxes, ha="center", va="top", fontsize=7.2,
                    color=style.INK)
    fig.tight_layout(h_pad=1.4 if notes else 0.6, w_pad=0.5)
    return fig


def video(play, times, lateral, window, notes=None, rings=(), width_in=5.4):
    """Web version: the same panel drawn every tenth of a second, with the current step's note."""
    import shutil
    from matplotlib import animation
    from IPython.display import HTML, display
    df = tracking(play)
    grid = np.round(np.arange(float(df["time"].min()), float(df["time"].max()) + 1e-9, 0.1), 2)
    span_w, span_d = lateral[1] - lateral[0], window[1] - window[0]
    fig, ax = plt.subplots(figsize=(width_in, width_in * span_d / span_w + 0.6))

    def update(t):
        ax.clear()
        panel(ax, play, df, t, lateral, window, rings)
        txt = "snap" if abs(t) < 0.05 else f"{t:+.1f} s"
        if notes:
            cur = [m for tt, m in zip(times, notes) if tt <= t + 1e-6]
            txt += ("\n" + cur[-1].replace("\n", " ")) if cur else ""
        ax.set_title(txt, fontsize=7.5, color=style.INK, loc="left")
        return []

    anim = animation.FuncAnimation(fig, update, frames=list(grid), interval=100)
    html = (anim.to_html5_video(embed_limit=40).replace("<video ", '<video style="max-width:100%;height:auto" ')
            if shutil.which("ffmpeg") else anim.to_jshtml(default_mode="once"))
    plt.close(fig)
    display(HTML(html))


def play_animation(play, times, lateral, window, notes=None, rings=(), **kw):
    """Video on the website; a numbered, captioned frame strip in the PDF."""
    if pdf_build():
        strip(play, times, lateral, window, notes=notes, rings=rings, **kw)
        plt.show()
    else:
        video(play, times, lateral, window, notes=notes, rings=rings)
Show the Python (the chapter’s data: nflverse play-by-play, participation and FTN charting)
from gridiron.data import nfl, pbp

SEASONS = list(range(2018, 2026))
COLS = ["season", "season_type", "week", "game_id", "play_id", "posteam", "defteam", "play_type", "epa", "success",
        "qb_scramble", "qb_kneel", "aborted_play", "down", "ydstogo", "rusher_player_id", "rusher_player_name",
        "passer_player_name", "rush", "pass", "yards_gained", "rushing_yards", "run_location", "wp",
        "half_seconds_remaining", "qtr", "score_differential", "fumble_lost", "interception"]
ALL = pbp(SEASONS, columns=COLS)
ALL = ALL[ALL["play_type"].isin(["run", "pass"]) & ALL["down"].notna() & ALL["epa"].notna() & (ALL["qb_kneel"] == 0)]
ALL["designed"] = (ALL["rush"] == 1) & (ALL["qb_scramble"] == 0) & (ALL["aborted_play"] == 0)
REG = ALL[ALL["season_type"] == "REG"]
POST = ALL[ALL["season_type"] == "POST"]
RUNS = REG[REG["designed"]]

# --- rushing EPA per designed run, every team, every season
TEAM_RUN = RUNS.groupby(["season", "posteam"])["epa"].mean().rename("epa").reset_index()
TEAM_RUN["rank"] = TEAM_RUN.groupby("season")["epa"].rank(ascending=False).astype(int)
LG_RUN = RUNS.groupby("season")["epa"].mean()
BAL_RUN = TEAM_RUN[TEAM_RUN["posteam"] == "BAL"].set_index("season")
BAL_SR = RUNS[RUNS["posteam"] == "BAL"].groupby("season")["success"].mean()
LG_SR = RUNS.groupby("season")["success"].mean()
ALL_EPA_RANK = (REG.groupby(["season", "posteam"])["epa"].mean().groupby("season").rank(ascending=False)
                .xs("BAL", level=1).astype(int))
RUSH_YDS_RANK = (REG.groupby(["season", "posteam"])["rushing_yards"].sum().groupby("season")
                 .rank(ascending=False, method="min").xs("BAL", level=1).astype(int))

# 2018: Joe Flacco started weeks 1-10, Lamar Jackson weeks 11-17
B18 = REG[(REG["season"] == 2018) & (REG["posteam"] == "BAL")]
R18 = {lab: (B18[m]["designed"].mean(), B18[m & B18["designed"]]["epa"].mean())
       for lab, m in [("flacco", B18["week"] <= 10), ("jackson", B18["week"] >= 11)]}

# designed runs by everyone but the quarterbacks (anyone with 20+ dropbacks for that team that season)
_qbs = set(REG[REG["passer_player_name"].notna()].groupby(["season", "posteam", "passer_player_name"]).size()
           .loc[lambda x: x >= 20].reset_index()["passer_player_name"])
_nq = RUNS[~RUNS["rusher_player_name"].isin(_qbs)]
_nqt = _nq.groupby(["season", "posteam"])["epa"].mean()
NQ_BAL = _nqt.xs("BAL", level=1)
NQ_RANK = _nqt.groupby("season").rank(ascending=False).xs("BAL", level=1).astype(int)
NQ_LG = _nq.groupby("season")["epa"].mean()

# neutral early downs: run share
NEU = REG[REG["down"].isin([1, 2]) & REG["wp"].between(0.2, 0.8) & (REG["half_seconds_remaining"] > 120)]
NEU_RR = NEU.groupby(["season", "posteam"])["designed"].mean()
NEU_RANK = NEU_RR.groupby("season").rank(ascending=False, method="min").xs("BAL", level=1).astype(int)
NEU_BAL = NEU_RR.xs("BAL", level=1)
NEU_LG = NEU.groupby("season")["designed"].mean()

# dropbacks (everything that isn't a designed run: passes, sacks, scrambles)
_DB = REG[~REG["designed"]].groupby(["season", "posteam"])["epa"].mean()
DB_BAL = _DB.xs("BAL", level=1)
DB_RANK = _DB.groupby("season").rank(ascending=False).xs("BAL", level=1).astype(int)
DB_LG = REG[~REG["designed"]].groupby("season")["epa"].mean()

# --- Lamar Jackson's designed runs (no scrambles, botched snaps or likely sneaks), per game he played
BAL = ALL[ALL["posteam"] == "BAL"]
LJ = BAL[(BAL["rusher_player_name"] == "L.Jackson") & BAL["designed"]
         & ~((BAL["ydstogo"] <= 2) & (BAL["run_location"] == "middle"))]
LJ_REG = LJ[LJ["season_type"] == "REG"]
LJ_GAMES = BAL[(BAL["season_type"] == "REG") & (BAL["passer_player_name"] == "L.Jackson")].groupby("season")["game_id"].nunique()
LJ_N = LJ_REG.groupby("season").size()
LJ_PG = (LJ_N / LJ_GAMES)
LJ_EPA = LJ_REG.groupby("season")["epa"].mean()
LJ_YPC = LJ_REG.groupby("season")["yards_gained"].mean()

# --- participation (personnel, formation, box count); NGS through 2022, FTN from 2023
parts = []
for yr in SEASONS:
    pa = nfl().load_participation([yr]).to_pandas()[["nflverse_game_id", "play_id", "offense_personnel",
                                                     "defense_personnel", "offense_formation", "defenders_in_box"]]
    parts.append(pa)
PART = pd.concat(parts, ignore_index=True).rename(columns={"nflverse_game_id": "game_id"})
RP = REG.merge(PART, on=["game_id", "play_id"], how="left")
_s = RP["offense_personnel"].fillna("")
RP["wr"] = _s.str.extract(r"(\d+) WR")[0].astype(float)
RP.loc[_s == "", "wr"] = np.nan
RP["heavy"] = (RP["wr"] < 3).where(RP["wr"].notna())
HEAVY = RP.groupby(["season", RP["posteam"] == "BAL"])["heavy"].mean().unstack()   # columns False (others), True (BAL)
LG_HEAVY = RP.groupby("season")["heavy"].mean()
PISTOL_NGS = RP[RP["season"] <= 2022].assign(p=lambda x: x["offense_formation"] == "PISTOL")
PISTOL_BAL = PISTOL_NGS[PISTOL_NGS["posteam"] == "BAL"].groupby("season")["p"].mean()
PISTOL_LG = PISTOL_NGS.groupby("season")["p"].mean()
P19 = PISTOL_NGS[(PISTOL_NGS["season"] == 2019) & PISTOL_NGS["p"]].groupby("posteam").size().sort_values(ascending=False)
B19 = RP[(RP["season"] == 2019) & (RP["posteam"] == "BAL")]
L19 = RP[RP["season"] == 2019]
PIST19_RUN = (B19[B19["designed"]]["offense_formation"] == "PISTOL").mean()
P13 = B19[B19["offense_personnel"] == "1 RB, 3 TE, 1 WR"]
P13_PASS = 1 - P13["designed"].mean()
L13_PASS = 1 - L19[L19["offense_personnel"] == "1 RB, 3 TE, 1 WR"]["designed"].mean()
P13_BASE = P13["defense_personnel"].fillna("").str.contains("4 DB").mean()
BASE_BAL19 = B19["defense_personnel"].fillna("").str.contains(r"\b4 DB").mean()
BASE_LG19 = L19["defense_personnel"].fillna("").str.contains(r"\b4 DB").mean()
_NGS = RP[(RP["season"] <= 2022) & RP["defense_personnel"].notna()]
_NGS = _NGS.assign(base=_NGS["defense_personnel"].str.contains(r"\b4 DB"))
BASE_BY = _NGS.groupby(["season", _NGS["posteam"] == "BAL"])["base"].mean().unstack()   # NGS labels, 2018-2022
P11_RUN_BAL = B19[B19["offense_personnel"] == "1 RB, 1 TE, 3 WR"]["designed"].mean()
P11_RUN_LG = L19[L19["offense_personnel"] == "1 RB, 1 TE, 3 WR"]["designed"].mean()
BOX = RP[RP["designed"] & (RP["defenders_in_box"] >= 3)]
BOX_BAL = BOX[BOX["posteam"] == "BAL"].groupby("season")["defenders_in_box"].mean()
BOX_LG = BOX.groupby("season")["defenders_in_box"].mean()
BOX8_BAL = (BOX[BOX["posteam"] == "BAL"]["defenders_in_box"] >= 8).groupby(BOX[BOX["posteam"] == "BAL"]["season"]).mean()
BOX8_LG = (BOX["defenders_in_box"] >= 8).groupby(BOX["season"]).mean()

# --- FTN charting 2022-2025: where the quarterback stood (U = under center, S = shotgun, P = pistol)
FTN = nfl().load_ftn_charting([2022, 2023, 2024, 2025]).to_pandas()
FTN = FTN.rename(columns={"nflverse_game_id": "game_id", "nflverse_play_id": "play_id"}).drop(columns=["season", "week"])
RF = REG[REG["season"] >= 2022].merge(FTN[["game_id", "play_id", "qb_location", "n_offense_backfield"]],
                                      on=["game_id", "play_id"], how="inner")
QBLOC = RF.groupby(["season", RF["posteam"] == "BAL"])["qb_location"].value_counts(normalize=True).unstack().fillna(0)
HENRY = RF[(RF["posteam"] == "BAL") & (RF["rusher_player_name"] == "D.Henry") & RF["designed"]]
HENRY_LOC = HENRY.groupby("qb_location").agg(n=("epa", "size"), epa=("epa", "mean"), ypc=("yards_gained", "mean"))
BAL_RUN_UC = RF[(RF["posteam"] == "BAL") & RF["designed"]].groupby("season")["qb_location"].apply(lambda s: (s == "U").mean())

# --- playoffs
GAMES = nfl().load_schedules(SEASONS).to_pandas()
GB = GAMES[(GAMES["home_team"] == "BAL") | (GAMES["away_team"] == "BAL")].copy()
GB["bal"] = np.where(GB["home_team"] == "BAL", GB["home_score"], GB["away_score"])
GB["opp_pts"] = np.where(GB["home_team"] == "BAL", GB["away_score"], GB["home_score"])
GB["opp"] = np.where(GB["home_team"] == "BAL", GB["away_team"], GB["home_team"])
REC = GB[GB["game_type"] == "REG"].groupby("season").apply(lambda g: (int((g.bal > g.opp_pts).sum()), int((g.bal < g.opp_pts).sum())))
PO = GB[GB["game_type"] != "REG"].set_index("game_id")
BP = POST[POST["posteam"] == "BAL"]
REG_LJ = REG[(REG["posteam"] == "BAL") & ~((REG["season"] == 2018) & (REG["week"] < 11))]


def game_line(x):
    return pd.Series({"epa": x["epa"].mean(), "run_epa": x[x["designed"]]["epa"].mean(),
                      "run_rate": x["designed"].mean(), "to": x["fumble_lost"].sum() + x["interception"].sum(),
                      "trail8": (x["score_differential"] < -7).mean(), "plays": len(x)})


PO_LINES = BP.groupby("game_id").apply(game_line).join(PO[["season", "game_type", "gameday", "opp", "bal", "opp_pts"]])
PO_LINES = PO_LINES.sort_values("gameday")
REG_LINE = game_line(REG_LJ)
REG_GAMES = REG_LJ["game_id"].nunique()
POST_LINE = game_line(BP)
LJ_PO = PO_LINES[PO_LINES.index != "2022_19_BAL_CIN"]
LJ_PO_W = int((LJ_PO["bal"] > LJ_PO["opp_pts"]).sum())
LJ_PO_L = int((LJ_PO["bal"] < LJ_PO["opp_pts"]).sum())
LOSSES = PO_LINES[PO_LINES["bal"] < PO_LINES["opp_pts"]]
# Drill 3: designed runs against light boxes (six or fewer), and runs v dropbacks when trailing by 8+
_light = RP[RP["designed"] & RP["defenders_in_box"].between(3, 6) & (RP["season"] >= 2019)]
LIGHT_BAL = _light[_light["posteam"] == "BAL"]["epa"].mean()
LIGHT_LG = _light["epa"].mean()
_tr = REG_LJ[REG_LJ["score_differential"] < -7]
TR_RUN, TR_DB = _tr[_tr["designed"]]["epa"].mean(), _tr[~_tr["designed"]]["epa"].mean()
TO3_LOSSES = int((LOSSES["to"] >= 3).sum())
# run share by score: within one score (a touchdown either way) and when not trailing
CLOSE_REG = REG_LJ[REG_LJ["score_differential"].between(-7, 7)]["designed"].mean()
CLOSE_POST = BP[BP["score_differential"].between(-7, 7)]["designed"].mean()
# playoff designed runs without the two games in which the run was stuffed (Jan 2019 LAC, Jan 2020 TEN)
_bp2 = BP[~BP["game_id"].isin(["2018_18_LAC_BAL", "2019_19_TEN_BAL"])]
PO_RUN_EX2 = _bp2[_bp2["designed"]]["epa"].mean()
# the league baseline: how much worse does a typical playoff offense play in January? (2018-2024 seasons)
_post = POST[POST["season"] <= 2024].groupby(["season", "posteam"])["epa"].mean()
_reg = REG[REG["season"] <= 2024].groupby(["season", "posteam"])["epa"].mean()
LG_PO_DROP = (_post - _reg.reindex(_post.index)).mean()
# Baltimore's own regular season against teams that made the playoffs that year
_po_teams = {(s_, t) for s_, g in GAMES[GAMES["game_type"] != "REG"].groupby("season")
             for t in set(g["home_team"]) | set(g["away_team"])}
REG_VS_PO = REG_LJ[[(s_, t) in _po_teams for s_, t in zip(REG_LJ["season"], REG_LJ["defteam"])]]["epa"].mean()

# the December 1, 2019 film room (49ers at Ravens)
_sf = RP[(RP["game_id"] == "2019_13_SF_BAL") & (RP["posteam"] == "BAL")]
SF19 = {"pistol": (_sf["offense_formation"] == "PISTOL").mean(), "run_rate": _sf["designed"].mean()}


def one_game(gid):
    """Baltimore's designed-run EPA and Jackson's designed runs in one game."""
    g = ALL[(ALL["game_id"] == gid) & (ALL["posteam"] == "BAL")]
    lj = g[(g["rusher_player_name"] == "L.Jackson") & g["designed"]]
    return {"run_epa": g[g["designed"]]["epa"].mean(), "runs": int(g["designed"].sum()),
            "lj_n": len(lj), "lj_yds": int(lj["yards_gained"].sum()),
            "lj_all_n": int((g["rusher_player_name"] == "L.Jackson").sum()),
            "lj_all_yds": int(g[g["rusher_player_name"] == "L.Jackson"]["yards_gained"].sum())}


MIA21, KC24, DET25R = one_game("2021_10_BAL_MIA"), one_game("2024_01_BAL_KC"), one_game("2025_03_DET_BAL")


# --- formatting helpers for the inline numbers
def sg(x, n=2):
    return f"{x:+.{n}f}"


def pct(x):
    return f"{100 * x:.0f}%"


def ordinal(n):
    n = int(n)
    return f"{n}{'th' if 10 <= n % 100 <= 20 else {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th')}"


def rec(season):
    w, l = REC.loc[season]
    return f"{w}–{l}"

Picture a typical Sunday in the fall of 2019. Baltimore breaks the huddle with two tight ends. Lamar Jackson stands four yards behind the center, with Mark Ingram directly behind him, three yards deeper. The defense has seen the film. It walks a safety down and crowds eight men within a few yards of the ball, the kind of picture a broadcaster calls “selling out to stop the run.” The ball is snapped, Jackson puts it in Ingram’s stomach, and an unblocked defensive end on the edge freezes for half a heartbeat, unsure whether to take the back or the quarterback. Ingram goes for seven. Two plays later, from the same look, the same end crashes on Ingram, and Jackson is past him and gone for twenty. The defense did what every coaching manual says, loaded the box, and lost anyway. How do you build an offense where “eight in the box” is not enough, and why did it beat the NFL for five years and then, in the games that mattered most, too often come up short? This chapter is that story, told as a closed era: the 2019–2025 Ravens, from Greg Roman’s rebuild to John Harbaugh’s firing in January 2026.

You’ll be able to:

NoteYou’ll need

How to read the diagrams. Offense is blue circles, defense orange squares; left and right mean the offense’s left and right. Q is the quarterback and R the running back; Y and U are tight ends (Y on the right, U on the left) and H a third tight end on the wing. LT, LG, C, RG and RT are the linemen. Defensive linemen are labelled by their technique number (9 is the end man outside a tight end, 5 on a tackle’s outside shoulder, 3 on a guard’s, 1 shaded on the center); M and W are the Mike and Will linebackers, S the Sam, and FS and SS the free and strong safeties. Lines ending in a short bar are blocks (a pulling lineman’s path also ends in a bar, where he makes his block), thin arrows are paths without the ball, thick dark arrows are ball carriers, and the dashed green line from the quarterback is his eyes: the defender he is reading. Dashed arrows are an option’s branches. A dotted brown line is the ball in the air on a pass, and a dotted orange line ties a defender to the man he covers in man coverage. Rings: orange marks the defender to watch, purple a blocker to follow, and green follows the ball in the animation. Wide receivers who do not matter to the play are left out of the box-sized pictures; they stand off the line of scrimmage, so the inline tight ends are the ends of the line and eligible to catch a pass. Every picture is a typical version of a play, not a copy of a particular Ravens snap.

The problem: a quarterback who didn’t fit (2018)

Lamar Jackson arrived in Baltimore with the last pick of the first round of the 2018 draft, 32nd overall, after a Heisman Trophy at Louisville in 2016 and a college career in which he ran for more than 4,000 yards.1 He also arrived with a question hanging over him that a lot of NFL people asked out loud: could a quarterback who ran like that be a real NFL passer, or would he have to move to another position? The loudest version came from the Hall of Fame executive Bill Polian, who said that February that Jackson should switch to wide receiver (and admitted in 2019 that he had been wrong).2 The Ravens had a veteran starter, Joe Flacco, a Super Bowl MVP five years earlier, and an offense built for a pocket passer.

Then, in November 2018, Flacco hurt his hip, and on November 18 against Cincinnati Jackson made his first NFL start.3 What followed was improvised. The coaching staff, with Marty Mornhinweg as offensive coordinator, didn’t have a Jackson offense, so it bolted his college game onto Flacco’s: lots of quarterback runs, read plays, extra tight ends, and as few dropbacks as it could get away with. The data shows how sudden the swerve was. With Flacco starting, weeks 1 through 10, the Ravens called a designed run on 34% of their plays; with Jackson starting, weeks 11 through 17, it was 59%. The runs also got better. Measured in EPA per play (expected points added: how much a play raised or lowered the number of points the offense could expect to score on the drive, the yardstick 01-02 introduced), they went from -0.02, slightly worse than an average snap, to +0.08.4 Jackson ran 147 times that season, a record for a quarterback at the time, and Baltimore won six of his seven starts to take the AFC North.

The improvisation had a ceiling, and the wild-card game on January 6, 2019 found it. The Los Angeles Chargers had played Baltimore two weeks earlier, and for the rematch their defensive coordinator, Gus Bradley, did something almost nobody did: he put seven defensive backs on the field for 58 of the Ravens’ 59 snaps, with two safeties playing linebacker. The Chargers had used seven defensive backs for a total of 50 snaps all season. Their head coach, Anthony Lynn, said later that when Bradley proposed it his first reaction was to ask whether the staff was insane. It wasn’t. The Ravens gained 90 rushing yards and 229 in total, the fewest the Chargers had ever allowed in a playoff game, Jackson was sacked seven times, and Los Angeles won 23–17 after leading 23–3 in the fourth quarter.5 (Figure 63.7, later in the chapter, draws the idea.)

The lesson the Ravens took from that afternoon was not “stop running the quarterback.” It was that a run game added on to an offense is a run game a defense can study, match and outnumber in a week. If Jackson’s legs were going to be the foundation, the whole offense had to be built on them, including the passing game, so that the defense’s best answer to the run would open something else. That is a design problem, and the man the Ravens handed it to had been working on exactly that problem for most of a decade.

The scheme answer: Greg Roman’s 2019 rebuild

In January 2019 the Ravens promoted Greg Roman, their assistant head coach and tight ends coach, to offensive coordinator in Mornhinweg’s place.6 Roman had run the 49ers’ offense from 2011 to 2014, when Colin Kaepernick’s pistol read game took San Francisco to a Super Bowl, and then Buffalo’s with another running quarterback, Tyrod Taylor: the Bills led the NFL in rushing yards in 2015, and kept leading it in 2016 with the run game he had installed, although he was fired two games into that season.7 He was, in other words, the league’s most experienced designer of run games with a quarterback in them.

His first public description of the plan was blunt. The staff would not try to fit Jackson into something that “other people had once done”; it would “rebuild the thing from the ground up” and build an offense “that really accommodates” him.8 What came out of that rebuild had four pieces, and each one answers a specific question the Chargers had asked.

1. Heavy personnel: more blockers, and slower defenders. The Chargers’ answer had been speed: little defenders who could run with Jackson. Roman’s first move was to make little defenders expensive with heavy personnel. The 2019 Ravens put fewer than three wide receivers on the field on 53% of their snaps, against 37% for the league, using tight ends Mark Andrews, Hayden Hurst and Nick Boyle and the fullback (and part-time defensive lineman) Patrick Ricard (02-01 has the personnel fingerprint). Every tight end or fullback is one more blocker on a run, which forces the defense to answer with bigger bodies. Against Baltimore in 2019, defenses were in a base defense (four defensive backs, seven bigger front players) on 40% of snaps; against the rest of the league they were in base on 27%.9 A linebacker is a better run defender than a safety, but he is a worse chaser of a sprinter-fast quarterback in space, and a much worse cover man against a tight end like Andrews.

2. The pistol: no tell, and both ends in play. The pistol (02-02) puts the quarterback about four yards from the ball with the back directly behind him. The Ravens made it their home. In 2019 they lined up in it on 54% of their plays, 558 snaps; the rest of the league combined used it on 3% of plays, and the next-heaviest user had 68 snaps all season.10 75% of Baltimore’s designed runs started there. Why the pistol rather than the shotgun? Three reasons, each one a fix for something the shotgun gives away:

  • No directional tell. In the shotgun the back stands beside the quarterback, and which side he is on usually hints at where the run is going. From the pistol the back can go either way with the same first step, so the defense can’t lean before the snap.
  • A downhill back. A back seven yards deep and straight behind the ball runs at the line with momentum, like a back in the I-formation, instead of starting sideways from the quarterback’s hip. That makes gap runs (power, counter, duo) work from the same look as reads.
  • Both ends are read keys. With the quarterback at four yards and the back behind him, the mesh can go to either side, so either defensive end can be the read key on any snap. A defense that wants to tell its backside end to “always crash” has to tell both ends, and an end can’t know before the snap which one he is.

3. A menu of reads. On top of the pistol, Roman ran the whole option family that 03-04 teaches, and added a few of his own: the zone read and bash, the inverted veer (power read), the counter read (two linemen pull and the backside end is read), counter bash (the back goes away from the pullers, the quarterback follows them), and the occasional speed option.11 The point of having so many is not variety for its own sake. Each one leaves an end alone but sends the runners to different places, so a defense that solves one read with a stunt or an exchange (03-04) is wrong against the next.

4. Designed quarterback runs: the back becomes a blocker. And sometimes there was no read at all. Jackson carried on purpose, on QB power, QB counter, QB draws and sweeps, and on those plays the running back or the fullback became one more lead blocker. In 2019 Jackson had 113 designed runs, about 7.5 a game, averaging 7.4 yards and +0.32 EPA per carry.12

None of the four pieces works without the people who block for it, and the 2019 Ravens had them. Left tackle Ronnie Stanley and right guard Marshal Yanda (his eighth Pro Bowl) were Pro Bowl starters that season, right tackle Orlando Brown Jr. was an alternate, and Patrick Ricard went to the Pro Bowl as a fullback for his lead blocking.13 A scheme that asks the line to block everybody but one defender needs linemen who win those blocks.

Put those together and you have what this book calls a pistol option offense: an offense that makes the pistol its base alignment and builds its run game on the quarterback as a runner, with option reads and designed quarterback runs behind extra tight ends and fullbacks, so that the defense must account for one more ball carrier on every snap. Chris Ault built the college version at Nevada, where the pistol was born in 2005 and Kaepernick learned to run it (03-04 and The College Laboratory tell that story). The 2019–2022 Ravens are the NFL’s defining example.

ImportantWhy it exists

A pistol option offense exists because a quarterback who is a real running threat is worth one blocker on every run, and the pistol lets an offense collect that blocker from either side, behind a downhill back, without showing which way it is going. The heavy personnel makes the defense match with bigger, slower players, and then the passing game punishes the bigger, slower players. The trade is the quarterback’s body (he is a ball carrier, or a target, on most snaps), a passing game built from fewer receivers, and a style that works best when the offense is ahead or level, because the run is its engine.

The rebuild worked at once. The 2019 Ravens went 14–2, led the league in scoring with 531 points, set the NFL record for team rushing yards with 3,296, and Jackson ran for 1,206 of them, a quarterback record, while also leading the league with 36 touchdown passes on his way to a unanimous MVP award.14 By the measure this chapter uses, Baltimore’s designed runs averaged +0.11 EPA per play that season, 1st in the league, while the league as a whole averaged -0.07. And the passing game the run was meant to open up did open up: on dropbacks (passes, sacks and scrambles) the 2019 Ravens averaged +0.32 EPA per play, also 1st in the league, against a league average of +0.03.15 The rest of this chapter explains why.

Why the quarterback changes every run fit

Run Fits gave the rule in one line: count the quarterback as a blocker when he reads. It is worth slowing that down, because the Ravens’ whole era is an argument about what the rule really costs a defense, and the cost is larger than one man.

Start with a defense’s ordinary plan against a run. Every gap has exactly one owner, and the defense tries to have one more run defender than the offense has blockers: the plus-one. Against a quarterback who hands off and watches, the plus-one is easy to find. The offense blocks as many defenders as it has blockers; the one left over is usually the end on the far side of the play, and he can do the most useful thing a free defender can do, chase the play down the line from behind and tackle the back if he cuts back.

Show the Python
fig, axes = plt.subplots(1, 2, figsize=(6.7, 3.9))
check_legal(pistol())
for ax, lamar in zip(axes, (False, True)):
    p = Play(pistol(), front62(), los=35, to_go=None)
    if lamar:
        go_to(p, "BE", (0.6, -6.9), "path", speed=1.0)                 # stays home for the quarterback
    else:
        go_to(p, "BE", (2.2, -1.9), "path", via=[(1.7, -4.9)])         # free: chases flat down the line
        go_to(p, "QB", (-4.4, -0.6), "path", speed=1.0)                # hands off and watches
    go_to(p, "Y", (0.4, 5.8), via=[(-0.4, 5.3)])                       # reach the play-side 9
    go_to(p, "RT", (0.4, 3.8), via=[(-0.4, 3.6)])                      # on the 5
    go_to(p, "RG", (0.4, 2.6), via=[(-0.4, 2.0)])                      # on the 3
    go_to(p, "C", (3.5, 1.6), via=[(-0.3, 0.6), (0.8, 1.0)])           # climbs to the Mike
    go_to(p, "LG", (0.4, -0.5), via=[(-0.4, -1.0)])                    # on the 1
    go_to(p, "LT", (0.4, -3.6), via=[(-0.4, -3.0)])                    # on the backside 5
    go_to(p, "U", (3.5, -2.0), via=[(-0.2, -4.3), (1.2, -3.2)])        # climbs to the Will
    p.highlight("BE", color=style.DEFENSE)
    f = draw(p, ax=ax, lateral=(-10.0, 10.0), window=(-8.6, 8.8), title=False)
    box_shade(f, p, 0.2, 5.6, -7.6, 7.6)
    if not lamar:
        # the back's cutback is drawn above the line markers so it stays visible as it crosses them
        branch(f, p, [(-7.0, 0.0), (-5.3, 1.3), (-2.8, 2.5), (-1.5, 1.7), (1.9, -0.8)], color=style.OFFENSE_DARK,
               zorder=9.5)
        tackle_x(f, p, 2.4, -1.2)
        say(f, p, -2.6, 4.7, "cuts back", color=style.OFFENSE_DARK, fontsize=6.4)
        point_to(f, p, (3.6, -7.4), (1.9, -4.4), "free: chases\nthe cutback", color=style.DEFENSE_DARK, fontsize=6.5)
        say(f, p, -6.6, -5.6, "Q hands off\nand watches", fontsize=6.4)
        say(f, p, 6.5, 0.0, "7 blockers v 8 in the box", color=GREEN, fontsize=6.8, weight="bold")
        ax.set_title("A quarterback who hands off", fontsize=9, loc="left", color=style.INK, fontweight="bold")
    else:
        read_line(f, p, (-4.0, 0.0), (1.0, -6.3))
        branch(f, p, [(-7.0, 0.0), (-5.3, 1.3), (-2.4, 2.1), (0.4, 0.6), (4.6, -0.6)], color=style.OFFENSE_DARK)
        branch(f, p, [(-4.0, 0.0), (-3.7, -2.4), (-1.8, -6.0), (0.4, -7.9), (2.8, -8.3)], color=GREEN)
        say(f, p, -3.7, -6.7, "keep, if\nhe chases", color=GREEN, fontsize=6.3, weight="bold")
        point_to(f, p, (4.9, -6.4), (1.2, -6.6), "must stay\nfor the QB", color=style.DEFENSE_DARK, fontsize=6.5)
        say(f, p, -6.6, -5.6, "Q reads the 9", color=GREEN, fontsize=6.4)
        say(f, p, 6.5, 0.0, "7 blockers + a read v 8", color=GREEN, fontsize=6.8, weight="bold")
        ax.set_title("Jackson at quarterback", fontsize=9, loc="left", color=style.INK, fontweight="bold")
fig.tight_layout(w_pad=0.6)
plt.show()
Figure 63.1: One front, two quarterbacks. The same pistol 12-personnel look (two tight ends) against the same eight-man box (shaded), and the same inside-zone run to the right. Left: with a quarterback who only hands off, seven blockers take seven defenders and the backside end (ringed) is the defense’s free man (some staffs would instead block him and leave the backside linebacker free; either way one defender is spare); he chases flat down the line and tackles the back when he cuts back. Right: with Jackson at quarterback, that end is the read key. The green branch is what happens if he chases: Jackson keeps the ball and runs where the end was. So he has to stay home and play the quarterback, and the cutback lane he would have closed stays open. Seven blockers plus a read against eight: a hat on every hat, and the defense’s only spare man is a safety 12 yards away.

Now put Jackson at quarterback (Figure 63.1, right). Nothing about the blocking changes, but the free man isn’t free any more. If the backside end chases the back, Jackson keeps the ball and runs into the space the end just left. So the end has to stay home and play the quarterback, and the moment he does, the defense has lost its plus-one: seven blockers and a read against eight defenders is a hat on every hat. To get the plus-one back, the defense has to bring a ninth defender down, and in Figure 63.1 the only one left is the free safety, twelve yards deep and the last man in the deep middle.

That is the arithmetic. The Ravens’ insight was that the arithmetic doesn’t only apply on read plays. It applies on every run, for three reasons that compound.

The threat works on plays that aren’t reads. Most of the Ravens’ runs were simple handoffs. But a backside end can’t see the play call. On every pistol handoff he sees the same mesh he sees on a read, and if he chases the back on the one snap in five that was a read, Jackson is gone. So he learns to stay home every time, and on the four plain handoffs the offense gets for free what it earned on the read: an end who isn’t chasing the cutback. Coaches call this “freezing” the backside, and it is why a quarterback’s run threat improves his running backs’ numbers even when he never touches the ball.

Every defender’s fit gets one more line. A run fit (05-05) is a list of who owns which gap. Against a running quarterback, the defense needs one more owner on that list, quarterback, on every play, and the person who owns him can’t own anything else. If it’s the end, he can’t squeeze; if it’s a linebacker, his gap needs a new owner; if it’s a safety, the deep middle does. There is no free version.

Pursuit stops being free. Even after the ball is handed off, a defense normally gets a bonus: everybody on the far side of the play turns and runs to the ball. That backside pursuit cleans up mistakes. Against Jackson, backside defenders who turn and run before they’re sure the quarterback doesn’t have it get punished by the keep, so they turn a step later, and a step later is the difference between a four-yard run and a twelve-yard one.

CautionCommon misconception

“Loading the box stops a running quarterback.” Loading the box is how a defense gets its plus-one against a handoff. Against a quarterback who reads, an eight-man box is a seven-man box, because one of the eight is assigned to the quarterback. In fact, the 2019 Ravens saw loaded boxes about as often as everyone else did (eight or more defenders on 22% of their designed runs, against 21% for the league), and still led the league in rushing efficiency. What defenses did differently against Baltimore was get bigger, not more numerous: base personnel on 40% of snaps against 26% against everyone else, a gap that held through Roman’s four seasons. The boxes got heavier later: in 2021 and 2022 Baltimore saw eight or more on 30% and 33% of its designed runs, against about 23% for the league, and its runs still ranked 13th and 2nd.16 The extra defender has to come from somewhere, and when he comes from the deep secondary, the passing game is aimed at the hole he leaves.

Signature play 1: the pistol inverted veer

The play that best shows Roman’s design is the inverted veer, the power read, run from the pistol with two tight ends. 03-04 drew it from the shotgun with a slot receiver; the Ravens’ version shows what the pistol and the extra tight ends add.

Show the Python
p = Play(pistol(), front62(), los=35, to_go=None)
go_to(p, "Y", (0.4, 4.1), via=[(-0.3, 4.5)])                               # down on the 5
go_to(p, "RT", (0.5, 2.7), via=[(-0.4, 3.0)])                              # down on the 3
go_to(p, "RG", (3.7, -1.6), via=[(-0.2, 1.9), (0.4, 2.0), (1.4, 0.8)])     # double the 3, climb to the Will
go_to(p, "C", (0.4, -0.5))                                                 # back on the 1
go_to(p, "LT", (0.3, -3.8), via=[(-0.6, -3.5)])                            # hinge on the backside 5
go_to(p, "U", (0.4, -6.0), via=[(-0.4, -5.3)])                             # wall off the backside 9
go_to(p, "LG", (3.4, 3.3), "pull", via=[(-2.2, -1.3), (-2.7, 0.9), (-1.9, 3.4), (-0.3, 5.0), (1.6, 4.6)])
p.highlight("PE", color=style.DEFENSE)
p.highlight("LG", color=PURPLE)
f = draw(p, lateral=(-9.0, 14.5), window=(-8.6, 9.0), title=False, width_in=5.8)
box_shade(f, p, 0.2, 5.8, -7.6, 7.6)
read_line(f, p, (-4.0, 0.3), (0.6, 6.0))
branch(f, p, [(-7.0, 0.0), (-5.0, 0.6), (-4.4, 1.4), (-3.7, 4.2), (-2.7, 8.4), (-0.6, 10.7), (5.4, 11.5)],
       color=style.OFFENSE_DARK, label="give if the 9\nsqueezes", label_at=(-5.9, 10.4), fontsize=6.8)
branch(f, p, [(-4.0, 0.0), (-3.7, 1.6), (-3.0, 3.5), (-1.5, 4.9), (0.3, 5.7), (6.0, 5.7)], color=GREEN,
       label="keep if the 9\nwidens", label_at=(7.0, 2.6), fontsize=6.8)
point_to(f, p, (-2.6, -6.6), (-2.3, -1.0), "LG pulls\nfor the Mike", color=PURPLE, fontsize=6.6)
point_to(f, p, (-6.6, -4.4), (-6.9, -0.5), "R: behind Q,\nno tell", fontsize=6.4)
say(f, p, 7.2, -4.6, "8 in the box v\n7 blockers + a read", color=GREEN, fontsize=6.8, weight="bold")
f.ax.set_title("Pistol inverted veer (power read), 12 personnel", fontsize=10, loc="left", color=style.INK,
               fontweight="bold")
plt.show()
Figure 63.2: The pistol inverted veer (power read) to the right, from 12 personnel against an eight-man box. The line blocks power: the right tight end (Y) blocks down on the 5, the right tackle and right guard double-team the 3 before the guard climbs to the Will, the center blocks back on the 1, and the left guard pulls across for the Mike. On the backside, the left tackle and the other tight end (U) wall off the 5 and the 9. The play-side 9 (ringed), the end man on the line outside the tight end, is the one defender not blocked: Jackson reads him. The back, starting directly behind the quarterback, comes around his right hip for the mesh and sweeps wide. If the 9 widens with the back, Jackson keeps and runs inside him, a step behind the pulling guard (green); if the 9 squeezes inside, Jackson gives and the back runs outside him (dark), where the play-side wide receiver (out of the picture) blocks the cornerback. Count the box: seven blockers and a read against eight. The free safety, the only defender left, is 12 yards deep (out of the picture).

Walk through what Figure 63.2 asks of the defense.

Before the snap, the defense can’t lean. In the shotgun, a back offset to the left usually means a run to the right; from the pistol, the back is straight behind the quarterback and gives nothing away. The defense has to stay balanced: a 9 on each side, a 5 on each side, linebackers over the middle.

At the snap, the line tells a story of power. The tight end and tackle block down, a guard pulls across. A linebacker who reads the guards has been taught to follow a pulling guard, because on power the puller leads the ball carrier through the hole. Here he does lead a ball carrier through the hole, but which one?

The end man has a bad choice. The play-side 9 is the man the power play would normally kick out, or log (seal inside from the outside so the runner can bounce around him). On this play nobody touches him. If he does what he’s taught against power, squeeze the hole from the outside so the pulling guard has nowhere to go, he has stepped inside the back, who is running outside him at full speed. Out there the play-side wide receiver blocks the cornerback, and the back’s problem is the alley player, usually a safety coming down from depth. If he widens to take the back, he has opened the hole for the quarterback, who follows the guard through it. Either way the defense’s best edge player is blocked by a decision.

Why build it from the pistol with tight ends instead of the shotgun with receivers, as most college teams did? The tight ends do two jobs a receiver can’t. The play-side tight end’s down block on the 5 creates the hole’s inside wall from a tighter angle, so the quarterback’s keep lane is a real gap, not a crease. And the backside tight end blocks a man a receiver couldn’t: the backside 9, the defender who on a shotgun power read is left to chase the quarterback from behind. With him walled off, the eight-man box is fully accounted for, seven blocks and a read, which is the arithmetic of Figure 63.1 made into a play.

What does it cost? Two things. The back is running sideways on the give, so a wrong read (a give when the 9 was actually widening) loses yards. And the keep sends Jackson between the tackles, where the linebackers and the safety arrive, on every snap that the 9 widens. That second cost is real enough that the Ravens ran the play in measured doses, which brings us to the formation that made the doses unpredictable.

Signature play 2: the 13-personnel sets

The most extreme version of Roman’s heavy offense put three tight ends on the field: 13 personnel, one back, three tight ends and one wide receiver. In 2019 it was a small slice of the Ravens’ offense, a few dozen regular-season snaps, but it is the clearest picture of the bind the whole offense put defenses in, and it hides a surprise.

Show the Python
def plate():
    off = pistol(extra=(O("H", "TE", -1.6, 6.6, "H"),))
    check_legal(off)
    dfn = [D("LE", "DE", 1.0, -4.0, "5"), D("NT", "DT", 1.0, -0.7, "1"), D("DT3", "DT", 1.0, 2.4, "3"),
           D("RE", "DE", 1.0, 5.7, "9"), D("SAM", "SAM", 4.0, 8.2, "S"), D("MIKE", "MIKE", 4.5, 1.6, "M"),
           D("WILL", "WILL", 4.5, -2.4, "W"), D("SS", "SS", 5.5, -6.8, "SS"), D("FS", "FS", 12.8, 0.0, "FS")]
    return Play(off, dfn, los=35, to_go=None)


fig, axes = plt.subplots(1, 2, figsize=(6.7, 4.7))
# left: play-action seam
p = plate()
go_to(p, "RB", (-5.2, -2.6), "path", via=[(-5.6, -0.8)])          # run fake to the left
p.route("Y", [(5.0, 0.4), (13.0, 0.9)])
p.man("MIKE", "Y")
go_to(p, "FS", (12.6, 3.8), "path", speed=2.0, delay=0.5)
for k, end in [("LT", (-0.6, -3.4)), ("LG", (-0.6, -1.5)), ("C", (-0.7, 0.0)), ("RG", (-0.6, 1.6)),
               ("RT", (-0.7, 3.3)), ("U", (-0.8, -4.9)), ("H", (-0.9, 6.0))]:
    go_to(p, k, end)
go_to(p, "QB", (-5.6, 2.2), "path", speed=3.0, via=[(-4.5, -0.9)])
p.pass_to("Y", t=2.2)
p.highlight("Y", color=PURPLE)
f = draw(p, ax=axes[0], lateral=(-10.0, 11.0), window=(-8.6, 15.6), title=False)
box_shade(f, p, 0.2, 6.2, -8.4, 9.4)
point_to(f, p, (8.0, -5.6), (7.0, 5.4), "Y on a\nlinebacker", color=PURPLE, fontsize=6.6)
say(f, p, -6.4, -6.4, "run fake", fontsize=6.4)
say(f, p, 13.2, -6.0, "one safety deep", color=style.DEFENSE_DARK, fontsize=6.6)
axes[0].set_title("The pass: the seam", fontsize=9, loc="left", color=style.INK, fontweight="bold")
# right: the run, eight blockers and a read
p = plate()
go_to(p, "Y", (0.5, 6.1), via=[(-0.3, 5.4)])                      # reach the 9
go_to(p, "H", (3.6, 7.9), via=[(0.2, 7.2)])                       # lead up on the Sam
go_to(p, "RT", (3.4, 2.4), via=[(-0.3, 3.4), (0.8, 3.0)])         # combo, climbs to the Mike
go_to(p, "RG", (0.5, 2.8), via=[(-0.3, 2.0)])                     # on the 3
go_to(p, "C", (0.4, -0.4), via=[(-0.3, 0.2)])                     # on the 1
go_to(p, "LG", (0.3, -0.9), via=[(-0.3, -1.3)])                   # helps on the 1
go_to(p, "LT", (3.6, -2.0), via=[(-0.3, -2.6), (0.8, -2.6)])      # climbs to the Will
go_to(p, "U", (4.8, -6.6), via=[(-0.2, -5.6), (1.6, -7.0)])       # arcs past the read end to the safety
p.highlight("LE", color=style.DEFENSE)
f = draw(p, ax=axes[1], lateral=(-10.0, 11.0), window=(-8.6, 15.6), title=False)
box_shade(f, p, 0.2, 6.2, -8.4, 9.4)
read_line(f, p, (-4.0, 0.0), (1.0, -4.0))
branch(f, p, [(-7.0, 0.0), (-5.3, 1.3), (-1.6, 2.1), (4.6, 1.6)], color=style.OFFENSE_DARK)
branch(f, p, [(-4.0, 0.0), (-3.6, -2.6), (-1.6, -5.0), (0.6, -5.4), (4.4, -4.6)], color=GREEN)
say(f, p, 9.2, 1.0, "8 blockers + a read\nv 8 in the box", color=GREEN, fontsize=6.7, weight="bold")
point_to(f, p, (-6.4, -6.2), (-0.5, -5.6), "U arcs\nto the SS", fontsize=6.4)
axes[1].set_title("The run: one man short", fontsize=9, loc="left", color=style.INK, fontweight="bold")
fig.tight_layout(w_pad=0.5)
plt.show()
Figure 63.3: The 13-personnel bind, as the 2019 Ravens used it (illustrative; in 2019 the three tight ends were typically Mark Andrews, Nick Boyle and Hayden Hurst, with Mark Ingram at running back and the lone wide receiver off the line as a flanker, out of the picture). Both panels show the same pistol formation, with the U and Y tight ends on the line and H on the wing, against the defense’s usual answer: a base 4-3, eight in the box (shaded) and one deep safety. The Sam already sits over the wing on the strong side, so the strong safety walks down on the other side, outside U, to give each side the same number of defenders. Left, the pass: after a run fake (the ball’s flight is the dotted brown line from the quarterback), the Y runs up the seam against a linebacker in man coverage (dotted orange). The only man behind him is the free safety, who has to hold the middle against the fake and arrives late. Right, the run: seven linemen and tight ends plus the wing make eight blockers for eight defenders before the quarterback reads anyone. Jackson reads the backside 5 (ringed) and the U arcs past him to the safety, so the defense is a man short until the free safety comes down, and the free safety is the man the pass in the left panel is aimed at.

Read Figure 63.3 as the defense’s coordinator, on the sideline, deciding what to put on the field when three tight ends run on.

If he plays it as a run formation, with base personnel and eight in the box (defenses chose base on about 77% of Baltimore’s 57 such snaps in 2019, a small sample, so read every 13-personnel number here as a tendency), he is still a man short against the run (right panel): eight blockers plus Jackson’s read against eight. To get even he needs the free safety in the box, and then nobody is deep. Meanwhile his linebackers are covering tight ends. Mark Andrews, who led the 2019 Ravens in catches, was a 6-foot-5 receiver in a tight end’s job description; a linebacker in man coverage on him up the seam (left panel) is the mismatch the formation was built to create.

If he plays it as a pass formation, with smaller players to cover the tight ends, the run arithmetic gets worse: smaller defenders to block, and seven of them in the box against eight blockers and a read.

And here is the surprise. The league ran the ball on about 58% of its 13-personnel snaps in 2019, as you’d expect from a goal-line-looking grouping. Baltimore threw on 67% of them.17 Three tight ends didn’t mean “run” for the Ravens any more than three wide receivers meant “pass”: that season they ran on 49% of their 11-personnel snaps (three receivers), against 29% for the league. The personnel stopped being a tell. That is the deepest point of Roman’s design: once the quarterback is a runner, every grouping can run, so every grouping can also throw, and a defense that reads personnel to guess run or pass, as 02-01 teaches you to, guesses wrong more often against Baltimore than against anyone.

Signature play 3: the QB counter

The third signature play has no read at all. On the QB counter, Jackson is the ball carrier from the snap, the running back is a decoy and then a blocker, and two blockers pull across the formation to lead him. 03-04 drew a shotgun version with a guard and tackle pulling. The Ravens’ favourite version used what they had most of: a tight end as the second puller.

Show the Python
def qb_counter():
    p = Play(pistol(), front62(), los=35, to_go=None)
    # the defense: linebackers take a real false step with the fake, then react; the play-side 9 squeezes
    go_to(p, "PE", (1.2, 6.9), "path", speed=1.0, delay=0.2)        # fights outside the kick-out
    go_to(p, "P5", (1.4, 3.3), "path", speed=0.7, delay=0.1)
    go_to(p, "DT3", (1.4, 2.0), "path", speed=0.7, delay=0.1)
    go_to(p, "DT1", (1.2, -0.3), "path", speed=0.5)
    go_to(p, "B5", (1.0, -4.0), "path", speed=0.6)
    go_to(p, "BE", (-0.6, -6.0), "path", speed=2.4, delay=0.1)
    go_to(p, "WILL", (4.0, -3.8), "path", speed=3.0, delay=0.1)                  # false step with the back
    go_to(p, "MIKE", (4.2, 0.0), "path", speed=3.0, delay=0.1)                   # false step...
    go_to(p, "MIKE", (4.6, 2.8), "path", speed=2.2, delay=0.25)                  # ...then back to the play
    # the offense (GY counter: down blocks on the play side, C back, the uncovered RG to the backside LB)
    go_to(p, "Y", (0.3, 3.9), via=[(-0.3, 4.4)], speed=1.6)                       # down on the 5
    go_to(p, "RT", (0.2, 2.6), via=[(-0.3, 2.9)], speed=1.6)                      # down on the 3
    go_to(p, "C", (0.1, -0.9), speed=1.4)                                         # back on the 1
    go_to(p, "RG", (3.0, -2.9), via=[(-0.2, 1.0), (1.0, 0.0)], speed=2.6)        # climbs to the Will
    go_to(p, "LT", (-0.1, -3.4), speed=1.0)                                       # on the backside 5
    go_to(p, "LG", (0.0, 6.4), "pull", via=[(-1.8, -1.1), (-2.1, 2.4), (-0.8, 5.2)], speed=5.2)   # kick out the 9
    go_to(p, "U", (3.4, 3.2), "pull", via=[(-1.6, -4.0), (-2.4, 1.0), (-1.2, 4.8), (0.8, 5.0)], speed=6.4)  # lead up
    go_to(p, "RB", (-6.4, -1.9), "path", speed=3.0)                              # the counter step
    go_to(p, "RB", (-1.6, -5.2), speed=4.6)                                      # then block the backside 9
    go_to(p, "QB", (-4.4, -1.3), "path", speed=2.4)                              # step left with the back
    go_to(p, "QB", (5.8, 4.9), "run", speed=5.6, delay=0.35,
          via=[(-3.8, 0.8), (-2.2, 3.0), (-0.8, 4.8), (1.0, 5.1)])
    return p


play_animation(qb_counter(), [0.0, 0.6, 1.9, 2.7], lateral=(-9.5, 9.5), window=(-8.2, 7.8),
               notes=["Eight in the box; the back\nis right behind the QB",
                      "QB and back step left;\nthe LBs step with them",
                      "The guard kicks out the 9; the\ntight end leads up the hole",
                      "QB follows the tight end;\nthe back blocks the backside 9"],
               rings=(("LG", PURPLE), ("U", PURPLE)), ncols=2)
Figure 63.4: QB counter from the pistol, 12 personnel, to the right (simulated, in Big Data Bowl coordinates; purple rings mark the two pullers, the green ring the ball, faint lines where each player has been). 1, the snap: eight defenders in the box, the back directly behind the quarterback. 2, Jackson and the back take a step to the left, selling a run that way; the two linebackers take a false step with them (the Mike’s faint trail points left), while the left guard and the backside tight end (U) start pulling right behind the line. 3, the guard kicks out the play-side 9 outside the Y’s down block; the tight end turns up through the hole the down blocks have made, leading for the quarterback. 4, Jackson follows a step behind the tight end into the second level, where the Mike, who first stepped the wrong way, has to take on a blocker; the right guard has climbed to the Will, and the back has turned around to block the backside 9. Eight blockers for eight defenders, with the quarterback carrying: the running back’s block is the hat a handoff can’t provide.

Three things make this play the Ravens’ and not just anyone’s.

The fake beats both kinds of linebacker. Linebackers are taught to read one of two things at the snap (their key), and the counter beats each one differently. A linebacker who keys the backfield reads the running back: if he steps left, the run is going left. Against Jackson he also has to wonder whether the quarterback is about to keep the ball and run left himself, so his false step left is bigger, and the cutback right, which is where the counter goes, is bigger too. A linebacker who keys the guards is not fooled about the direction: he sees the guard and the tight end pull right, and the pullers tell the truth. What beats him is the half-second of doubt before he trusts them. The mesh looks like every read play he has seen, and if he flies after the pullers and Jackson hands the ball off going left, he has run himself out of the play. So he hesitates for a beat, and a beat is all the tight end needs to arrive at him with a head of steam.

The pullers are a tight end and a guard, not two linemen. Many counter plays pull the backside guard and tackle. Pulling the tackle leaves a second hole in the backside of the line, where he used to stand, that someone has to step back and fill so a defender can’t run through it into the backfield. Pulling the backside tight end instead keeps the tackle home on the 5, and the tight end is usually the better athlete for the second puller’s job: turning up through the hole and finding a linebacker in space. The running back, after his fake, turns around and blocks the backside 9 (Figure 63.4, panel 4), the job he would have on any counter. Count the blocks: eight, against eight. That is the designed-run arithmetic of 03-04 with the Ravens’ extra tight end supplying the eighth.

It punishes the defense for following the rules. The linebackers who follow the first flow are doing what they’re coached to do. The 9 who squeezes the down blocks is doing what he’s coached to do. Both are wrong, because the play is built to make their rules point the wrong way. That is the pattern of the whole offense: not a trick, but a set of plays where each defender’s textbook reaction is the one the next play attacks.

The price is the one the “Why it exists” box named: Jackson is a ball carrier between the tackles, so the Ravens rationed these runs. How much they rationed them, and how that changed, is in the data.

NoteFilm room: San Francisco at Baltimore, December 1, 2019 (watching the pistol)

Earlier chapters showed these Ravens’ personnel, early downs, reads and boxes; this time, watch the pistol itself, in the game that shows it best. The 10–1 49ers, the eventual NFC champions, came to Baltimore on a rainy 40-degree afternoon. The Ravens lined up in the pistol on 71% of their snaps, their highest share of the season, and called a designed run on 61% of their plays; Jackson ran 16 times for 101 yards, every carry a designed run. With 4:39 left and the score tied, he converted a fourth-and-1 at his own 44 on a run behind the right guard, and Justin Tucker won it 20–17 with a 49-yard field goal as time expired.18

What to notice: before each snap, note where the back is. Behind Jackson (pistol) is the base look, and in this game it was seven snaps in ten. Then, at the snap, watch the two ends rather than the ball. One of them is being read and the other is being blocked, and you can usually tell which within a step: the read end is the one nobody touches. Over a quarter, notice how rarely either end chases the back flat down the line. That restraint, the frozen backside, is the hidden yardage of the whole offense: it is there on the plain handoffs too.

The data fingerprint

How good was the Ravens’ run game, how long did it last, and how did it change? Play-by-play data can’t see a read, but it can see the results. Figure 63.5 compares Baltimore’s designed runs with every other team’s, season by season, using EPA per play.

Show the Python
style.apply()
fig, ax = plt.subplots(figsize=(6.6, 3.5))
ax.axvspan(2018.5, 2022.5, color=style.SERIES[0], alpha=0.06, lw=0)
ax.axvspan(2022.5, 2025.5, color=style.SERIES[1], alpha=0.06, lw=0)
for s in SEASONS:
    v = TEAM_RUN[(TEAM_RUN["season"] == s) & (TEAM_RUN["posteam"] != "BAL")]["epa"]
    ax.scatter(np.full(len(v), s) + np.random.default_rng(s).uniform(-0.14, 0.14, len(v)), v, s=9,
               color=style.MUTED, alpha=0.45, lw=0, zorder=2)
ax.plot(LG_RUN.index, LG_RUN.values, color=style.INK_2, lw=1.4, zorder=3)
ax.plot(BAL_RUN.index, BAL_RUN["epa"], color=style.SERIES[0], lw=2.2, marker="o", ms=5, zorder=4)
for s, r in BAL_RUN.iterrows():
    off2021 = s == 2021                                   # 2021 sits among the gray dots: label it to the right
    ax.annotate(ordinal(r["rank"]), (s, r["epa"]), xytext=(7, 0) if off2021 else (0, 7), textcoords="offset points",
                ha="left" if off2021 else "center", va="center" if off2021 else "bottom",
                fontsize=7.5, color=style.SERIES[0], fontweight="bold",
                bbox=dict(boxstyle="round,pad=0.1", fc="white", ec="none", alpha=0.85) if off2021 else None)
ax.annotate("Baltimore", (2025, BAL_RUN.loc[2025, "epa"]), xytext=(10, -2), textcoords="offset points",
            fontsize=8, color=style.SERIES[0], va="center")
ax.annotate("league average", (2025, LG_RUN.loc[2025]), xytext=(10, -2), textcoords="offset points",
            fontsize=8, color=style.INK_2, va="center")
ax.text(2020.5, 0.235, "Roman OC, 2019–2022", ha="center", fontsize=7.5, color=style.INK_2)
ax.text(2024.0, 0.235, "Monken OC, 2023–2025", ha="center", fontsize=7.5, color=style.INK_2)
ax.annotate("Henry\narrives", (2024, BAL_RUN.loc[2024, "epa"]), xytext=(2023.25, 0.17), fontsize=7,
            color=style.INK_2, ha="center", arrowprops=dict(arrowstyle="-", color=style.MUTED, lw=0.7))
ax.axhline(0, color=style.BASELINE, lw=0.9, zorder=1)
ax.set_xlim(2017.6, 2026.6)
ax.set_ylim(-0.27, 0.26)
ax.set_xticks(SEASONS)
ax.tick_params(labelsize=8)
ax.set_ylabel("EPA per designed run", fontsize=8)
ax.grid(axis="x", visible=False)
fig.tight_layout()
plt.show()
Figure 63.5: Rushing efficiency, regular seasons 2018–2025: EPA per designed run (quarterback scrambles, kneel-downs and botched snaps excluded) for every team (gray dots), the league average (gray line) and Baltimore (blue, with its league rank). In 2018 Jackson started only the last seven games; 2021 and 2022 were cut short by his injuries in December. Shaded bands mark the two offensive coordinators of the era. Data: nflverse play-by-play via nflreadpy.

Four things stand out in Figure 63.5.

It was the best run game of its time, and it stayed that way. Baltimore’s designed runs ranked 1st, 1st, 13th, 2nd, 3rd, 1st, 3rd in the league from 2019 to 2025, and were above the league average every season. In a league where the average designed run loses expected points (the gray line sits below zero every year), the Ravens’ runs gained them in every season but 2021, under two coordinators, with five different leading running backs (Mark Ingram, J. K. Dobbins, Devonta Freeman, Gus Edwards and Derrick Henry). Their success rates tell the same story. A run is “successful” when its EPA is positive, meaning it left the offense better off than before the snap; more than 43% of Baltimore’s designed runs were successful every season, against a league that hovered around 41%.

2018 was a different offense halfway through. The season’s dot mixes ten weeks of Flacco with seven of Jackson; the split earlier in the chapter (from -0.02 to +0.08 per run) is the real story.

The dip is the injuries. 2021 is the one season the Ravens’ runs were merely average (13th). Jackson played 12 games before an ankle injury in December ended his season, the team lost its top two running backs before the season began, and Baltimore finished 8–9.19 2022 ended the same way, with Jackson hurt in December and backup Tyler Huntley starting the wild-card loss in Cincinnati. Even so, the run game ranked 2nd that year. (A footnote on the cost the chapter keeps naming, the quarterback’s body: neither season-ending injury came on a designed run. The 2021 ankle injury came on a completed pass at Cleveland, and the 2022 knee injury on a sack against Denver. A running quarterback takes more hits, but the pocket is not safe either.) The 2021 line was weakened too: left tackle Ronnie Stanley, back from the ankle injury that had ended his 2020 season, played only the opener before more surgery ended his year.20

Two coordinators, one result, different recipes. The top of the chart is level across the change of coordinator in 2023, which can make it look as if nothing changed. A great deal did. Figure 63.6 shows the recipes.

Show the Python
fig, axes = plt.subplots(2, 2, figsize=(6.6, 4.9))
blue, gray = style.SERIES[0], style.MUTED


def two_lines(ax, xs, bal, lg, title, fmt="{:.0%}", ymax=None):
    ax.plot(xs, lg, color=gray, lw=1.6, marker="o", ms=3)
    ax.plot(xs, bal, color=blue, lw=2.2, marker="o", ms=4)
    ax.set_title(title, fontsize=9)
    ax.tick_params(labelsize=7.5)
    ax.grid(axis="x", visible=False)
    if ymax:
        ax.set_ylim(0, ymax)
    ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda v, _: fmt.format(v)))


ax = axes[0, 0]
ax.bar(LJ_PG.index, LJ_PG.values, color=[blue if s != 2018 else "#9ec5f4" for s in LJ_PG.index], width=0.7)
for s, v in LJ_PG.items():
    ax.text(s, v + 0.15, f"{v:.1f}", ha="center", fontsize=7, color=style.INK_2)
ax.set_title("Jackson's designed runs per game", fontsize=9)
ax.set_ylim(0, 10)
ax.tick_params(labelsize=7.5)
ax.grid(axis="x", visible=False)
two_lines(axes[0, 1], SEASONS, [HEAVY.loc[s, True] for s in SEASONS], LG_HEAVY.values,
          "Plays with fewer than 3 WRs", ymax=0.95)
ax = axes[1, 0]
pist_bal = [PISTOL_BAL.get(s, np.nan) if s <= 2022 else QBLOC.loc[(s, True), "P"] for s in SEASONS]
pist_lg = [PISTOL_LG.get(s, np.nan) if s <= 2022 else
           (QBLOC.loc[(s, False), "P"]) for s in SEASONS]
two_lines(ax, SEASONS, pist_bal, pist_lg, "Plays from the pistol", ymax=0.62)
ax.axvline(2022.5, color=style.BASELINE, lw=0.9, ls=(0, (3, 2)))
ax.text(2022.6, 0.56, "NGS | FTN", fontsize=6.5, color=style.MUTED)
# FTN also charted 2022: plot it as a hollow marker so the 2022 -> 2023 drop can be read like for like
ax.plot([2022, 2023], [QBLOC.loc[(2022, True), "P"], QBLOC.loc[(2023, True), "P"]], color=blue, lw=1.0, ls=(0, (2, 1.5)))
ax.plot([2022], [QBLOC.loc[(2022, True), "P"]], marker="D", ms=4.5, mfc="white", mec=blue, mew=1.3, ls="none")
ax.annotate("2022 by FTN", (2022, QBLOC.loc[(2022, True), "P"]), xytext=(2019.2, 0.15), fontsize=6.3,
            color=blue, arrowprops=dict(arrowstyle="-", color=blue, lw=0.6))
ax = axes[1, 1]
yrs = [2022, 2023, 2024, 2025]
two_lines(ax, yrs, [QBLOC.loc[(s, True), "U"] for s in yrs], [QBLOC.loc[(s, False), "U"] for s in yrs],
          "Plays under center", ymax=0.45)
ax.set_xticks(yrs)
ax.set_xlim(2021.6, 2025.4)
for a in (axes[0, 0], axes[0, 1], axes[1, 0]):
    a.set_xticks([2018, 2020, 2022, 2024])
axes[0, 1].text(2018.1, 0.86, "Baltimore", color=blue, fontsize=7.5, fontweight="bold")
axes[0, 1].text(2018.1, 0.12, "league", color=gray, fontsize=7.5, fontweight="bold")
fig.tight_layout(h_pad=1.2, w_pad=1.2)
plt.show()
Figure 63.6: How the run game was built, 2018–2025 regular seasons (Baltimore in blue, league average in gray). Top left: Jackson’s designed runs (no scrambles, botched snaps or likely sneaks) per game he played. Top right: share of plays with fewer than three wide receivers. Bottom left: share of plays from the pistol (Next Gen Stats formation labels through 2022, FTN charting from 2023; the vertical line marks the change of source). The two sources disagree a little, so the hollow diamond adds FTN’s own 2022 figure: measured the same way both years, Baltimore’s pistol share still fell from about a quarter of its plays to under a tenth, so the drop is real and not an artifact of the new source. Bottom right: share of plays with the quarterback under center (FTN charting, 2022–2025 only). Data: nflverse play-by-play, participation and FTN charting via nflreadpy.

Read the four panels of Figure 63.6 as three eras.

The Roman years (2019–2022): the pistol option offense at full strength. Jackson averaged between 5.5 and 7.5 designed runs a game. The pistol was the base alignment (bottom left), at many times the league’s rate. And heavy personnel deepened over time: by 2022, Roman’s last season, the Ravens had fewer than three wide receivers on the field on 86% of their snaps, more than twice the league’s rate, with 22 personnel (two backs, two tight ends) their single most common grouping.21 Part of that was choice and part was necessity. The Ravens traded their leading receiver, Marquise Brown, to Arizona on the first night of the April 2022 draft, and lost his replacement as the top receiver, Rashod Bateman, to a foot injury that needed season-ending surgery in November.22 With few receivers they trusted, Roman leaned on what he had: Andrews, the rookie tight end Isaiah Likely and Ricard.

The Monken reset (2023). The 2023 bars and lines move together, and all in one direction: Jackson’s designed runs fell to 4.1 a game, the pistol nearly disappeared (7% of plays, down from 26% in 2022 counted the same way), the shotgun went to 78%, and heavy personnel fell back toward the league average. That is a coordinator deliberately moving the offense’s center of gravity from the quarterback’s legs to his arm. The section on Monken below tells that story.

The Henry years (2024–2025): under center. With Derrick Henry at running back, the heavy personnel came back but the pistol didn’t. Instead the Ravens put Jackson under center on 28% of their plays in 2024 and 35% in 2025, up from 11% in 2022, and by 2025 more often than the league (bottom right). Jackson’s designed runs kept falling, to 2.2 a game in 2025. The run game became a running back’s run game, with the quarterback’s threat held in reserve.

The constant across all three eras is the result in Figure 63.5. That is the strongest evidence that the Ravens’ advantage was never one play or one formation. It was a quarterback the defense had to account for, and a staff that kept finding new ways to collect on it.

How opponents responded

If the Ravens’ run game was the league’s best for seven years, what did defenses do about it? They tried four broad answers, and each one worked in some games, often in the games that mattered most.

Answer 1: match speed with speed

The Chargers’ seven-defensive-back plan of January 2019 was the first famous answer, and its logic is worth seeing on the field.

Show the Python
off = pistol(extra=(O("X", "WR", -1.5, -15.0, "X"), O("Z", "WR", -1.5, 14.5, "Z")))   # both receivers off the line
check_legal(off)
dfn = [D("LE", "DE", 1.0, -6.3, "9"), D("DT3", "DT", 1.0, -2.4, "3"), D("DT1", "DT", 1.0, 0.7, "1"),
       D("RE", "DE", 1.0, 6.3, "9"), D("S1", "S", 4.6, -2.4, "S"), D("S2", "S", 4.6, 2.6, "S"),
       D("NB", "NB", 4.5, -9.6, "NB"), D("LCB", "CB", 6.5, -15.0, "CB"), D("RCB", "CB", 6.5, 14.5, "CB"),
       D("FS", "FS", 12.5, -6.5, "FS"), D("SS", "SS", 12.5, 6.5, "SS")]
p = Play(off, dfn, los=35, to_go=None)
p.highlight("S1", color=style.DEFENSE)
p.highlight("S2", color=style.DEFENSE)
f = draw(p, lateral=(-18.0, 17.5), window=(-8.6, 15.0), title=False, width_in=6.0)
box_shade(f, p, 0.2, 6.0, -7.6, 7.6)
point_to(f, p, (8.6, 0.2), (5.0, 2.0), "safeties playing\nlinebacker", color=style.DEFENSE_DARK, fontsize=6.8)
say(f, p, 2.4, 11.2, "box: 6 v 7\nblockers + a read", color=GREEN, fontsize=6.8, weight="bold")
say(f, p, -6.6, -11.6, "7 defensive backs,\nno linebackers", color=style.DEFENSE_DARK, fontsize=6.8)
f.ax.set_title("The seven-DB answer: speed everywhere", fontsize=10, loc="left", color=style.INK, fontweight="bold")
plt.show()
Figure 63.7: Speed against speed: the idea behind the Chargers’ seven-defensive-back plan in the January 6, 2019 wild-card game (illustrative alignment). The Ravens are shown in pistol 12 personnel, two tight ends and two wide receivers, because the radical part of the plan was playing seven defensive backs against Baltimore’s heavy sets, not just against three receivers. Four defensive linemen and no linebackers: two safeties (ringed) line up at linebacker depth, a nickel back stands outside the left tight end, and two corners and two deep safeties play behind them. The box holds six defenders against seven blockers and a read, so on paper the Ravens have two spare hats; the defense’s bet is that six fast defenders, every one of whom can run with Jackson to the sideline, will make up the difference with pursuit and leave nothing open behind them when the Ravens throw.

The trade in Figure 63.7 is stark. The defense concedes the box count on paper (six defenders against seven blockers and a read), and gives up size at every position: a 210-pound safety at linebacker loses a lot of collisions with a pulling guard. In return, every defender can run. Against a quarterback whose best plays are outside, pursuit from the inside is the defense’s real plus-one, and safeties pursue faster than linebackers do. And a defense of seven defensive backs is already in the right personnel if the offense throws.

Why didn’t everybody copy it? Because it was built for a particular offense at a particular moment. The 2018 Ravens were short on receivers, and Jackson, making only his eighth NFL start, could not yet punish a defense for selling out to his legs. Roman’s 2019 rebuild was in large part a reply to this game. The heavy personnel made small defenders a liability, since six defensive backs can’t hold up against two tight ends and a fullback blocking down on them all afternoon. The passing game, aimed at the middle of the field with Andrews, punished defenses that took linebackers off it. The seven-DB idea never became a standard defense against Baltimore; as the base-personnel numbers earlier in the chapter show, most opponents went the other way and got bigger. Its premise, speed in the box can be worth more than size against this quarterback, survives in a milder form: the big nickel, a nickel package whose fifth defensive back is a third safety, big enough to fit a run and fast enough to chase one (02-05 has the packages).

Answer 2: get ahead and make them throw

The answer that worked best wasn’t schematic. It was the scoreboard. A pistol option offense is built on the run, and the run is most valuable when the game is close. Get ahead by two scores and the offense has to throw, and a team that has spent the week preparing for runs is suddenly defending a passing game built from fewer receivers.

NoteFilm room: Tennessee at Baltimore, January 11, 2020 (divisional round)

The 14–2 Ravens, the AFC’s top seed, lost 28–12 at home to the sixth-seeded Titans, and the game is the clearest example of the scoreboard answer. Tennessee led 14–6 at halftime and 28–6 after three quarters, Derrick Henry ran for 195 yards against the Ravens, and Baltimore spent the second half throwing.23

The numbers, from the play-by-play: the Ravens trailed by more than a touchdown on 86% of their snaps and called a designed run on only 23% of them; Jackson ran 20 times for 143 yards (many of them scrambles on broken pass plays), Baltimore turned the ball over three times, and it failed on all four of its fourth-down attempts, two of them Jackson runs on fourth-and-1 that were stopped for no gain.24

What to notice: watch the first quarter and the fourth. In the first, the Ravens are the offense this chapter describes: pistol, reads, runs. In the fourth, they are in the shotgun with four and five receivers, a team built around the run trying to throw its way back from three scores. Tennessee’s plan, as analysts described it at the time, also included more defenders at the second level who could flow to the ball, a version of answer 1, but the scoreboard did the most damage.25

Answer 3: pressure and zero

The third answer attacked the passing half of the bargain, and accepted a risk against the run to do it. In a Cover 0 pressure there is no deep safety; every defender either rushes or covers a man, and the quarterback has to beat the blitz with a quick throw before it reaches him. Miami did this to Jackson on a Thursday night in November 2021, blitzing on most of his dropbacks and playing zero coverage behind it, and the 2–7 Dolphins beat the 6–2 Ravens 22–10. 10-02 has the blitz numbers and the film.

Why is zero a gamble against this quarterback in particular? Because in zero every defender is either rushing or running with a receiver with his back to the ball, so nobody is left facing the quarterback. If Jackson escapes the rush, or simply runs on a play the defense read as a pass, there is no one to tackle him short of the end zone. Zero works against him only with two refinements. The rushers keep their lanes, each one responsible for one side of the quarterback so that he has no crease to escape through, and the defenders covering the backs play the hug rule (also called a green dog): if the back stays in to block, his man has nobody to cover and rushes too, a step late, often straight at the quarterback. That late rusher is a spy on the move. Miami’s plan had both, and the run game didn’t rescue Baltimore that night either: its designed runs lost value (-0.14 EPA per run on 19 of them), with Jackson held to 21 yards on 5 designed carries.26

Zero also takes away the time a play-action pass needs: a heavy formation’s fake pulls linebackers up, but a blitzing linebacker was coming up anyway. The Ravens’ longer-term answer was the one the next sections describe: a passing game with more receivers who could win quickly, built under a new coordinator.

Answer 4: spy him and disguise it

The fourth answer is the most direct: assign one defender to Jackson, a spy, and hide which one. The cost of a spy is the cost of every answer in this list: a defender assigned to the quarterback is not in coverage and not filling a gap. The results were mixed, which is what you’d expect from a tool rather than a cure. The Chiefs spied him with linebacker Leo Chenal on the 2024 opening night and won 27–20, but not because the spy stopped the run: Jackson ran 16 times for 122 yards and Baltimore’s designed runs averaged +0.26 EPA, in a game decided on its final snap. The Lions’ plan in Week 3 of 2025 worked better: Jackson ran 7 times for 35 yards, the designed runs averaged -0.40, and Detroit won 38–30 in Baltimore, with its coordinator crediting not the spy but a disguise that made every defender look the same before the snap.27 10-02 tells both games.

The everyday answers

The four answers above are the famous ones, built for single games. Underneath them, every defense that played Baltimore used the ordinary tools of option defense from 03-04 and 05-05, mixed snap to snap so the quarterback’s read kept changing:

  • The scrape exchange and the gap exchange. The read end crashes on the back, and a linebacker (or a safety) loops outside him to take the quarterback. Jackson reads “keep” and runs into the man who was waiting for him.
  • The mesh charge. The read end attacks the mesh itself, straight at the point where the ball is being handed off, so the quarterback has to decide in a fraction of a second, and a hurried decision is more often wrong.
  • Make him pay. Give him the keep, and hit him on every one. It concedes yards on the play to tax the quarterback over four quarters and a season, and it is the reason the Ravens rationed his carries.
  • Late safety rotation. Show two deep safeties before the snap, so the box looks light and the read looks easy, then rotate one down into the alley after it. It is the light-box idea of the Fangio-era defenses that 12-08 studies, and it was built in part for quarterbacks like this one.

What the answers have in common

Look at the four together and a pattern appears. None of them stopped the run game for a season; the chart in Figure 63.5 says so. What they did was make the Ravens play from behind, or make them throw against a defense built to stop the throw, in single games. That is the honest summary of the era’s defensive history, and it leads straight to the question every Ravens fan has argued about.

The playoffs, honestly

From the 2018 season through 2024, the Ravens with Jackson at quarterback went 3–5 in the playoffs and never reached a Super Bowl. “Can’t win in January” became the era’s caption. What does the data say? (Playoff games are played in January of the following calendar year, so this section names each game by its date: “January 2020” is the 2019 season’s playoff game.)

Show the Python
fig, axes = plt.subplots(1, 2, figsize=(6.7, 3.6), gridspec_kw={"width_ratios": [1.45, 1]})
ax = axes[0]
ax.axhline(REG_LINE["epa"], color=style.MUTED, lw=1.4)
ax.text(8.45, REG_LINE["epa"] + 0.03, "regular season", fontsize=7, color=style.INK_2, ha="right")
ax.axhline(0, color=style.BASELINE, lw=0.8)
for i, (gid, r) in enumerate(PO_LINES.iterrows()):
    won = r["bal"] > r["opp_pts"]
    ax.scatter(i, r["epa"], s=36, color=style.SERIES[0], facecolor=style.SERIES[0] if won else "white", lw=1.5,
               zorder=4, label=("all plays" if i == 0 else None))
    ax.scatter(i + 0.22, r["run_epa"], s=16, marker="D", color=style.SERIES[1], lw=0, zorder=3,
               label=("designed runs" if i == 0 else None))
ax.set_xticks(range(len(PO_LINES)),
              [f"{r.opp}{'*' if gid == '2022_19_BAL_CIN' else ''}\n{pd.Timestamp(r.gameday):%b}\n'{pd.Timestamp(r.gameday):%y}"
               for gid, r in PO_LINES.iterrows()], fontsize=6.4)
ax.set_ylabel("EPA per play", fontsize=8)
ax.set_title("Each playoff game", fontsize=9)
ax.tick_params(axis="y", labelsize=7.5)
ax.grid(axis="x", visible=False)
ax.set_ylim(-0.72, 0.42)
ax.legend(fontsize=6.6, loc="lower right", frameon=False, handletextpad=0.2)
ax = axes[1]
labels = ["trailing by 8+", "designed runs", "turnovers / game"]
regv = [REG_LINE["trail8"], REG_LINE["run_rate"], REG_LINE["to"] / REG_GAMES]
pov = [POST_LINE["trail8"], POST_LINE["run_rate"], POST_LINE["to"] / len(PO_LINES)]
y = np.arange(3)[::-1]
ax.barh(y + 0.18, regv, height=0.34, color=style.MUTED, label="regular season")
ax.barh(y - 0.18, pov, height=0.34, color=style.SERIES[1], label="playoffs")
for yy, a, b, k in zip(y, regv, pov, range(3)):
    fa = (lambda v: f"{v:.1f}") if k == 2 else pct
    ax.text(a + 0.03, yy + 0.18, fa(a), va="center", fontsize=7, color=style.INK_2)
    ax.text(b + 0.03, yy - 0.18, fa(b), va="center", fontsize=7, color=style.INK_2)
ax.set_yticks(y, labels, fontsize=7.5)
ax.set_xlim(0, 2.3)
ax.set_xticks([])
ax.grid(False)
ax.spines["bottom"].set_visible(False)
ax.legend(fontsize=7, loc="upper right")
ax.set_title("What changed", fontsize=9)
fig.tight_layout(w_pad=1.0)
plt.show()
Figure 63.8: Baltimore’s nine playoff games, 2018–2024 seasons, against the regular season. Left: EPA per play in each game, labelled by opponent and the month the game was played (blue circles for all plays, filled for wins and open for losses; small orange diamonds for the designed runs alone; the gray line is the Ravens’ average on all plays across the Jackson-era regular seasons, from week 11 of 2018). The January 2023 game in Cincinnati (*) was started by Tyler Huntley, and Jackson left the January 2021 game in Buffalo with a concussion at the end of the third quarter. Right: three things that separate the playoff games from the regular season: the share of snaps played while trailing by more than a touchdown, the share of plays that were designed runs, and turnovers per game. Data: nflverse play-by-play and schedules via nflreadpy.

Three readings.

The offense was worse in the playoffs, by more than the opposition explains. Across the Jackson-era regular seasons the Ravens averaged +0.089 EPA per play, an elite offense. In their nine playoff games they averaged -0.008, roughly an average one. Some drop is normal: playoff defenses are better, and they have time to prepare. But the typical playoff offense of these years played about 0.06 EPA per play worse in January than in its own regular season, and Baltimore fell by 0.10, close to twice as much. Nor was it simply the quality of the opponents: in the regular season, against teams that went on to make the playoffs, the Ravens still averaged +0.083, barely below their overall +0.089.28 That part of the narrative is true.

The run game failed early in the era, and not at the end. Overall, the Ravens’ designed runs fell from +0.08 EPA per play in the regular season to -0.02 in the playoffs, but the diamonds in Figure 63.8 show where that came from. The runs lost value in each of the first four losses: the Chargers in January 2019 (the seven-defensive-back game), the Titans in January 2020, Buffalo in January 2021 and the Huntley game in Cincinnati in January 2023. They gained value in the last two losses and in all three wins. Take out just the January 2019 and January 2020 games and the playoff designed runs averaged +0.06. So the early answer to “was the run game solved?” is partly yes, in single games, by defenses with a week to prepare; the later answer is no.

The losses came from falling behind and giving the ball away. The right panel is the mechanism. In the regular season the Ravens trailed by more than a touchdown on only 10% of their snaps; in the playoffs, on 37%. They turned the ball over 1.1 times a game in the regular season and 1.8 in the playoffs, with three or more turnovers in 4 of the 6 losses. Did the staff abandon the run on its own, or did the score take it away? The split by score answers it. With the game within a touchdown either way, the Ravens called designed runs on 47% of their playoff snaps, against 45% in the regular season: in close games they ran as much as ever. The overall run share fell, from 46% to 41%, only because they spent so much more of their playoff time behind. That five-point fall looks small, but it is an average over nine games; in the two worst deficits (January 2020 and January 2024) the designed-run share was 23% and 18%. This is answer 2 from the last section, playing out across a decade: a run game built to win close games was asked, again and again, to win from behind.

Look at the losses one at a time and both stories appear. In January 2019 against the Chargers and January 2020 against the Titans, the run game was stuffed, and early deficits and turnovers finished the job. In January 2021 at Buffalo, a 3–3 game at halftime turned late in the third quarter: down 10–3, Jackson drove the Ravens to the Bills’ 9 and threw an interception that Taron Johnson returned 101 yards for a touchdown, and two plays later Jackson left with a concussion, and the Ravens lost 17–3 with Tyler Huntley at quarterback.29 In January 2024 against Kansas City, the Ravens trailed 17–7 at halftime and ran only 16 times all game, with their running backs carrying six of them; their designed runs averaged +0.17 EPA, better than their regular-season average, and they lost 17–10 after three turnovers.30 In January 2025 at Buffalo they ran for 176 yards and still lost 27–25, after an interception, a fumble on a sack and a Mark Andrews fumble, then a failed two-point conversion that would have tied the game.31 The later losses look less like a scheme being solved and more like an offense built on the run being taken out of it by the scoreboard.

How much of that is bad luck? Some, certainly: nine games, about 597 plays, is a small sample, and turnovers in particular are noisy from game to game. But a pattern repeated across six seasons, four lead running backs (Gus Edwards, Ingram, Dobbins and Henry) and three coordinators (Mornhinweg, Roman and Monken) deserves an explanation, and the most honest one is this: the pistol option offense was at its best protecting a lead or playing even, and the games it lost were the games where it had to do the thing it was built to do least. The 2023 change of coordinator was, in part, an attempt to fix exactly that.

NoteMadden vs. real life

In a video game, a great offense is great in every situation, because you can call any play from any score. Real offenses are built for a range of situations: personnel, practice time and a quarterback’s skills all get spent on the situations the staff expects most. An offense built to run is spending its practice week on runs, and when it falls behind by fourteen, it is asking players to win with the part of the playbook they practised least. That is a trade-off, not a flaw, and every offense makes one.

The Monken changes (2023)

After the 2022 season Greg Roman left. The Ravens’ offense under him had ranked first, seventh, 17th and 19th in points in his four years as coordinator, and the passing game had never quite caught up to the run.32 In February 2023 Baltimore hired Todd Monken, who had just won two straight national championships as Georgia’s offensive coordinator and had run NFL offenses in Tampa Bay and Cleveland.33

Monken’s mandate was to make Jackson a quarterback who could win from the pocket, without throwing away what made him dangerous. Figure 63.6 shows what he did:

  • From the pistol to the shotgun. Pistol snaps fell from 26% of plays in 2022 to 7% in 2023, and the shotgun rose to 78%.34 The shotgun gives the quarterback a better view of the coverage and a faster launch, at the cost of the pistol’s downhill back and its lack of a tell.
  • Fewer designed quarterback runs. Jackson’s designed runs fell from 5.5 a game to 4.1. He still ran, but more of it was scrambling, improvising when a pass play broke down, rather than running on purpose.
  • More receivers, and more passing on early downs. The Ravens drafted receiver Zay Flowers in the first round, used three-receiver sets more, and on neutral early downs ran the ball on 43% of plays, 27th in the league, after ranking first in that category in 2019.

The result was a career-high 3,678 passing yards for Jackson and his second MVP award, won with 49 of 50 first-place votes, and a 13–4 record and the AFC’s top seed.35 The run game didn’t collapse without the volume of quarterback runs. It ranked 3rd in EPA per designed run, and the Ravens still led the league in rushing yards. The likeliest reason is that the threat was doing some of the work the carries used to do: a backside end who had to respect Jackson every snap was still frozen, whether Jackson ran six times or four. Play-by-play can’t see a frozen end, but it can see the runs Jackson didn’t carry, and they held up. Baltimore’s designed runs by its running backs and receivers averaged +0.002 EPA in 2023, 5th in the league and well above the league’s -0.08; in Roman’s last season, with more Jackson carries, they had ranked 9th.36

NoteFilm room: AFC Championship, Kansas City at Baltimore, January 28, 2024

The Ravens beat Houston 34–10 in the divisional round, then hosted the Chiefs for a trip to the Super Bowl. They lost 17–10, and the game became the sharpest criticism of the Monken offense: Baltimore ran only 16 times, and its running backs carried just six of those.37

What to notice: the first half and the turnovers, not the play-calling alone. Kansas City led 17–7 at halftime, and the Ravens turned the ball over three times: a fumble on a sack in the second quarter, then, in the fourth, Zay Flowers’s fumble at the Kansas City 1-yard line and an interception in the end zone. Watch Steve Spagnuolo’s defense on Jackson’s dropbacks: four-man rushes, disguised coverages, and rushers who kept their lanes so that Jackson’s scrambles went nowhere. Then ask the question the chapter keeps asking: was the run abandoned, or was it taken away by the scoreboard? The answer is some of both, and it was the last time the Ravens’ run game rested mainly on Jackson.

The Derrick Henry seasons (2024–2025)

In March 2024 the Ravens signed Derrick Henry, the 30-year-old former Titans back who had run for 2,027 yards in 2020 and had beaten them almost single-handedly in that January 2020 playoff game, on a two-year contract.38 It was a signing that made the threat run both ways. For five years, defenses had to decide whom to fear on a read: the quarterback or a good running back. Now the back was one of the most punishing runners of his generation, a 247-pound back who had led the league in rushing twice.

2024 was the best offense of the era. Henry ran for 1,921 yards and 16 touchdowns, second in the league in yards behind Saquon Barkley’s 2,005; Jackson threw for 4,172 yards, 41 touchdowns and only four interceptions; and Baltimore led the NFL in EPA per play, in rushing yards, and, again, in EPA per designed run.39 The team went 12–5, beat Pittsburgh in the wild-card round as Henry ran for 186 yards, and lost at Buffalo, the game described above.

The run game moved under center. The data shows how differently the Henry offense was built. The Ravens took 28% of their 2024 snaps with Jackson under center, nearly three times their 2022 rate, and 45% of their designed runs started there.

Why under center, after five years of selling the pistol, and for the most downhill back in the league? The pistol and the I-formation both put the back about seven yards deep, so depth isn’t the difference. The differences are what happens at the mesh and what the defense can see.

  • A straight track to the hole. From the pistol the quarterback stands four yards deep, directly in the back’s way, so the back has to bend around his hip to take the handoff. From under center the quarterback opens out of the back’s path and hands off on the move, and the back runs his zone or duo track with his shoulders square to the line. That was the game Henry had built his career on: from 2019 to 2023 no player ran for more yards with his quarterback under center (5,795, by ESPN’s count). His former Tennessee teammate Taylor Lewan said publicly that Henry wanted to run downhill and disliked turning his shoulder on read plays, although Henry himself said he was comfortable with them.40
  • Play-action with the quarterback’s back to the defense. Under center, the quarterback turns away from the line to fake the handoff, so linebackers and safeties can’t see the ball, and the fake to a back as feared as Henry holds them longer. Henry’s Titans had averaged more yards per play-action pass than any other team since 2016, and the Ravens’ +0.31 EPA per dropback in 2024 ranked 1st in the league.
  • A quarterback who was now a passer first. Monken had already moved Jackson’s game toward the pocket. With Henry carrying the load, the offense no longer needed Jackson’s legs on every snap, and under center it no longer had to show them.

What it gave up was the read: from under center the quarterback can’t easily read an end and keep the ball. So the read game didn’t vanish; it became a change-up, and it kept its sting. Henry’s carries from the pistol, 92 of them across the two seasons, averaged 7.8 yards and +0.21 EPA, against 5.1 yards and +0.09 EPA from under center.41 Read that comparison with care. It is a small sample, and a change-up is called when the staff thinks the defense is set up for it, so its numbers flatter it; the same play called on every snap would not average that much. But it is the read offense in miniature: an end who has to stay home for Jackson is an end who isn’t chasing Henry.

NoteFilm room: Pittsburgh at Baltimore, January 11, 2025 (wild-card round)

The Henry offense at its best. Baltimore beat Pittsburgh 28–14, called designed runs on 64% of its plays, and never trailed; Henry carried 26 times for 186 yards.42

What to notice: Jackson’s alignment before each run. Under center, watch the back’s track and the second level: the linebackers have to fit Henry’s downhill run, not a read. Then watch for the snaps in the pistol or shotgun, the change-up, and for the backside end on them: he can’t chase Henry, because the quarterback might keep. Two offenses, one game, and the defense has to be ready for both.

2025 was the end. Jackson missed four games, three with a hamstring injury in October and one with a back injury in December, and was less of a runner when he played, saying afterwards that his health had limited his mobility:43 by this chapter’s count 28 designed runs in 13 games, about two a game. Henry still ran for more than 1,500 yards, the run game still ranked 3rd in EPA per designed run, and the Ravens ran on early downs more than all but one team. But a 1–5 start, a defense that gave up 398 points, and a 26–24 loss at Pittsburgh in Week 18 with the division on the line left them 8–9 and out of the playoffs.44 On January 6, 2026, the Ravens fired John Harbaugh after 18 seasons.

What survives: the era closed, and the 2026 epilogue

The coaches have scattered. Harbaugh is the Giants’ head coach in 2026, Monken is the Browns’ head coach, and Roman spent 2024 and 2025 as Jim Harbaugh’s offensive coordinator with the Chargers, who fired him in January 2026.45 The 2026 Ravens belong to Jesse Minter, a Ravens assistant from 2017 to 2020 and the Chargers’ defensive coordinator in 2024 and 2025, as head coach, with Declan Doyle, who came from Ben Johnson’s Chicago staff, as offensive coordinator and Anthony Weaver as defensive coordinator.46 Jackson is still the quarterback. What Doyle builds around him is a 2026 story, and this book’s coverage stops at the end of the 2025 season, so whether his Ravens line up in the pistol, the shotgun or under center is a question for your own Sunday viewing. The Watch-for-it list below is how to answer it.

What survives the era is bigger than one team.

  • The arithmetic is now conventional wisdom. “Count the quarterback as a blocker” was an option coach’s slogan in 2018. By 2025 it was how every NFL defense planned for Jalen Hurts, Josh Allen and Jayden Daniels, and how every analyst explained why those offenses ran well against light boxes.
  • Heavy personnel came back. In 2025 NFL offenses used fewer than three receivers on 41.7% of plays, the most since Next Gen Stats began tracking.47 The Ravens weren’t the only cause (the 49ers, Rams and Eagles all pushed the same way), but they showed that a modern offense could live in 12, 21 and 22 personnel and still throw.
  • The quarterback run became a managed resource. The Ravens’ own data tells the story of the last seven years in miniature: from seven or eight designed runs a game in 2019 to about two in 2025, with the efficiency of the run game barely moving. The lesson the league took is that a running quarterback’s value is mostly in the threat, which can be spent a few carries at a time, on the downs and in the games where it matters, and saved the rest of the time.
  • The pistol didn’t. The pistol as a base alignment was a Ravens thing, and it left with Roman. It survives as a run-heavy niche look (02-02’s data has a few teams living in it), and as a change-up that offenses built around the shotgun or under center use when they want a read, as the Henry-era Ravens did. That is a good example of the course’s larger point: the principle (an extra blocker, either side, no tell) outlived its favourite formation.
TipWatch for it

On Sunday: count the defenders against a running quarterback. Any team with a quarterback who runs, Baltimore or not, will do.

Before the snap

  • Count the blockers: five linemen, plus every tight end and fullback near the line.
  • Count the box: the box count is every defender within about 5–7 yards of the line and inside the tight ends.
  • Add the quarterback. If he’s in the pistol or shotgun with a back beside or behind him and is a real runner, add one to the offense’s side. Now compare: more defenders than blockers-plus-one, and the defense has its plus-one; even, and the defense’s extra man has to come from a safety.
  • Find where the extra man is. A safety walked down at eight yards, or a single deep safety in the middle? Then the play-action pass is aimed at him.
  • Where is the back? Directly behind the quarterback (pistol): no directional tell. Beside him: note the side. On zone runs and zone reads the run usually goes the other way; gap runs from the shotgun often go to the back’s side, so look for a puller before you commit.

At the snap

  • Watch the two ends. The one nobody touches is being read. Does he stay home (give coming) or chase (keep coming)?
  • Look for a puller. A guard pulling toward the unblocked end: power read. A guard and a tight end pulling the same way while the quarterback and the back step the other way: QB counter. The same two pullers with the back following them: counter read or counter bash, with the quarterback reading an end.

After the play

  • On the replay, check the backside end on a plain handoff. Did he chase the cutback, or stay home for a quarterback who didn’t have the ball? Count how often he stays, over a quarter. That is the hidden yardage.
  • Note the score. Is the offense still running when it’s behind by two scores? That’s the question that decided the Ravens’ playoff games.

Predict the play

TipPredict the play

Drill 1: eight in the box. First-and-10 at midfield, first quarter, tied. Baltimore is in the pistol with two tight ends; the defense walks a safety down and shows eight in the box with one deep safety. Count the blockers and the box with Jackson as a runner. Who is the defense’s extra man, and which Ravens play does this picture invite?

Show the Python
dfn = [D("SS", "SS", 1.6, 6.8, "SS") if q.key == "PE" else q for q in front62(fs=True)]
p = Play(pistol(), dfn, los=60, to_go=10, title="Drill 1: 1st and 10, midfield")
f = draw(p, lateral=(-10.0, 10.5), window=(-8.6, 14.6), width_in=4.8)
box_shade(f, p, 0.2, 5.6, -7.6, 8.3)
plt.show()
Figure 63.9: Drill 1: pistol 12 personnel against an eight-man box and a single deep safety. The strong safety (SS) has walked down outside the right tight end to play the edge, the job a 9-technique would do (the wide receivers and cornerbacks are out of the picture).

Seven blockers plus Jackson’s read against eight: a hat on every hat. Five linemen and two tight ends make seven blockers. The box has eight: five linemen, the walked-down strong safety on the right edge, and two linebackers. On a handoff with a pocket quarterback the defense would be plus-one. With Jackson reading one end, it is even (Figure 63.1, Figure 63.2).

The defense’s extra man is the free safety, the only defender not in the box and the only one deep. If he comes down to stop the run, there is nobody behind the linebackers.

What it invites: both, and that’s the point. A read is fine: the box is even, and the 2019 Ravens ran well against eight-man boxes all season. But eight in the box against the Ravens meant the defense had spent its last spare man, so this is also the look a play-action pass at the deep middle is built for: a fake to the back that holds the linebackers and the safety for a beat, with a tight end up the seam behind them (Figure 63.3, left). A defense that shows this picture can’t be right about both, which is why the Ravens could call either.

TipPredict the play

Drill 2: read the 9. Same look, a moment after the snap. The line is blocking power to the right: the tight end and tackle have blocked down, the left guard is pulling. The play-side 9 has not been touched. He has stepped hard inside, toward the hole. The back is coming around Jackson’s right hip and sweeping wide. Give or keep? And if the read is right, who has to make the tackle?

Show the Python
dfn = [q.moved(d=1.6, w=5.6) if q.key == "PE" else q for q in front62(fs=True)]
p = Play(pistol(), dfn, los=60, to_go=10, title="Drill 2: the 9 squeezes")
go_to(p, "Y", (0.4, 4.1), via=[(-0.3, 4.5)])
go_to(p, "RT", (0.5, 2.7), via=[(-0.4, 3.0)])
go_to(p, "RG", (3.7, -1.6), via=[(-0.2, 1.9), (0.4, 2.0), (1.4, 0.8)])
go_to(p, "C", (0.4, -0.5))
go_to(p, "LT", (0.3, -3.8), via=[(-0.6, -3.5)])
go_to(p, "U", (0.4, -6.0), via=[(-0.4, -5.3)])
go_to(p, "LG", (3.4, 2.9), "pull", via=[(-2.2, -1.3), (-2.7, 0.9), (-1.9, 3.4), (-0.3, 4.7), (1.6, 4.3)])
go_to(p, "RB", (-4.0, 3.6), "path", via=[(-5.0, 0.6), (-4.4, 1.4)])
p.highlight("PE", color=style.DEFENSE)
f = draw(p, lateral=(-10.0, 13.0), window=(-8.6, 14.2), width_in=5.0)
x0, y0 = _P(f, p, 1.0, 8.6)
x1, y1 = _P(f, p, 1.4, 6.8)
f.ax.add_patch(FancyArrowPatch((float(x0), float(y0)), (float(x1), float(y1)), arrowstyle="-|>", mutation_scale=10,
                               color=style.DEFENSE, lw=1.6, zorder=6))
point_to(f, p, (4.2, 9.6), (1.5, 7.2), "the 9 squeezes\ninside", color=style.DEFENSE_DARK, fontsize=6.8)
read_line(f, p, (-4.0, 0.3), (0.6, 5.0))
plt.show()
Figure 63.10: Drill 2: the pistol power read a moment after the snap. The play-side 9 (ringed) has squeezed inside from where he lined up (his short arrow). Every block is drawn as its full assignment: the down blocks, the right guard’s climb to the Will and the left guard’s pull for the Mike. The back (thin arrow) is coming around the quarterback’s right hip.

Give. The 9 has squeezed inside, toward the hole the quarterback would run through: he has taken the keep away himself. So Jackson hands the ball to the back, who is already running outside the 9 at full speed (Figure 63.2).

Who has to tackle him? Probably nobody in the box. Every other box defender has a blocker assigned (the down blocks, the right guard climbing to the Will, the pulling guard leading for the Mike), and the 9 is inside the runner. The one box defender with a chance is the Mike, if he reads the play fast and beats the pulling guard over the top, which is why a give is never a sure thing. Otherwise the tackle has to come from outside or behind: the cornerback on that side (if the wide receiver hasn’t blocked him) or the free safety, coming from 12 yards deep across the field. That is the power read’s promise, a running back in space against a defensive back, and its risk: if the corner or safety arrives in time and the back was running sideways, the gain is small.

If the 9 had widened with the back instead, the answer flips: keep, and Jackson runs inside him behind the pulling guard.

TipPredict the play

Drill 3: down fourteen. Second-and-8 at your own 30, early in the third quarter of a playoff game. You’re down 14. The defense shows two deep safeties and only six in the box. Your quarterback is a great runner; your offense has the league’s best run game. Does the box say run? Does the scoreboard? What would you call, and what does the Ravens’ history say about the trap in this picture?

Show the Python
off = line5() + [O("QB", "QB", -5.0, 0.0, "Q"), O("RB", "RB", -5.0, 2.1, "R"), O("Y", "TE", -0.7, 4.8, "Y"),
                 O("X", "WR", -0.7, -15.0, "X"), O("H", "WR", -1.5, -9.0, "H"), O("Z", "WR", -1.5, 14.5, "Z")]
check_legal(off)
dfn = [D("LE", "DE", 1.0, -4.1, "5"), D("DT1", "DT", 1.0, -0.7, "1"), D("DT3", "DT", 1.0, 2.4, "3"),
       D("RE", "DE", 1.0, 5.6, "9"), D("MIKE", "MIKE", 5.0, 1.6, "M"), D("WILL", "WILL", 5.0, -2.4, "W"),
       D("NB", "NB", 5.0, -8.6, "NB"), D("LCB", "CB", 7.0, -15.0, "CB"), D("RCB", "CB", 7.0, 14.5, "CB"),
       D("FS", "FS", 13.0, -7.0, "FS"), D("SS", "SS", 13.0, 7.0, "SS")]
p = Play(off, dfn, los=40, to_go=8, title="Drill 3: 2nd and 8, down 14, third quarter")
f = draw(p, lateral=(-18.0, 17.5), window=(-8.0, 15.6), width_in=5.6)
box_shade(f, p, 0.2, 6.0, -6.4, 7.0)
plt.show()
Figure 63.11: Drill 3: shotgun 11 personnel (one tight end, three receivers) against a two-high shell with six in the box; the offense is down 14 early in the third quarter.

The box says run. It is a light box: six defenders against six blockers (five linemen and the tight end) and a quarterback who reads: the offense is effectively plus-one. Against light boxes, efficient running teams do well, and the Ravens were the most efficient running team of their era (Figure 63.5).

The scoreboard says “don’t panic.” Down 14 with more than a quarter and a half to play is two possessions, not a two-minute drill. A good run on second-and-8 is a good play in any quarter, and this two-high shell is inviting you to throw: if you throw against two deep safeties and five underneath (the nickel, both linebackers and both corners, with four rushing), you are throwing into the coverage the defense chose. (The win-probability tools of 13-03 make this precise.)

But is running really better for this team? That is the comparison that decides it, and it isn’t automatic: 10-01 showed that passing is usually the more efficient choice, and the 2019 Ravens’ dropbacks (+0.32 EPA) were worth more per play than their runs (+0.11). Two things tip this snap toward the run. The box: against six or fewer, Baltimore’s designed runs averaged +0.09 EPA across 2019–2025, against the league’s -0.02. And the score: in the Jackson-era regular seasons, when the Ravens trailed by more than a touchdown, their designed runs averaged +0.11 EPA and their dropbacks +0.06, because a defense protecting a lead plays pass and leaves the box light.48

The trap is the one in Figure 63.8: a deficit that pulls a run team off the run, and the turnovers that come with throwing from behind. The right call here is the run the defense is conceding (a read, or a designed run with the back blocking), and the right plan is to stay patient for another series. The two-score deficit becomes a three-score deficit far more often from an interception than from a three-yard run.

Takeaways

  • The 2019–2022 Ravens built a pistol option offense: the pistol as the base alignment, heavy personnel, a menu of option reads and designed quarterback runs, so that the defense had to account for one more ball carrier on every snap.
  • A running quarterback changes every run fit, not just the reads: the end he might read has to stay home on plain handoffs too, every defender’s fit gets a “quarterback” line, and backside pursuit stops being free. An eight-man box against him is a seven-man box.
  • The signature plays each make a defender’s textbook reaction wrong: the pistol inverted veer (squeeze the hole and the back runs outside you), 13 personnel (play it as a run formation and the seam opens; Baltimore threw from it two times in three), and the QB counter (follow the first flow and the play goes back the other way).
  • The data fingerprint: Baltimore’s designed runs were above the league average every season from 2018 to 2025, top three in six of them, under two coordinators and five lead backs. What changed was the recipe: pistol and quarterback runs under Roman, the shotgun and fewer designed runs under Monken (2023), under-center runs with Derrick Henry (2024–2025), chosen for his downhill track and for play-action, with the pistol read kept as a change-up.
  • Defenses answered with speed, pressure, spies, the everyday option answers and, most effectively, the scoreboard. In the playoffs (3–5 with Jackson) the offense fell off by about twice the usual playoff drop. Early in the era prepared defenses stuffed the run in single games; later, the losses came from deficits and turnovers. In close playoff games the Ravens ran as often as ever.
  • The era closed in January 2026 with Harbaugh’s firing. What survives is the arithmetic (count the quarterback as a blocker), heavy personnel, and the idea that a running quarterback’s value is mostly the threat, spent a few carries at a time.

Connections

Builds on: Option Football (every read and designed run this chapter uses), Run Fits: Gap Integrity, Force, Spill, and Box Math (the plus-one and the fit chart), Personnel Groupings and Formation Rules and Vocabulary (heavy personnel and the pistol), and The Modern Era (the league around these Ravens).

Sets up and connects to:

If you want to go deeper: the Ravens’ own press-conference transcripts from 2019 (Roman’s, especially), which are unusually candid about the design; Chris B. Brown’s Smart Football essays on the pistol and the inverted veer and his The Art of Smart Football; Chris Ault’s clinic talks on the Nevada pistol; and, for the data, this chapter’s code, which runs on the free nflverse data and can be pointed at any team with a running quarterback.

NoteGo deeper

From the simulation to real tracking data. The QB counter animation in this chapter is a gridiron simulation written in NFL Big Data Bowl coordinates (simulated, BDB-format), so real tracking frames drop into the same drawing code. The Big Data Bowl releases carry the play-by-play description of each play, so finding a Jackson designed run is a text search ("L.Jackson" as the rusher, no “scramble”), and the pre-snap frames of the 2025 edition show whether he was in the pistol. The cell below, folded on the website and not run (the course does not download the Big Data Bowl files), shows the shape of that code.

Show the Python (real tracking data; not run)
import pandas as pd
from gridiron import bdb, show_animation

trk = bdb.load_tracking("data/bdb/tracking_week_*.csv")       # BDB 2025: 2022 season, weeks 1-9
plays = pd.read_csv("data/bdb/plays.csv")
runs = plays[(plays["possessionTeam"] == "BAL") & plays["playDescription"].str.contains("L.Jackson")
             & ~plays["playDescription"].str.contains("scramble|pass", case=False)]
one = bdb.prepare(trk, plays, game_id=runs["gameId"].iloc[0], play_id=runs["playId"].iloc[0])
show_animation(one, times=[0.0, 0.6, 1.9, 2.7])                 # the same four moments as the simulation

  1. “Lamar Jackson,” Wikipedia: https://en.wikipedia.org/wiki/Lamar_Jackson (Heisman Trophy 2016; drafted 32nd overall in 2018 after the Ravens traded back into the first round; 4,132 college rushing yards at Louisville, 960, 1,571 and 1,601 in his three seasons). The trade with Philadelphia for the 32nd pick: NFL.com, “Ravens trade up to take Lamar Jackson at No. 32” (April 2018): https://www.nfl.com/news/ravens-trade-up-to-take-lamar-jackson-at-no-32-0ap3000000929151.↩︎

  2. Danny Heifetz, “Lamar Jackson Is a Quarterback,” The Ringer (February 20, 2018), on Polian’s ESPN comments of February 19: https://www.theringer.com/2018/2/20/17032538/lamar-jackson-quarterback-bill-polian-wide-receiver; Polian’s retraction (“I was wrong,” to USA Today, November 2019) is reported by CBS Sports: https://www.cbssports.com/nfl/news/bill-polian-finally-admits-he-was-wrong-to-say-lamar-jackson-should-switch-from-quarterback-to-receiver.↩︎

  3. Flacco’s hip injury and Jackson’s first start on November 18, 2018, against Cincinnati: “2018 Baltimore Ravens season,” Wikipedia: https://en.wikipedia.org/wiki/2018_Baltimore_Ravens_season; the game date is confirmed by nflverse play-by-play (game_id 2018_11_CIN_BAL).↩︎

  4. This book’s calculation from nflverse play-by-play, 2018 regular season, Baltimore: designed runs (rushes excluding quarterback scrambles, kneel-downs and botched snaps) as a share of run and pass plays, and their EPA per play, for weeks 1–10 (Flacco’s starts) and weeks 11–17 (Jackson’s). Jackson’s 147 rushing attempts and 695 yards in the 2018 regular season are from the same data. The 147 carries broke Bobby Douglass’s quarterback record of 141, set in 1972 (Jackson passed it again with 176 in 2019): ESPN, “QB Jackson counters coach, says run less” (2019): https://www.espn.com/nfl/story/_/id/27307544/qb-jackson-counters-coach-says-run-less; “List of dual-threat quarterback records,” Wikipedia: https://en.wikipedia.org/wiki/List_of_dual-threat_quarterback_records.↩︎

  5. NFL.com, “Chargers’ speedy secret plan stopped Ravens” (January 2019; Next Gen Stats: seven defensive backs on 58 of 59 snaps, 50 such snaps all regular season; Lynn’s reaction; 229 total yards, 90 rushing, seven sacks): https://www.nfl.com/news/chargers-speedy-secret-plan-stopped-ravens-0ap3000001008744. The 229 yards as the fewest the Chargers had allowed in a postseason game: Chargers.com, “Recap: Chargers top Ravens 23-17 in AFC Wild Card game” (January 6, 2019): https://www.chargers.com/news/recap-chargers-top-ravens-23-17-in-afc-wild-card-game. The yardage, sacks and score progression are confirmed by nflverse play-by-play (game_id 2018_18_LAC_BAL).↩︎

  6. NFL.com, “Roman in as new Ravens OC; Mornhinweg departs” (January 2019): https://www.nfl.com/news/roman-in-as-new-ravens-oc-mornhinweg-departs-0ap3000001009637.↩︎

  7. Roman’s coaching history (49ers offensive coordinator 2011–2014, Bills 2015–2016, back in Baltimore from 2017) is in the NFL.com piece above. His firing by Buffalo after an 0–2 start: Sports Illustrated, “Bills fire offensive coordinator Greg Roman” (September 16, 2016): https://www.si.com/nfl/2016/09/16/bills-fire-offensive-coordinator-greg-roman. The Bills’ rushing titles are this book’s calculation from nflverse play-by-play: 2,432 rushing yards in 2015 and 2,630 in 2016, first in the league both years.↩︎

  8. “Transcript: Greg Roman Press Conference,” BaltimoreRavens.com (February 19, 2019): https://www.baltimoreravens.com/news/transcript-greg-roman-press-conference-transcript. NFL.com’s account of the same period quotes him: “Obviously with Lamar at quarterback, it’s going to be tailored towards Lamar”: https://www.nfl.com/news/roman-ravens-offense-to-be-tailored-towards-lamar-0ap3000001017961.↩︎

  9. This book’s calculation from nflverse participation data (Next Gen Stats personnel labels), 2019 regular season: share of Baltimore’s run and pass plays with fewer than three wide receivers; share of plays on which the defense had exactly four defensive backs (base), against Baltimore and against all offenses.↩︎

  10. This book’s calculation from nflverse participation data, 2019 regular season (offense_formation == "PISTOL", Next Gen Stats labels): Baltimore’s pistol snaps, the league-wide share, and the next team’s count.↩︎

  11. Mike Santagata, an in-depth look at the Ravens’ rushing attack, Sports Illustrated All Bengals (October 22, 2021), on the Ravens’ power read, counter read and counter bash: https://www.si.com/nfl/bengals/allbengals-insiders-plus/in-depth-look-at-lamar-jackson-and-the-baltimore-ravens-rushing-attack.↩︎

  12. This book’s calculation from nflverse play-by-play, regular seasons: Jackson’s rushes excluding scrambles, kneel-downs, botched snaps and likely sneaks (middle runs with two yards or less to go), per game in which he threw a pass. This is the same definition of a designed quarterback run that 03-04 uses for the league. 05-05 counts 120 designed runs at 7.1 yards for Jackson in 2019 because its count keeps the short-yardage sneaks, which gain a yard or two; this chapter leaves them out, which gives 113 at 7.4.↩︎

  13. BaltimoreRavens.com, “Ravens Tie NFL Record With 12 Pro Bowlers” (December 17, 2019; Stanley, Yanda and Ricard as starters, Yanda’s eighth selection, Orlando Brown Jr. an alternate): https://www.baltimoreravens.com/news/ravens-tie-nfl-record-with-12-pro-bowlers.↩︎

  14. NFL.com, “Ravens break single-season team rushing record” (December 2019; 3,296 yards, breaking the 1978 Patriots’ 3,165): https://www.nfl.com/news/ravens-break-single-season-team-rushing-record-0ap3000001092761; “2019 Baltimore Ravens season,” Wikipedia: https://en.wikipedia.org/wiki/2019_Baltimore_Ravens_season (531 points; Jackson’s 1,206 rushing yards and 36 touchdown passes; unanimous MVP).↩︎

  15. This book’s calculation from nflverse play-by-play, regular seasons: EPA per play on everything that was not a designed run (passes, sacks and quarterback scrambles), by team and season.↩︎

  16. This book’s calculation from nflverse play-by-play and participation data (Next Gen Stats box counts and defensive personnel), regular seasons 2019–2022: share of designed runs facing eight or more defenders in the box, and share of snaps on which the defense had exactly four defensive backs (base), Baltimore against the rest of the league. Base personnel against Baltimore ran between 36% and 40% in each of those seasons, against 23% to 26% for other offenses. Rows recording fewer than three box defenders are treated as missing. The 2023–2025 box counts come from FTN charting and are not comparable.↩︎

  17. This book’s calculation from nflverse participation and play-by-play, 2019 regular season: plays with offense personnel “1 RB, 3 TE, 1 WR” (Baltimore: 57 snaps), designed runs as a share of them, for Baltimore and the league; the defense personnel on Baltimore’s 13-personnel snaps; and the same run shares for “1 RB, 1 TE, 3 WR.” The passing figures rest on a few dozen plays; read them as a tendency, not a precise rate.↩︎

  18. This book’s calculation from nflverse play-by-play, schedules and participation data, game_id 2019_13_SF_BAL: the score, the 49ers’ 10–1 record coming in and their NFC Championship win that season (game_id 2019_20_GB_SF, 37–20); Jackson’s 16 rushes for 101 yards; the fourth-and-1 run at 4:39 of the fourth quarter with the score 17–17; Tucker’s 49-yard field goal with three seconds left; the pistol share (Next Gen Stats formation labels); and the weather field (“Cloudy, Light Rain”, 40°F).↩︎

  19. “2021 Baltimore Ravens season,” Wikipedia: https://en.wikipedia.org/wiki/2021_Baltimore_Ravens_season (J. K. Dobbins and Gus Edwards lost to knee injuries before the season; Jackson’s ankle injury in Week 14 against Cleveland); “2022 Baltimore Ravens season,” Wikipedia: https://en.wikipedia.org/wiki/2022_Baltimore_Ravens_season (Jackson’s knee injury, a sprained PCL, in Week 13 against Denver: ESPN, https://www.espn.com/nfl/story/_/id/35190224/ravens-say-qb-lamar-jackson-week-week-knee-injury; Huntley started the wild-card game, a 24–17 loss at Cincinnati). Games played are from nflverse play-by-play. The 2021 injuries (Dobbins’s torn ACL in the preseason finale on August 28; Jackson’s ankle injury at Cleveland on December 12, after which he did not play again that season): AP via ClickOnDetroit, https://www.clickondetroit.com/sports/2021/08/29/ravens-dobbins-expected-to-miss-season-because-of-torn-acl/; CBS Sports, https://www.cbssports.com/nfl/news/lamar-jackson-ruled-out-of-ravens-browns-game-after-getting-carted-off-with-ankle-injury-in-first-half.↩︎

  20. CBS Sports, “Ravens former All-Pro Ronnie Stanley placed on injured reserve, left tackle to have surgery on injured ankle” (October 2021): https://www.cbssports.com/nfl/news/ravens-former-all-pro-ronnie-stanley-placed-on-injured-reserve-left-tackle-to-have-surgery-on-injured-ankle. The plays on which Jackson was hurt in 2021 (a completed pass to Andrews to open the second quarter at Cleveland) and 2022 (his last snap, a sack late in the first quarter against Denver) are from nflverse play-by-play (game_id 2021_14_BAL_CLE and 2022_13_DEN_BAL).↩︎

  21. This book’s calculation from nflverse participation data, 2022 regular season: Baltimore used 22 personnel on about a third of its plays, more than any other grouping.↩︎

  22. The Brown trade (to Arizona with the 100th pick, for the 23rd, on April 28, 2022): The Boston Globe (AP), https://www.bostonglobe.com/2022/04/28/sports/cardinals-pull-off-huge-trade-add-wr-marquise-brown-ravens. Bateman’s Lisfranc surgery, announced November 3, 2022: BaltimoreRavens.com, https://www.baltimoreravens.com/news/rashod-bateman-foot-surgery-lisfranc-season-over. Baltimore’s 2022 personnel mix (22 personnel 33% of snaps, 21 personnel 25%, 12 personnel 23%, 11 personnel 12%) is this book’s calculation from nflverse participation data; a fullback and two tight ends on the field together that often fits a real heavy-personnel season rather than a labelling quirk.↩︎

  23. “2019–20 NFL playoffs,” Wikipedia: https://en.wikipedia.org/wiki/2019%E2%80%9320_NFL_playoffs; Henry’s 195 rushing yards and the score by quarter are confirmed by nflverse play-by-play (game_id 2019_19_TEN_BAL).↩︎

  24. This book’s calculation from nflverse play-by-play, game_id 2019_19_TEN_BAL: Baltimore’s share of snaps trailing by more than seven points and of designed runs, its turnovers, Jackson’s 20 carries for 143 yards, and its four fourth-down plays (two Jackson runs on fourth-and-1, in the second and third quarters, and two incomplete passes in the fourth).↩︎

  25. Danny Kelly, The Ringer (September 10, 2020), on how defenses might solve the Ravens’ read-option offense, including Tennessee’s plan in the divisional round: https://www.theringer.com/nfl/2020/9/10/21430211/lamar-jackson-read-option-offense-nfl-solve.↩︎

  26. This book’s calculation from nflverse play-by-play, game_id 2021_10_BAL_MIA (final score 22–10, Miami’s 2–7 and Baltimore’s 6–2 records coming in from nflverse schedules).↩︎

  27. This book’s calculation from nflverse play-by-play and schedules, game_id 2024_01_BAL_KC (27–20) and 2025_03_DET_BAL (38–30); Jackson’s totals include scrambles. The Chenal plan and Kelvin Sheppard’s credit to the disguise are sourced in 10-02.↩︎

  28. This book’s calculation from nflverse play-by-play and schedules, 2018–2024 seasons: for every team-season that reached the playoffs, playoff EPA per play minus regular-season EPA per play, averaged over team-seasons; and Baltimore’s Jackson-era regular-season EPA per play against opponents that made the playoffs that season.↩︎

  29. AP, “Bills advance to AFC championship with 17-3 win over Ravens” (January 16, 2021), via KSAT: https://www.ksat.com/sports/2021/01/17/bills-advance-to-afc-championship-with-17-3-win-over-ravens/; CBS Sports, “Lamar Jackson ruled out of Ravens playoff game vs. Bills after failing to clear concussion protocol”: https://www.cbssports.com/nfl/news/lamar-jackson-ruled-out-of-ravens-playoff-game-vs-bills-after-failing-to-clear-concussion-protocol/. The halftime score, the drive to the Buffalo 9 and the sequence of plays are confirmed by nflverse play-by-play (game_id 2020_19_BAL_BUF).↩︎

  30. This book’s calculation from nflverse play-by-play, game_id 2023_21_KC_BAL: 16 rushing attempts (Jackson 8, Gus Edwards 3, Justice Hill 3, Zay Flowers 2), three turnovers, Kansas City ahead 17–7 at halftime.↩︎

  31. This book’s calculation from nflverse play-by-play, game_id 2024_20_BAL_BUF: 30 rushes for 176 yards; an interception in the first quarter, a fumble on a sack in the second, Andrews’s fumble in the fourth, and the failed two-point conversion (a pass to Andrews) with about 1:33 to play; final score 27–25.↩︎

  32. NFL.com, “Ravens, offensive coordinator Greg Roman part ways after six seasons” (January 2023), with the offense’s rankings in yards and points under him: https://www.nfl.com/news/ravens-offensive-coordinator-greg-roman-part-ways-after-six-seasons.↩︎

  33. CBS Sports, “Ravens hire Todd Monken as offensive coordinator: Georgia assistant returns to NFL to replace Greg Roman” (February 14, 2023): https://www.cbssports.com/nfl/news/ravens-hire-todd-monken-as-offensive-coordinator-georgia-assistant-returns-to-nfl-to-replace-greg-roman.↩︎

  34. This book’s calculation from nflverse FTN charting (qb_location: U, S or P), regular seasons 2022–2025, joined to play-by-play on game and play ID.↩︎

  35. NFL.com, “Ravens QB Lamar Jackson wins AP 2023 NFL Most Valuable Player award” (February 8, 2024; 49 of 50 first-place votes): https://www.nfl.com/news/ravens-qb-lamar-jackson-wins-ap-2023-nfl-most-valuable-player-award.↩︎

  36. This book’s calculation from nflverse play-by-play, regular seasons: designed runs by anyone other than a quarterback (a player with 20 or more dropbacks for that team that season), EPA per play by team and season.↩︎

  37. Running back carries and the halftime score are from nflverse play-by-play (game_id 2023_21_KC_BAL); the divisional-round score from nflverse schedules. The three turnovers, as the play-by-play describes them: Jackson sacked and stripped in the second quarter; Flowers’s fumble after a catch at the Kansas City 1, recovered for a touchback, on the first play of the fourth; and an interception in the end zone (intended for Isaiah Likely) later in the fourth.↩︎

  38. Sports Illustrated, “Ravens signing RB Derrick Henry to two-year deal” (March 12, 2024): https://www.si.com/nfl/2024/03/12/ravens-signing-rb-derrick-henry-to-two-year-deal. Henry’s 2,027 yards in 2020 and his listed weight (247 pounds on the Ravens’ 2024 roster; he was later listed at 252) are from “Derrick Henry,” Wikipedia: https://en.wikipedia.org/wiki/Derrick_Henry, and the Ravens’ roster page: https://www.baltimoreravens.com/team/players-roster/derrick-henry-x7588/.↩︎

  39. Henry’s 1,921 yards and 16 touchdowns and Jackson’s 4,172 yards, 41 touchdowns and 4 interceptions are confirmed in nflverse play-by-play, 2024 regular season; Barkley’s 2,005: NFL.com, https://www.nfl.com/news/saquon-barkley-becomes-ninth-player-to-rush-for-2000-yards-in-a-season. The team ranks (EPA per play, rushing yards, EPA per designed run) are this book’s calculation from the same data; the wild-card score and Henry’s 186 yards from nflverse schedules and play-by-play.↩︎

  40. Jamison Hensley, “How Derrick Henry could impact Ravens’ formation,” ESPN (March 21, 2024; Henry’s 5,795 yards with the quarterback under center since 2019, the most of any player; the Titans’ league-best 9.9 yards per play-action attempt since 2016; Lewan’s comments and Henry’s reply): https://www.espn.com/nfl/story/_/id/39770809/derrick-henry-ravens-lamar-jackson-line-formation.↩︎

  41. This book’s calculation from nflverse play-by-play and FTN charting, 2024–2025 regular seasons: Henry’s designed runs by the quarterback’s alignment. The pistol sample is small; read it as a direction, not a size.↩︎

  42. Score, Henry’s 26 carries for 186 yards, the designed-run share and the fact that Baltimore never trailed are from nflverse play-by-play and schedules (game_id 2024_19_PIT_BAL).↩︎

  43. NFL.com, “Lamar Jackson says health limited his mobility in 2025”: https://www.nfl.com/news/ravens-lamar-jackson-health-limited-mobility-2025 (the Week 4 hamstring injury at Kansas City); the Week 16 back injury against New England that kept him out at Green Bay: NFL.com, https://www.nfl.com/news/ravens-qb-lamar-jackson-back-doubtful-to-play-saturday-vs-packers. The missed games (Weeks 5, 6, 8 and 17; Week 7 was the bye) are from nflverse schedules (starting quarterbacks).↩︎

  44. Baltimore’s 2025 results (8–9; 424 points scored, 398 allowed; a 26–24 loss at Pittsburgh on January 4, 2026; Jackson’s starts) are from nflverse schedules. Pittsburgh won the AFC North at 10–7: see FACTS-current §2 and “2025–26 NFL playoffs,” Wikipedia: https://en.wikipedia.org/wiki/2025%E2%80%9326_NFL_playoffs. Harbaugh’s firing on January 6, 2026, after 18 seasons: “2026 NFL season,” Wikipedia: https://en.wikipedia.org/wiki/2026_NFL_season.↩︎

  45. CBS Sports, “Chargers fire offensive coordinator Greg Roman” (January 2026): https://www.cbssports.com/nfl/news/chargers-fire-offensive-coordinator-greg-roman-five-candidates-to-maximize-justin-herbert/.↩︎

  46. Pro Football Rumors, 2026 offensive and defensive coordinator search tracker (Doyle from Chicago to Baltimore; Weaver as defensive coordinator; Monken to Cleveland as head coach): https://www.profootballrumors.com/2026/02/2026-nfl-offensive-defensive-coordinator-search-tracker; head coaches (Minter to Baltimore, Harbaugh to the Giants): “2026 NFL season,” Wikipedia, cited above. Minter’s career (Ravens 2017–2020, defensive backs coach in the last of those seasons; Vanderbilt and Michigan defensive coordinator; Chargers defensive coordinator 2024–2025): “Jesse Minter,” Wikipedia: https://en.wikipedia.org/wiki/Jesse_Minter; WTOP/AP, https://wtop.com/baltimore-ravens/2026/01/baltimore-ravens-tab-chargers-defensive-coordinator-jesse-minter-as-new-head-coach/.↩︎

  47. NFL.com, “NFL trend watch: heavy personnel could shape Super Bowl LX showdown between Patriots and Seahawks” (Next Gen Stats, 2025 season: fewer than three wide receivers on 41.7% of plays): https://www.nfl.com/news/nfl-trend-watch-heavy-personnel-could-shape-super-bowl-lx-showdown-between-patriots-and-seahawks.↩︎

  48. This book’s calculation from nflverse play-by-play and participation data (Next Gen Stats box counts through 2022, FTN from 2023), regular seasons 2019–2025: EPA per designed run against six or fewer defenders in the box, Baltimore and the league; and Baltimore’s EPA per designed run and per dropback while trailing by more than seven points, Jackson-era regular seasons. The trailing comparison is not a controlled experiment: defenses with a lead play to stop the pass, which is part of why the runs do well.↩︎