The Game Plan: A Week Inside an NFL Building

Where Sunday’s plays come from: six days of film, meetings and practice, squeezed onto one laminated sheet

Show the Python (shared helpers for this chapter’s diagrams)
import os
import re
import shutil
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from matplotlib.patches import Circle, Ellipse, FancyArrowPatch, FancyBboxPatch, Rectangle, RegularPolygon, Patch
from gridiron import Play, Player, Field, technique, style, routes as r

PURPLE = style.SERIES[6]   # rings: the defenders a game plan moved
RED = style.SERIES[7]      # rings and badges: the matchup being hunted, or the bill for a choice
GREEN = "#0f7a55"          # darker aqua, for text and boxes on the light page


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


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


def line(labels=False):
    """Five offensive linemen; unlabeled except the center unless labels=True."""
    names = ["LT", "LG", "C", "RG", "RT"]
    return [O(k, k, -0.7, w, k if (labels or k == "C") else "")
            for k, w in zip(names, (-3.2, -1.6, 0.0, 1.6, 3.2))]


def trips_y():
    """11 personnel, shotgun 3x1: the tight end (Y) detached as the inside receiver of three on the right.
    Seven on the line: the five linemen, X and Z. Y and H are off the ball, R beside Q."""
    return line() + [O("QB", "QB", -5.0, 0.0, "Q"), O("RB", "RB", -5.0, -1.6, "R"),
                     O("X", "WR", -0.7, -15.0, "X"), O("Y", "TE", -1.5, 6.5, "Y"),
                     O("H", "WR", -1.5, 11.0, "H"), O("Z", "WR", -0.7, 16.0, "Z")]


def dl_nickel():
    """Four down linemen against a formation with no attached tight end."""
    return [D("LDE", "DE", 1.0, -5.0, "E"), D("LDT", "DT", 1.0, technique("1", -1), "T"),
            D("RDT", "DT", 1.0, technique("3", 1), "T"), D("RDE", "DE", 1.0, 5.0, "E")]


def their_usual():
    """The opponent's usual answer to 3x1: nickel Cover 1. The Will (W) has the tight end, the third
    receiver, in man; the Mike (M) has the back; the strong safety is the robber and the free safety is deep."""
    return dl_nickel() + [D("WILL", "WILL", 4.5, 3.4, "W"), D("MIKE", "MIKE", 4.5, -0.6, "M"),
                          D("NB", "NB", 5.0, 11.0, "$"), D("SS", "SS", 9.0, 1.5, "SS"),
                          D("FS", "FS", 13.5, 1.0, "FS"), D("LCB", "CB", 6.0, -15.0, "CB"),
                          D("RCB", "CB", 6.0, 16.0, "CB")]


def game_plan_dime():
    """The game-plan answer: dime. A sixth defensive back (D) replaces the Will and takes the tight end
    with outside leverage (the out-breaking routes and anything short); the strong safety sits inside
    and over him at nine yards (the in-breaking routes and anything deep): an in/out bracket. The free
    safety shades to the three-receiver side; the Mike takes the back. Nobody helps the cornerback on X."""
    return dl_nickel() + [D("MIKE", "MIKE", 4.5, -0.6, "M"), D("DIME", "DB", 2.5, 7.3, "D"),
                          D("NB", "NB", 5.0, 11.0, "$"), D("SS", "SS", 9.0, 4.6, "SS"),
                          D("FS", "FS", 13.5, 7.0, "FS"), D("LCB", "CB", 6.5, -15.0, "CB"),
                          D("RCB", "CB", 6.0, 16.0, "CB")]


def go_to(p, key, end, kind="path", delay=0.0, speed=None, via=()):
    """Move `key` through absolute (d, w) points `via` to `end`, as a block, path, run or route."""
    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)
    if kind == "route":
        return p.route(key, pts, frame="field", delay=delay, speed=speed)
    if kind == "rush":
        return p.rush(key, pts=pts, delay=delay, speed=speed)
    return {"path": p.path, "run": p.run}[kind](key, pts, delay=delay, speed=speed)


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


def say(fld, p, d, w, text, color=style.INK_2, fontsize=7.0, 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=11, fontweight=weight,
                linespacing=1.1, bbox=dict(boxstyle="round,pad=0.18", fc=style.SURFACE, ec="none", alpha=0.92))


def point_to(fld, p, text_at, target, text, color=style.INK_2, fontsize=6.8):
    """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=11, linespacing=1.1,
                    arrowprops=dict(arrowstyle="-", color=color, lw=0.7, shrinkA=2, shrinkB=7),
                    bbox=dict(boxstyle="round,pad=0.18", fc=style.SURFACE, ec="none", alpha=0.92))


def badge(ax, x, y, text, color=RED, fontsize=7.0, zorder=12):
    """A small numbered disc, in data coordinates, that ties a spot to a note elsewhere in the figure."""
    ax.text(x, y, text, fontsize=fontsize, color="white", ha="center", va="center", zorder=zorder,
            fontweight="bold", bbox=dict(boxstyle="circle,pad=0.18", fc=color, ec="white", lw=0.6))


def field_badge(fld, p, d, w, text, color=RED):
    x, y = _P(fld, p, d, w)
    badge(fld.ax, float(x), float(y), text, color=color)


def ring(fld, p, d, w, color, ls="-"):
    x, y = _P(fld, p, d, w)
    fld.ax.add_patch(Circle((float(x), float(y)), 1.15, fc="none", ec=color, lw=1.7, ls=ls, zorder=7))


def ghost(fld, p, pts, color=style.DEFENSE, lw=1.4, alpha=0.6, zorder=5):
    """A faint dashed arrow through absolute (d, w) points: what a player did (or will do) off the play."""
    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=(0, (3, 2)), alpha=alpha, zorder=zorder)
    fld.ax.add_patch(FancyArrowPatch((xs[-2], ys[-2]), (xs[-1], ys[-1]), arrowstyle="-|>", mutation_scale=10,
                                     color=color, lw=lw, alpha=alpha, ls=(0, (3, 2)), zorder=zorder))


def clean(ax):
    """Zoomed diagrams: drop hash ticks and yard numbers, and clip text to 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, ax=None, width_in=5.8, **kw):
    fld = p.draw(ax=ax, width_in=width_in, title=False, **kw)
    clean(fld.ax)
    return fld


def panel_title(ax, text, fontsize=8.6):
    ax.set_title(text, fontsize=fontsize, loc="left", color=style.INK, fontweight="bold", pad=3)


def blank(ax):
    ax.set_axis_off()
    ax.grid(False)


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


# ---- frame strips (print) and videos (web) for simulated plays
MARK_R = 0.6    # frame-strip marker radius in yards, smaller than the static diagrams'


def tracking(play):
    df = play.to_tracking(t_after=1.6)   # run past the last route so a late throw can land
    df["_id"] = df["displayName"].astype(str)
    return df


def panel(ax, play, df, t, lateral, window, rings=(), ball=1.0):
    """One moment of a simulated play: markers, trails of on-screen players, rings (ball = ball size factor)."""
    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")]
    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 _, rr in shown.iterrows():
        x, y = (float(v) for v in fld.xy(rr["x"], rr["y"]))
        if rr["side"] == "ball":
            ax.add_patch(Ellipse((x, y + 0.45 * ball), 0.7 * ball, 0.42 * ball, fc=style.BALL, ec="white",
                                 lw=0.5, zorder=9))
            continue
        lab = str(rr.get("label", "") or "")
        if rr["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 rings:
        rr = cur[cur["displayName"] == key].iloc[0]
        x, y = fld.xy(rr["x"], rr["y"])
        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, notes, lateral, window, ncols=2, rings=(), width_in=6.4, frame_rings=None):
    """Numbered, captioned frame strip: the PDF stand-in for the video.
    frame_rings: optional {frame index: ((key, color), ...)} drawn on that frame only, on top of `rings`."""
    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.55)))
    for ax in axes.flat[len(times):]:
        ax.axis("off")
    for i, (ax, t) in enumerate(zip(axes.flat, times)):
        panel(ax, play, df, t, lateral, window, tuple(rings) + tuple((frame_rings or {}).get(i, ())), ball=2.4)
        ax.set_title(f"{i + 1}.  " + ("snap" if abs(t) < 1e-6 else f"{t:+.1f} s"), fontsize=8.5,
                     color=style.INK_2, loc="left")
        ax.text(0.5, -0.03, notes[i], transform=ax.transAxes, ha="center", va="top", fontsize=7.5,
                color=style.INK, linespacing=1.15)
    fig.tight_layout(h_pad=1.6, w_pad=0.6)
    return fig


def video(play, times, notes, lateral, window, rings=(), width_in=5.6):
    """Web version of the strip: every frame, with the strip's notes as running captions."""
    from matplotlib import animation
    from IPython.display import HTML, display
    df = tracking(play)
    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.4))
    tmap = df.groupby("frameId")["time"].first()

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

    anim = animation.FuncAnimation(fig, update, frames=sorted(df["frameId"].unique()), 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 animation_or_strip(play, times, notes, lateral, window, rings=(), ncols=2, frame_rings=None):
    """Video on the website; a numbered, captioned frame strip in the PDF."""
    if pdf_build():
        strip(play, times, notes, lateral, window, ncols=ncols, rings=rings, frame_rings=frame_rings)
        plt.show()
    else:
        video(play, times, notes, lateral, window, rings=rings)


def pct(x, nd=0):
    return f"{100 * x:.{nd}f}%"
Show the Python (load the data: a 2025 tendency report, snap-by-snap efficiency 2022-2025, and targets by position)
from gridiron.data import pbp, nfl

# ---- 1. The tendency report: Seattle's 2025 defense against dropbacks, by situation
TR_COLS = ["season", "season_type", "game_id", "play_id", "posteam", "defteam", "play_type", "down", "ydstogo",
           "yardline_100", "qb_dropback", "epa"]
SITS = ["1st & 10", "2nd & 7+", "2nd & 1-6", "3rd & 1-3", "3rd & 4-6", "3rd & 7+", "Red zone"]
TWO_HIGH = ["COVER_2", "2_MAN", "COVER_4", "COVER_6"]
COV_NAME = {"COVER_0": "Cover 0", "COVER_1": "Cover 1", "COVER_2": "Cover 2", "COVER_3": "Cover 3",
            "COVER_4": "Cover 4", "COVER_6": "Cover 6", "2_MAN": "2-Man", "COVER_9": "Cover 9"}


def situation(down, togo, yl):
    if yl <= 20:
        return "Red zone"
    if down == 1:
        return "1st & 10" if togo == 10 else None
    if down == 2:
        return "2nd & 1-6" if togo <= 6 else "2nd & 7+"
    if down == 3:
        return "3rd & 1-3" if togo <= 3 else ("3rd & 4-6" if togo <= 6 else "3rd & 7+")
    return None


def dropbacks(season):
    """Regular-season dropbacks with the defense's charted rushers and coverage (participation file)."""
    d = pbp([season], columns=TR_COLS)
    d = d[(d["season_type"] == "REG") & d["play_type"].isin(["pass", "run"]) & (d["qb_dropback"] == 1)
          & d["down"].notna()].copy()
    part = nfl().load_participation([season]).to_pandas()[
        ["nflverse_game_id", "play_id", "number_of_pass_rushers", "defense_man_zone_type", "defense_coverage_type"]]
    d = d.merge(part, left_on=["game_id", "play_id"], right_on=["nflverse_game_id", "play_id"], how="left")
    d["sit"] = [situation(a, b, c) for a, b, c in zip(d["down"], d["ydstogo"], d["yardline_100"])]
    d = d[d["sit"].notna() & d["defense_coverage_type"].notna()
          & ~d["defense_coverage_type"].isin(["BLOWN", "COMBO"])]
    d["rush5"] = d["number_of_pass_rushers"] >= 5
    d["man"] = d["defense_man_zone_type"] == "MAN_COVERAGE"
    d["two_high"] = d["defense_coverage_type"].isin(TWO_HIGH)
    return d


def tendency_report(d, team=None):
    t = d[d["defteam"] == team] if team else d
    g = t.groupby("sit").agg(n=("epa", "size"), rush5=("rush5", "mean"), man=("man", "mean"),
                             two_high=("two_high", "mean"),
                             top=("defense_coverage_type", lambda s: COV_NAME.get(s.value_counts().index[0], "?")),
                             top_share=("defense_coverage_type", lambda s: s.value_counts(normalize=True).iloc[0]))
    return g.reindex(SITS)


DB25 = dropbacks(2025)
TR_SEA = tendency_report(DB25, "SEA")
TR_NFL = tendency_report(DB25)

# ---- 2. Does the opening script gain more? Efficiency by the offense's snap number, 2022-2025
SC_COLS = ["season", "season_type", "week", "game_id", "play_id", "posteam", "play_type", "down", "wp",
           "half_seconds_remaining", "pass", "epa", "success"]
SEASONS = [2022, 2023, 2024, 2025]
BINS = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 200]
BIN_LABELS = ["1-5", "6-10", "11-15", "16-20", "21-25", "26-30", "31-35", "36-40", "41-45", "46-50", "51+"]


def personnel_code(s):
    """'1 C, 2 G, 1 QB, 1 RB, 2 T, 1 TE, 3 WR' (or pre-2023 '1 RB, 1 TE, 3 WR') -> '11' (+OL if 6+ linemen)."""
    if not isinstance(s, str):
        return None
    n = {k: int(v) for v, k in re.findall(r"(\d+) ([A-Z]+)", s)}
    ol = sum(n.get(k, 0) for k in ("C", "G", "T", "OL")) or 5
    return f"{n.get('RB', 0) + n.get('FB', 0)}{n.get('TE', 0)}" + ("+OL" if ol > 5 else "")


def snaps(seasons):
    d = pbp(seasons, columns=SC_COLS)
    d = d[(d["season_type"] == "REG") & d["play_type"].isin(["pass", "run"]) & d["down"].notna()]
    d = d.sort_values(["game_id", "play_id"]).copy()
    d["snap_no"] = d.groupby(["game_id", "posteam"]).cumcount() + 1     # the offense's nth snap of the game
    part = nfl().load_participation(seasons).to_pandas()[["nflverse_game_id", "play_id", "offense_personnel"]]
    ftn = nfl().load_ftn_charting(seasons).to_pandas()[["nflverse_game_id", "nflverse_play_id", "qb_location",
                                                        "n_offense_backfield"]]
    d = d.merge(part, left_on=["game_id", "play_id"], right_on=["nflverse_game_id", "play_id"], how="left")
    d = d.merge(ftn, left_on=["game_id", "play_id"], right_on=["nflverse_game_id", "nflverse_play_id"], how="left")
    d["pers"] = d["offense_personnel"].map(personnel_code)
    # a "look" = personnel + QB alignment + number of backs; "new" = not used by this team in an earlier game
    ok = d["pers"].notna() & d["qb_location"].notna()
    d["look"] = np.where(ok, d["pers"] + "|" + d["qb_location"].astype(str) + "|"
                         + d["n_offense_backfield"].astype(str), None)
    first_week = d[ok].groupby(["season", "posteam", "look"])["week"].transform("min")
    d.loc[ok, "new_look"] = (d.loc[ok, "week"] == first_week).astype(float)
    d["game_no"] = d.groupby(["season", "posteam"])["week"].rank(method="dense")
    d["neutral"] = d["wp"].between(0.2, 0.8) & (d["half_seconds_remaining"] > 120)
    d["bin"] = pd.cut(d["snap_no"], BINS, labels=BIN_LABELS)
    return d


SN = snaps(SEASONS)
NEU = SN[SN["neutral"]]
BY_BIN = NEU.groupby("bin", observed=True)["epa"].agg(["mean", "std", "size"])
BY_BIN["se"] = BY_BIN["std"] / np.sqrt(BY_BIN["size"])
NL = NEU[(NEU["game_no"] >= 5) & NEU["new_look"].notna()]            # from each team's 5th game on
NEW_BIN = NL.groupby("bin", observed=True)["new_look"].agg(["mean", "size"])
NEW_BIN["se"] = np.sqrt(NEW_BIN["mean"] * (1 - NEW_BIN["mean"]) / NEW_BIN["size"])
FIRST15 = NEU["snap_no"] <= 15
EPA15, EPA_REST = NEU.loc[FIRST15, "epa"].mean(), NEU.loc[~FIRST15, "epa"].mean()
SR15, SR_REST = NEU.loc[FIRST15, "success"].mean(), NEU.loc[~FIRST15, "success"].mean()
NEW15 = NL.loc[NL["snap_no"] <= 15, "new_look"].mean()
NEW_MID = NL.loc[NL["snap_no"].between(16, 45), "new_look"].mean()
NEW_LATE = NL.loc[NL["snap_no"] > 45, "new_look"].mean()
# the same test on first and second downs only (a script's downs)
E12 = NEU[NEU["down"].isin([1, 2])]
EPA15_12, EPA_REST_12 = E12.loc[E12["snap_no"] <= 15, "epa"].mean(), E12.loc[E12["snap_no"] > 15, "epa"].mean()
SNAPS_PG = SN[SN["season"] == 2025].groupby(["game_id", "posteam"]).size().mean()   # offensive plays per game


# ---- 3. Matchup hunting in data: EPA per target allowed by each defense, by the target's position
def targets(season):
    d = pbp([season], columns=["season", "season_type", "defteam", "play_type", "epa", "receiver_player_id"])
    d = d[(d["season_type"] == "REG") & (d["play_type"] == "pass") & d["receiver_player_id"].notna()]
    ros = nfl().load_rosters([season]).to_pandas()[["gsis_id", "position"]].drop_duplicates("gsis_id")
    d = d.merge(ros, left_on="receiver_player_id", right_on="gsis_id", how="left")
    return d[d["position"].isin(["WR", "TE", "RB"])]


def allowed(season):
    t = targets(season)
    g = t.groupby(["defteam", "position"])["epa"].agg(["mean", "std", "size"])
    lg = t.groupby("position")["epa"].mean()
    g["rel"] = g["mean"] - g.index.get_level_values("position").map(lg).values
    g["se"] = g["std"] / np.sqrt(g["size"])
    return g, lg


# ---- 4. Scouting a play-caller: early-down pass rate over expected, Drew Petzing's Arizona offenses 2023-2025
PR = pbp([2023, 2024, 2025], columns=["season", "season_type", "posteam", "play_type", "down", "wp",
                                      "half_seconds_remaining", "pass_oe"])
PR = PR[(PR["season_type"] == "REG") & PR["play_type"].isin(["pass", "run"]) & PR["down"].isin([1, 2])
        & PR["wp"].between(0.2, 0.8) & (PR["half_seconds_remaining"] > 120) & PR["pass_oe"].notna()]
PROE = PR.groupby(["season", "posteam"])["pass_oe"].mean().unstack(0)      # percentage points
PROE_RANK = PROE.rank(ascending=False).astype(int)


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


AL25, LG25 = allowed(2025)
AL24, _ = allowed(2024)
TE25 = AL25.xs("TE", level="position").sort_values("rel")
TE_YOY = TE25["rel"].corr(AL24.xs("TE", level="position")["rel"].reindex(TE25.index))
WR_YOY = AL25.xs("WR", level="position")["rel"].corr(AL24.xs("WR", level="position")["rel"])

Sunday, 1:03 in the afternoon, first play of the game. The offense comes out in a formation you have not seen from it all season: two tight ends, both standing out where wide receivers usually stand, and a lone back behind the quarterback. The defense, caught in its heavy, run-stopping personnel, sends two linebackers trotting out to cover them. The ball goes to the tight end on the right for 11 yards. On the second play the offense is back in a normal look and runs the ball for three. “That first one was a script play,” says the analyst. “They drew that up on Tuesday.” Tuesday? Who decided, five days before kickoff, what the first play would be? Why that formation, why that tight end, why against those linebackers? And if they knew it would work, why not run it again on the second snap? This chapter is about the six days you don’t see, in which a staff takes one opponent apart and puts its pieces on a sheet of laminated paper.

You’ll be able to:

NoteYou’ll need

How to read the diagrams. Offense is blue circles, defense orange squares, and left and right are always the offense’s. Q is the quarterback, R the running back, Y and U the tight ends, X and Z the outside receivers and H a slot receiver; C is the center. On defense, E and T are the ends and tackles, S, M and W the Sam, Mike and Will linebackers, $ the nickel back, D a sixth defensive back (a “dime” back), CB the cornerbacks and FS and SS the safeties. Dotted lines join a man-coverage defender to his receiver. A red ring marks the matchup a plan is hunting (or a player who pays the bill for a choice), and a purple ring a defender the game plan moved.

What a game plan is, and why there has to be one

Start with arithmetic. An NFL playbook holds hundreds of plays once you count every formation, motion and protection they can be run from. A team gets three real practices a week during the season, and only about one practice a week in pads: fourteen padded practices in the whole regular season.1 A player can learn and rehearse only so many new things in three days, and every snap of practice spent on one play is a snap not spent on another. So each week a staff has to choose, and what it chooses is the game plan: the slice of the playbook picked for one opponent, plus the rules for using it. Which runs and passes, from which formations, in which situations. Which coverages and pressures. Which of the opponent’s players to attack, and which to avoid.

What a Play Really Is drew the funnel: playbook, then game plan, then call sheet. This chapter is the middle step, and it is most of a coach’s working life. A game plan answers four questions, and the rest of the chapter takes them one at a time.

  1. What do they do? The opponent’s habits, by situation. That’s the tendency report, built by the quality control staff from film.
  2. What do they do best, and who does it? The thing the opponent most needs to work: a run, a receiver, a pass rusher, a blitz. The plan either takes it away or protects against it.
  3. Where can we win? The matchups: which of our players should beat which of theirs, and how to arrange the formation so those pairings happen.
  4. What do they think we’ll do? The opponent has scouted us. The plan includes which of our own tendencies to keep, hide or break (the self-scout from Constraint Theory).
ImportantWhy it exists

A game plan exists because time is scarce and every opponent is different. Practicing everything means practicing nothing well; practicing the same thing every week means the opponent knows what’s coming. The plan is the compromise: a stable core of plays the team runs every week (so they are sharp) dressed and aimed differently for each opponent (so they are hard to scout). The trade is real. Every play in the plan gets practice time and can be called with confidence; every play left out is unavailable on Sunday, however well it might have worked. A coach who loves a play that isn’t in this week’s plan can’t call it. That is why what’s left out of a game plan matters as much as what goes in.

NoteMadden vs. real life

In a video game, every play in the book is available on every snap, and your players execute a play you’ve never called as well as one you call every week. Neither is true in real life. A play-caller chooses from a few dozen calls per situation that his players rehearsed on Wednesday, Thursday and Friday, and a play the players haven’t practiced this week is a play somebody is likely to get wrong. The coach’s real menu is not the playbook. It’s the practice schedule.

The game week, day by day

Almost every NFL staff runs some version of the same week for a Sunday game, and the logic of its order explains most of it. Figure 44.1 lays it out, with the four groups of people who do the work.

Show the Python
FILM, PLAN, PRAC, REST, GAME = "#ecebe5", style.OFFENSE_TINT, "#d9efe4", "#ffffff", style.DEFENSE_TINT
DAYS = [("Mon", "day after\nthe game"), ("Tue", "game-plan\nday"), ("Wed", "1st & 2nd\ndown"),
        ("Thu", "3rd down"), ("Fri", "red zone,\n2-minute"), ("Sat", "walk-through,\ntravel"), ("Sun", "game")]
LANES = [
    ("Coordinators\n& position\ncoaches", [
        (FILM, "grade last\ngame; then\nopponent\nfilm"),
        (PLAN, "1st & 2nd\ndown plan;\nscript draft;\nlate night"),
        (PRAC, "teach it;\nthen build\n3rd-down\nplan"),
        (PRAC, "teach 3rd\ndown; build\nred-zone\nplan"),
        (PLAN, "2-minute;\nspecial\nteams; set\nthe sheet\nand script"),
        (PRAC, "walk-\nthrough;\ngame-\nmanagement\nmeeting"),
        (GAME, "call the\ngame from\nthe sheet")]),
    ("Quality\ncontrol\ncoaches", [
        (FILM, "finish the\nbreakdown\n(begun\nweeks ago)"),
        (FILM, "tendency\nreport;\ncut-ups"),
        (FILM, "practice\nfilm; 3rd-\ndown and\nblitz report"),
        (FILM, "red-zone\nreport;\nscout-team\ncards"),
        (PLAN, "print and\nproof the\ncall sheet"),
        (FILM, "their past\nopening\nscripts"),
        (GAME, "booth:\nchart them,\ntell the\nsideline")]),
    ("Analytics\nstaff", [
        (FILM, "last game:\nefficiency,\n4th downs"),
        (PLAN, "opponent\nmodel;\nmatchup\nnumbers"),
        (PLAN, "4th-down,\n2-point\nchart"),
        (PLAN, "red-zone,\nshort-\nyardage\nnumbers"),
        (PLAN, "decision\ncard to the\nhead coach"),
        (REST, "weather;\nlast checks"),
        (GAME, "headset:\n4th downs,\ntimeouts,\nchallenges")]),
    ("Players", [
        (FILM, "treatment,\nlift, last\ngame's film"),
        (REST, "day off\n(film on\nthe tablet)"),
        (PRAC, "INSTALL 1:\n1st & 2nd\ndown"),
        (PRAC, "INSTALL 2:\n3rd down,\nblitz\npickup"),
        (PRAC, "INSTALL 3:\nred zone,\n2-minute"),
        (PRAC, "walk-\nthrough,\ntravel,\nmeetings"),
        (GAME, "GAME")]),
]
fig, ax = plt.subplots(figsize=(6.6, 5.6))
blank(ax); ax.set_xlim(0, 100); ax.set_ylim(-3.5, 66)
X0, CW = 15.0, (100 - 15.0) / 7
LH, Y_TOP = 13.0, 55.0
for j, (day, sub) in enumerate(DAYS):
    xc = X0 + CW * (j + 0.5)
    ax.text(xc, 63.4, day, ha="center", va="center", fontsize=9.0, fontweight="bold", color=style.INK)
    ax.text(xc, 59.0, sub, ha="center", va="center", fontsize=6.4, color=style.INK_2, linespacing=1.05)
for i, (lane, cells) in enumerate(LANES):
    yc = Y_TOP - LH * (i + 0.5)
    ax.text(0.4, yc, lane, ha="left", va="center", fontsize=6.9, fontweight="bold", color=style.INK,
            linespacing=1.05)
    for j, (fc, txt) in enumerate(cells):
        x0 = X0 + CW * j
        ax.add_patch(FancyBboxPatch((x0 + 0.45, yc - LH / 2 + 0.45), CW - 0.9, LH - 0.9,
                                    boxstyle="round,pad=0,rounding_size=0.8", fc=fc, ec=style.BASELINE, lw=0.6))
        ax.text(x0 + CW / 2, yc, txt, ha="center", va="center", fontsize=6.3, color=style.INK, linespacing=1.08)
# the advance work that starts before the week: an arrow into Monday's QC cell
yq = Y_TOP - LH * 1.5
ax.annotate("", xy=(X0 + 0.6, yq), xytext=(X0 - 2.4, yq),
            arrowprops=dict(arrowstyle="-|>", color=style.INK_2, lw=0.9))
h = [Patch(fc=FILM, ec=style.BASELINE, label="film and breakdown"), Patch(fc=PLAN, ec=style.BASELINE, label="planning"),
     Patch(fc=PRAC, ec=style.BASELINE, label="teaching and practice"), Patch(fc=REST, ec=style.BASELINE, label="off / light"),
     Patch(fc=GAME, ec=style.BASELINE, label="game day")]
ax.legend(handles=h, loc="lower center", bbox_to_anchor=(0.5, -0.02), ncol=5, fontsize=6.6, frameon=False,
          handlelength=1.4, columnspacing=0.9)
plt.show()
Figure 44.1: A typical game week for a Sunday game (illustrative; every staff varies the details). Read across for one group’s week and down for one day. The installs move from the most common situations to the rarest: first and second down on Wednesday, third down on Thursday, red zone and two-minute on Friday, when the special-teams plan is also finished. The quality control coaches start the opponent a week or more before anyone else, and the analytics staff’s work arrives as decisions made in advance (fourth downs, two-point tries) rather than plays. On a short week before a Thursday game the same work is squeezed into three days, mostly as meetings and walk-throughs.

Monday: close the last game, open the next one. The morning after a game belongs to the last game. Coaches grade every player on every snap, the players come in for treatment, a lift and a film session on what went wrong, and the staff decides what to fix. By the afternoon the coordinators have turned to the next opponent, and they are not starting from scratch. The quality control coaches have been breaking that opponent down for a week or more (more on that below), so the coordinators spend the day watching film with a stack of reports already beside them.

Tuesday: the coaches’ day. At most teams Tuesday is the players’ day off, and the longest day of the week for the coaches.2 This is when the first-and-second-down game plan gets built: which runs, which passes, from which formations, against which of the opponent’s fronts and coverages. It’s when the first draft of the opening script appears, and when the plays the players will see on Wednesday morning are drawn, named and loaded onto their tablets. Some coordinators sleep at the facility. John Wolford, then a Rams quarterback, described getting the week’s play calls on Tuesday night, “though oftentimes we do not get these until Wednesday morning.”3

Wednesday: install one, first and second down. The first practice day teaches the plays that will be called most. First and second down are roughly three of every four snaps an offense runs, so they get the first meetings, the first walk-through and the most practice repetitions. The defense mirrors it: its base fronts and coverages against this opponent’s base runs and passes. A scout team of backups runs the opponent’s plays, drawn from the quality control coaches’ cards, so the starters see what they’ll see on Sunday. A scout card is a drawing of one opponent play on a sheet of poster board: the scout quarterback holds it up in the huddle, each backup finds his assignment on it, and they line up and run it.

Thursday: install two, third down. Third down is its own game (see Third Down): the defense’s pressures and disguises come out, and the offense needs answers to each. Thursday adds the third-down plan, the protections against the opponent’s blitzes, and usually some changes to Wednesday’s plan after the staff has watched Wednesday’s practice film. A quarterback calls it the hardest day of the week: “Bodies are sore from yesterday, practice is more intense, and more information is thrown at you.”

Friday: install three, the rare situations. Red zone, goal line, two-minute drill and the four-minute offense (running out the clock with a lead) come last, in a shorter practice. These are the situations with the fewest snaps and the highest stakes per snap. The call sheet is finished, and the script is set. The third phase gets its plan too: the special-teams coordinator’s return and coverage schemes for this opponent’s kicker and punter, and any fake or punt-block chance he found on film.

Saturday: walk-through and travel. A light, helmets-only rehearsal, special situations, travel for road games, and meetings at the team hotel at night, where the players often walk through the opening script. Many head coaches also hold a game-management meeting late in the week: timeouts, challenges, the end of each half and the analytics staff’s decision card, so that the clock decisions on Sunday are rehearsed rather than improvised.

Sunday: the sheet. Everything above is now a few hundred calls on one or two laminated pages in the play-caller’s hand, plus a booth full of people charting what the opponent actually does. Only a fraction of those calls get used: in 2025 an offense ran about 60 plays a game (runs and passes, not counting kicks or plays wiped out by penalties), and many of those were the same favorite calls more than once.4 The sheet is big because nobody knows in advance which sixty will be needed.

Who decides? The coordinators build their sides of the plan and usually call them on Sunday, but the head coach signs off on the whole thing, and on the decisions that belong to the whole team (fourth downs, two-point tries, timeouts, how aggressive to be) he has the final word. A head coach who calls one side himself, as several do, is also that side’s coordinator in all but name.

ImportantWhy it exists: why the installs go in that order

The week runs from the most common situations to the rarest. First and second down get Wednesday, the freshest day with the most practice time, because they’re most of the game; red zone and two-minute get Friday’s short practice because they are a small share of snaps. The defense has the same calendar, for the same reason. The trade is plain: the situations that decide close games (a fourth-and-goal, the last two minutes) get the least rehearsal. Coaches cover that gap by keeping their red-zone and two-minute plans small and familiar, built from the same few concepts week after week, with a new wrinkle or two for the opponent.

Two weeks break the pattern. A short week before a Thursday game compresses everything into about three days. The players come in sore on Monday, there is no time for a real practice in pads, and the install happens mostly in meetings and walk-throughs, so game plans on Thursday nights are usually smaller and simpler. A bye week goes the other way: the players get several days off, and the staff turns its film study on itself. That’s when most teams do the formal self-scout from Constraint Theory, and it is often when they plan the tendency breakers for the rest of the season.

Film first: the breakdown and the tendency report

Every game plan starts with film, and the film is broken down long before the coordinators see it. The quality control coaches (QC coaches) from What a Play Really Is take the opponent’s recent games (often the last four or five, plus any game against an offense or defense like their own, and last season’s meeting if the coordinator hasn’t changed) on coaches film, and tag every snap. A tag is a row in a database: down, distance, yard line, hash, quarter, score, personnel, formation, motion, the play (run direction, pass concept, protection), the defense’s front, coverage and pressure, and the result. A season of one team’s snaps is a thousand-plus rows, and a QC coach may tag several opponents at once, a week or two ahead.

Those rows become the tendency report: the written breakdown of what the opponent does in each situation, personnel group and formation. On offense it reads like “from 12 personnel, under center, on first down: run 68%, mostly outside zone to the tight end side; play-action off it when the strong safety walks down.” On defense it reads like “third-and-4 to 6: man coverage half the time, five-man pressure from the slot when we’re in empty.” The report comes with cut-ups: video playlists of every snap in one category (“all their third-and-medium blitzes,” “every red-zone snap against 3x1”) so a coordinator can watch the twenty plays behind a number rather than trust the number alone.

You can build a simple version of one yourself. Figure 44.2 is what a defensive tendency report’s first page might look like if it were built only from public charting data: the defense that won Super Bowl LX, Mike Macdonald’s 2025 Seahawks, as the Patriots’ staff might have summarized it before that game.5 This is not their report. It’s a reconstruction, and a real one would be much finer grained.

Show the Python
COLS = [("rush5", "5+ rushers"), ("man", "man coverage"), ("two_high", "two-deep shell")]
cmap = LinearSegmentedColormap.from_list("vs_lg", [style.SERIES[1], style.SURFACE, style.SERIES[0]])
fig, ax = plt.subplots(figsize=(6.6, 3.7))
fig.subplots_adjust(left=0.005, right=0.995, top=0.995, bottom=0.005)
blank(ax); ax.set_xlim(0, 100); ax.set_ylim(0, 9.4)
ax.add_patch(Rectangle((0, 0), 100, 9.4, fc="#fbfaf4", ec=style.BASELINE, lw=1.0))
ax.text(1.5, 8.95, "OPPONENT TENDENCIES  ·  SEATTLE DEFENSE vs. DROPBACKS  ·  2025 REGULAR SEASON",
        fontsize=7.4, fontweight="bold", color=style.INK, va="center")
ax.text(1.5, 8.45, "(rebuilt from public charting data; a staff's report splits further by personnel, formation, hash and opponent)",
        fontsize=5.9, color=style.INK_2, va="center")
CX = {"n": 24.0, "rush5": 38.0, "man": 52.0, "two_high": 66.0, "top": 86.0}
ax.text(1.5, 7.7, "Situation", fontsize=6.9, fontweight="bold", color=style.INK, va="center")
for key, h_ in (("n", "Dropbacks"), ("rush5", "5+ rushers"), ("man", "Man coverage"), ("two_high", "Two-deep shell"),
                ("top", "Favorite coverage")):
    ax.text(CX[key], 7.7, h_, fontsize=6.9, fontweight="bold", color=style.INK, va="center", ha="center")
for i, s in enumerate(SITS):
    y = 6.85 - i * 0.98
    ax.add_patch(Rectangle((0.8, y - 0.45), 98.4, 0.9, fc="white" if i % 2 else "#f6f5ef", ec="none"))
    ax.text(1.5, y, s, fontsize=7.4, color=style.INK, va="center", fontweight="bold")
    ax.text(CX["n"], y, f"{int(TR_SEA.loc[s, 'n'])}", fontsize=7.4, color=style.INK, va="center", ha="center")
    for col, _ in COLS:
        v, lg = TR_SEA.loc[s, col], TR_NFL.loc[s, col]
        x = CX[col]
        ax.add_patch(FancyBboxPatch((x - 5.6, y - 0.4), 11.2, 0.8, boxstyle="round,pad=0,rounding_size=0.25",
                                    fc=cmap(0.5 + np.clip(v - lg, -0.2, 0.2) * 2.5), ec="none"))
        ax.text(x, y + 0.1, pct(v), fontsize=7.8, fontweight="bold", color=style.INK, ha="center", va="center")
        ax.text(x, y - 0.24, f"NFL {pct(lg)}", fontsize=5.4, color=style.INK_2, ha="center", va="center")
    ax.text(CX["top"], y + 0.1, TR_SEA.loc[s, "top"], fontsize=7.4, color=style.INK, ha="center", va="center")
    ax.text(CX["top"], y - 0.24, f"{pct(TR_SEA.loc[s, 'top_share'])} of snaps (NFL: {TR_NFL.loc[s, 'top']})",
            fontsize=5.3, color=style.INK_2, ha="center", va="center")
plt.show()
Figure 44.2: A tendency report’s first page, rebuilt from public data: how the 2025 Seattle Seahawks’ defense played opposing dropbacks in each situation of the regular season, with the league-wide rate beneath each number. Blue cells are well above the league rate, orange well below, pale about the same (the colors mean higher and lower, not good or bad for either side). Four things jump out: two-deep shells on two first downs in three (league: under half), with Cover 2 the favorite; very few five-man pressures on second-and-long and third-and-medium; more pressure than average on third-and-long; and, on that same third-and-long, much less man coverage than the league. Coverage and rusher counts are charted labels, not tracking; some cells rest on fewer than 50 snaps. Data: nflverse play-by-play and participation (FTN charting) via nflreadpy; course calculation.

Read it the way an offensive coordinator would, one row at a time, asking “so what do I call?”

  • First and 10: Seattle played a two-deep shell on 64% of first-down dropbacks, against 45% for the league, and its single most common call was Cover 2 (the league’s was Cover 3). Two deep safeties mean fewer defenders near the line, so the first-down plan leans on runs and on the throws that beat two-high shells: the smash and dagger family from What Beats What.
  • Second-and-long and third-and-4 to 6: five or more rushers on only 14% and 11% of dropbacks, well under the league’s 22% and 28%. Seattle was content to rush four and cover. So the third-and-medium plan can lean on routes that take a beat longer to develop, because the extra rusher usually isn’t coming. But four rushers isn’t the same as a plain four-man rush. Macdonald’s defense is built on simulated pressure (a linebacker or defensive back rushes while a lineman drops into coverage; see Zone Blitzes and Simulated Pressure), and this column can’t tell the two apart. The protection still has to find the fourth rusher, and the hot throw still has to be ready.
  • Third-and-medium: man coverage on about half the snaps, the same as the rest of the league (third-and-short ran a little higher, on a handful of snaps). A tendency only matters where a team differs from what everyone does. Here the report says “expect man about half the time, like everyone else,” so the plan carries a man beater and a zone beater in every third-down box (you’ll see that pairing on the call sheet below).
  • Third-and-long: more five-man pressure than average (37%), and the loudest cell on the page: man coverage on only 19% of those snaps, against 30% for the league. Put the two together and Seattle’s third-and-long answer is pressure with zone behind it, the fire zone family from Zone Blitzes and Simulated Pressure: five come, and the coverage behind them sits in zones with a hole where a rusher left. So the call is not a man beater that needs time. It’s a quick zone beater thrown into the vacated spot, a screen that lets the rush run past, or a deeper zone beater only if the protection can pick up all five. That’s why the call sheet’s third-and-long box carries a “vs 5+” line.

Three cautions keep a report honest, and good staffs say them out loud. Sample size: the third-and-short row rests on 26 snaps, and a couple of plays either way would move it ten points. Context: a defense’s numbers depend on whom it played; a coverage rate built against pass-happy opponents is not the same as one built against run-first teams. Coding: these are charters’ labels, and different charting services code the same snap differently (in the public data, for example, the league’s man-coverage share jumps from season to season in a way that looks like a change in how snaps were charted, not in how teams played). The coverage column is also what the defense played after the snap. The tendency a staff would most like to know, how often a two-deep look before the snap turns into one deep safety after it (the disguise of Disguise, Rotation and Split-Field), isn’t in this table at all; the QC coaches chart it by hand. That is why the cut-ups come with the report. The number tells the coordinator where to look. The film tells him what he’s looking at.

CautionCommon misconception

“The tendency report tells you what they’re going to call.” It tells you what they have called, in situations like this, against opponents like the ones they’ve faced. The opponent knows its own report too: it ran a self-scout over its bye, and its coordinator may break the tendency on purpose this week, in this game, because he knows you have the report. A report shifts the odds; it doesn’t read minds. Its best use is to prepare the players for the likeliest looks so they recognize them instantly, while the plan keeps an answer ready for the tendency breaker.

Who builds what: QC coaches and the analytics staff

The staff map in What a Play Really Is showed where people sit on game day. The game week shows what they produce, and the two jobs at the bottom of that map do very different work.

The quality control coaches produce the descriptive layer: what the opponent did, tagged and counted. They build the tendency report, the cut-ups, the scout-team cards that show the backups what to run in practice, and often the first draft of the call sheet’s formatting. It is detail work at enormous volume, which is why it’s the traditional first job for a young coach, and why so many head coaches started there.

The analytics staff produces the decision layer: numbers that tell the staff what to do, not just what happened. Three of its products show up in a game week:

  • The fourth-down and two-point chart. Before the game, the analysts work out (for this opponent, this stadium, this kicker, the expected weather) when the numbers favor going for it on fourth down and when to try for two. The head coach sees the chart on Wednesday or Thursday, argues with it, and carries a version of it on Sunday, so that the decision on fourth-and-2 at the opponent’s 38 was mostly made calmly three days earlier. The math behind it is in Win Probability and Fourth Downs.
  • Efficiency splits. Where is this opponent good and bad once you adjust for situation: by down, by field zone, by kind of play, by the position of the player being targeted. You’ll see one of these in the matchup section.
  • Opponent models. How often this offense passes in each situation compared with an average team (the pass-rate-over-expected idea in Tendencies, PROE and Charting), which turns “they like to throw” into a number the defensive coordinator can plan around.

The two jobs overlap more every year: QC coaches use data tools, and analysts watch film. The difference that remains is in the question each answers. QC asks what did they do? Analytics asks what should we do about it?

Scouting the play-caller and the quarterback

A tendency report describes a team. A good game plan also scouts two people: the man calling the opponent’s plays and the man taking the snaps. Each has habits that travel with him.

The play-caller. A coordinator’s habits survive from week to week and often from team to team. Staffs study questions like these:

  • What does he call when he needs it? His favorite third-and-medium concept, the pressure he trusts on a big third down, his go-to red-zone call. Under stress, play-callers fall back on what they believe in, which makes the biggest snaps the most predictable ones.
  • What does he call after something happens? After a timeout, after a turnover, on the first play after halftime, after a big play against him. These “reset” moments are when coordinators reach for prepared calls, and the calls show up on film.
  • How does he script? His opening scripts from previous games show what he likes to test early: motions to find man coverage, a quick shot downfield, a new formation. A defense that knows his early habits can plan its own answers for the first two series.
  • Who actually calls the plays? The title can mislead. In 2026 Kyle Shanahan (49ers), Matt LaFleur (Packers) and Kevin O’Connell (Vikings) call their offenses as head coaches, and the Jets’ Aaron Glenn calls his defense, so the coordinator’s name on the depth chart is not the man to scout.6

When a play-caller changes teams, the staff simply follows him to his old film. A defense facing the 2026 Lions studies Drew Petzing’s Arizona offenses as well as Detroit’s; an offense facing the 2026 Ravens watches the Miami defenses that Anthony Weaver called, and the Chargers defenses that Jesse Minter coordinated before he became Baltimore’s head coach. That last one is the “who actually calls it” question again: with a defensive head coach and a defensive coordinator who have both run defenses elsewhere, whose habits Baltimore’s defense will show is itself a scouting question until the 2026 film answers it. The players are different, but a coordinator’s sequencing, his favorite pressures and his third-down habits usually aren’t.

Here’s what that looks like done with public data, and why the film still matters. Petzing called Arizona’s offense from 2023 through 2025.7 Measure how often his offenses threw on early downs in close games, compared with what an average team would have done in the same situations (pass rate over expected, from 13-04), and you get -7 percentage points in 2023 (29th in the league), +2 in 2024 and +8 in 2025 (1st).8 Same play-caller, opposite ends of the league. The season averages followed Arizona’s roster and its quarterback situation, not Petzing’s character, so a defense preparing for his Detroit offense learns little from them. What travels is smaller and lives on film: what he calls after a timeout, how he likes to open a second half, which concept he trusts on third-and-5. That is why the QC coaches cut up his Arizona third downs rather than quoting his pass rate.

The same problem makes Week 1 the hardest game to plan for: a new coordinator, new players and an off-season of changes, with nothing yet on film from this team in this system, and preseason games in which starters barely play and nobody shows his real plan. So staffs plan Week 1 from the coordinator’s old film and last season’s tape, keep their own plan simpler than usual, and expect to learn the real answers in the first quarter, which puts extra weight on the opening script.

Scouting has limits, and the line is clear: film, public data and what a team shows on the field are fair game; spying on the opponent is not. The league bans videotaping an opponent’s signals from the sideline or booth, and when the Patriots were caught taping the Jets’ defensive signals in Week 1 of 2007 (“Spygate”), the league fined Bill Belichick $500,000 and the club $250,000 and took away its 2008 first-round pick.9

The quarterback. The opposing quarterback is studied like a coverage. A defensive staff wants to know:

  • Where does he go under pressure? Does he climb the pocket or drift back, escape to his right or his left, throw it away or take the sack?
  • How does he handle disguise? Does he hold the ball when the safeties rotate late (the disguise from Disguise, Rotation and Split-Field), or throw on rhythm to where the coverage was?
  • What’s his favorite answer? The receiver he looks for on third down, the throw he trusts against the blitz, the throws he avoids (some quarterbacks rarely throw the deep out to the far side of the field).
  • What does he change at the line? Which checks he’s allowed, and what he checks to against which looks. If he always checks to a run against two-deep safeties, the defense can show two deep and play one.

The answers become the defense’s rules for the week: “make him hold it” (disguise late, cover longer), “keep him in the pocket” (rushers stay in their lanes rather than looping inside), “make him throw left,” “take away his first read on third down and see if he gets to his second.” An offense does the same to the defense’s best pass rusher: which side he rushes from, what move he wins with, whether he drops into coverage on pressures, and therefore whether the plan needs a tight end or a back to help block him, a protection that slides toward him, or quick throws that get the ball out before he arrives.

Matchup hunting

Football is eleven against eleven, but it’s decided one pairing at a time. A matchup is the pairing of one offensive player with the defender responsible for him, and a mismatch is a pairing one side should win clearly: a fast tight end covered by a slower linebacker, a big receiver against a short cornerback, a running back out of the backfield against a linebacker who can’t turn and run. Matchup hunting is the offense’s half of the game plan that asks where can we win? and then arranges formations, motions and personnel so that those pairings actually happen on the field.

Staffs build the answer as a grid: every one of their receivers against every defender who might cover him, graded from scouting (speed, size, how each defender has fared on film against players like ours). Figure 44.3 is a made-up example for a sample game.

Show the Python
ROWS = [("X", "outside WR,\ndeep speed"), ("Z", "outside WR,\nbig, physical"), ("H", "slot WR,\nquick"),
        ("Y", "tight end,\nbest receiver"), ("R", "running back,\ngood hands")]
COLS_D = [("CB", "2nd\ncorner"), ("CB", "star\ncorner"), ("$", "rookie\nnickel"), ("SS", "box\nsafety"),
          ("M", "Mike\nLB"), ("W", "Will\nLB")]
G = np.array([[1.0, -1.5, 1.0, 1.5, np.nan, np.nan],
              [0.5, -1.0, 0.0, 0.5, np.nan, np.nan],
              [0.5, -0.5, 1.0, 1.0, np.nan, np.nan],
              [0.0, -0.5, 0.5, 0.0, 1.5, 2.0],
              [np.nan, np.nan, 0.0, 0.5, 1.5, 1.0]])
HUNT = [(3, 5), (4, 4), (2, 2)]
AVOID = [(0, 1)]
mcmap = LinearSegmentedColormap.from_list("matchup", [style.SERIES[1], "#f3f2ee", style.SERIES[0]])
fig = plt.figure(figsize=(6.6, 4.1))
ax = fig.add_axes([0.20, 0.04, 0.44, 0.66])
for i in range(G.shape[0]):
    for j in range(G.shape[1]):
        v = G[i, j]
        if np.isnan(v):
            ax.add_patch(Rectangle((j, i), 1, 1, fc=style.GRID, ec=style.SURFACE, lw=2))
            ax.text(j + 0.5, i + 0.5, "n/a", ha="center", va="center", fontsize=6.4, color=style.MUTED)
        else:
            ax.add_patch(Rectangle((j, i), 1, 1, fc=mcmap((v + 2) / 4), ec=style.SURFACE, lw=2))
            ax.text(j + 0.5, i + 0.5, f"{v:+.1f}" if v else "0", ha="center", va="center", fontsize=8.4,
                    fontweight="bold", color=style.INK)
for (i, j) in HUNT:
    ax.add_patch(Rectangle((j + 0.05, i + 0.05), 0.9, 0.9, fc="none", ec=RED, lw=2.0, zorder=5))
for (i, j) in AVOID:
    ax.add_patch(Rectangle((j + 0.05, i + 0.05), 0.9, 0.9, fc="none", ec=style.INK, lw=1.4, ls=(0, (2.5, 1.5)), zorder=5))
ax.set_xlim(0, 6); ax.set_ylim(5, 0); ax.set_axis_off()
for j, (k, sub) in enumerate(COLS_D):
    ax.text(j + 0.5, -0.66, k, ha="center", va="center", fontsize=8.4, fontweight="bold", color=style.INK)
    ax.text(j + 0.5, -0.22, sub, ha="center", va="center", fontsize=5.6, color=style.INK_2, linespacing=1.0)
for i, (k, sub) in enumerate(ROWS):
    ax.text(-0.12, i + 0.5, sub, ha="right", va="center", fontsize=5.9, color=style.INK_2, linespacing=1.05)
    ax.text(-1.55, i + 0.5, k, ha="center", va="center", fontsize=9, fontweight="bold", color=style.INK)
ax.text(3.0, -1.25, "Defenders who might cover him", ha="center", va="center", fontsize=7.4, color=style.INK)
nx = fig.add_axes([0.665, 0.04, 0.335, 0.66]); nx.set_xlim(0, 10); nx.set_ylim(5, 0); nx.set_axis_off()
NOTES = [(3.5, "Y vs W: line up 3x1 with Y\ninside; in their usual Cover 1\nthe Will has the third receiver."),
         (4.5, "R vs M: motion the back out\nof the backfield (empty); the\nMike follows him in man."),
         (2.5, "H vs $: put H in the slot\naway from the safety's help\non third down."),
         (0.5, "Avoid X vs their star corner:\nhe stays on the offense's right,\nso X lines up on the left.")]
for y, t in NOTES:
    nx.text(0.2, y, t, ha="left", va="center", fontsize=6.3, color=style.INK, linespacing=1.15)
nx.text(0.2, 1.5, "(Z vs anyone: no edge.\nUse him to clear space.)", ha="left", va="center", fontsize=6.0,
        color=style.INK_2, style="italic", linespacing=1.1)
fig.text(0.02, 0.965, "MATCHUP GRID  ·  sample game (illustrative)", fontsize=8, fontweight="bold", color=style.INK)
fig.text(0.02, 0.925, "grade = expected edge for the offense, from -2 (defense wins) to +2 (offense wins)",
         fontsize=6.3, color=style.INK_2)
plt.show()
Figure 44.3: A matchup grid for a sample game (illustrative; the grades are invented for this example). Rows are the offense’s receivers, columns the defenders who might cover them; blue cells are pairings the offense should win, orange cells pairings the defense should win, grey ones that won’t happen in this defense’s scheme. The plan hunts the red-boxed cells and avoids the dashed one. The best pairing on the board is the tight end (Y) against the Will linebacker, so much of this week’s plan is about making that pairing happen; the defense’s star cornerback, who always plays on the offense’s right, is the pairing to avoid. The notes on the right are how the offense intends to get each one.

The notes on the right of Figure 44.3 are the important part, because a mismatch on paper is worth nothing until the formation produces it. Offenses have three tools for that.

  • Formation. Defenses assign coverage by rules: in a typical Cover 1 (man coverage with one deep safety), the linebackers take the backs and tight ends, the nickel takes the slot. So an offense that knows the rule can place its tight end where the rule hands him to a linebacker, such as the inside receiver in a three-receiver set. Detaching a tight end from the line, or putting him in the slot, is matchup hunting by alignment. It’s a question, not a guarantee: against a tight end who can really run, many defenses now answer with a safety on him and give the linebacker the back. Then the offense has learned something else, such as who the robber is this week, or that there isn’t one.
  • Motion. Motioning a player across the formation or out of the backfield does two jobs at once: it shows whether the defense is in man (a defender runs with him) and it moves the matchup. Send the running back out wide and, in man coverage, a linebacker has to follow him into open space.
  • Personnel. This is the oldest version. If the offense sends in two or three tight ends, the defense usually answers with bigger players (base or heavy personnel); then the offense spreads those tight ends out and throws against linebackers. If the defense answers with smaller, faster defensive backs, the offense keeps the tight ends in tight and runs at them. A receiving tight end who can also block is valuable precisely because he forces the defense to pick its poison, which is the conflict you met in Personnel Groupings.

The defense hunts too, in reverse. It looks for the offense’s weak blocker to rush against, the receiver who can’t beat press coverage, the back who can’t pick up a blitz. And it protects its own weak links by substitution (a sixth defensive back for a slow linebacker), by rules that hand the dangerous receiver to the right defender (a shadow corner), or by sending help.

What the data can see

A staff’s grid comes from scouting, but the analytics staff can point at where to look. One of the simplest efficiency splits is how much a defense gave up when the ball went to each kind of receiver. Figure 44.4 shows it for tight ends in 2025: the expected points each defense allowed per target to a tight end, compared with the league average.

Show the Python
style.apply()
fig, ax = plt.subplots(figsize=(6.6, 5.6))
y = np.arange(len(TE25))
cols = [style.SERIES[0] if v > 0 else style.SERIES[1] for v in TE25["rel"]]
ax.hlines(y, TE25["rel"] - 1.96 * TE25["se"], TE25["rel"] + 1.96 * TE25["se"], color=style.GRID, lw=2.2, zorder=1)
ax.scatter(TE25["rel"], y, s=26, color=cols, zorder=3, edgecolor=style.SURFACE, lw=0.6)
ax.axvline(0, color=style.INK_2, lw=1.0, zorder=0)
ax.set_yticks(y, TE25.index, fontsize=6.6)
for lab in ax.get_yticklabels():
    if lab.get_text() in {"CAR", "WAS", "ARI", "PHI", "HOU"}:
        lab.set_fontweight("bold"); lab.set_color(style.INK)
ax.set_ylim(-0.8, len(TE25) - 0.2)
ax.grid(axis="y", visible=False)
ax.set_xlabel("EPA per tight-end target, minus the league average", fontsize=8)
ax.tick_params(axis="x", labelsize=7.5)
ax.text(0.30, 6.0, "right of zero: gave up\nmore to tight ends,\na matchup to hunt", fontsize=6.8, color=style.INK_2,
        ha="left", va="center")
ax.text(-0.30, len(TE25) - 5.0, "left of zero:\ncovered tight\nends well", fontsize=6.8, color=style.INK_2, ha="right", va="center")
fig.tight_layout()
plt.show()
Figure 44.4: Matchup hunting starts in the data: expected points added (EPA) per target when the ball went to a tight end, by defense, 2025 regular season, minus the league average for tight-end targets. Right of zero, a defense gave up more than average when tight ends were targeted; left of zero, less. Whiskers are 95% confidence intervals: most defenses’ intervals cross zero, so only the ends of the chart are strong evidence. Each defense faced roughly 80 to 170 tight-end targets. A staff uses a chart like this to decide where to look on film, not as a verdict. Data: nflverse play-by-play and rosters (the targeted player’s listed position) via nflreadpy; course calculation.

At the top of Figure 44.4, Carolina, Washington and Arizona gave up roughly a quarter to a third of a point more per tight-end target than the average defense; at the bottom, Philadelphia’s defense gave up about a third of a point less. Across a season of about 124 tight-end targets per defense, gaps that size add up to 30 or 40 points, several touchdowns. An offense with a good receiving tight end, preparing for one of the defenses at the top, would start its film study there: who covered the tight ends, in which coverages, and could we create those pairings?

Two lessons hide in the whiskers. The first is noise: most defenses’ intervals cross zero, so the middle of the chart is mostly luck, and a staff that built a plan on a 0.1 difference would be chasing ghosts. The second is change. Correlate each defense’s 2024 number with its 2025 number and you get 0.32 for tight ends and 0.09 for wide receivers (0 would mean no carry-over at all from one season to the next, 1 a perfect repeat): last year’s weakness is a weak guide to this year’s, because rosters, injuries and coordinators change. That is exactly why matchup hunting is redone every week from recent film rather than looked up once.

Take away their best thing

The defensive half of the second question, what do they do best?, produces the most famous principle in game planning: take away the thing the opponent most needs, and make it beat you some other way. If an offense lives on one runner, stop the run and dare the quarterback to win the game. If it lives on one receiver, double him and make the other four beat you. If it lives on one concept, take away that concept’s landing spot.

The principle is associated above all with Bill Belichick, whose defenses, as the Giants’ defensive coordinator and then as head coach of the Browns and the Patriots, were built anew for each opponent.10 Michael Lombardi, who worked for him for years, added an important refinement: Belichick “doesn’t take away what the opponent does best, but what their individual players do best.”11 The target is often a person (the one receiver the quarterback trusts, the back the offense is built around), not a play. Belichick’s best-known plans (the Buffalo plan in the film room below, Super Bowl XXXVI against the Rams’ Marshall Faulk, Super Bowl LIII against the Rams again) are drawn and taken apart in Case Study: Belichick and Game-Plan Football.

Figure 44.5 shows the idea in the sample game from Figure 44.3, from the defense’s side. The offense’s best thing is its tight end, and the defense’s usual answer to a three-receiver set puts a linebacker, the Will, on him.

Show the Python
fig, axes = plt.subplots(2, 1, figsize=(5.0, 6.1))
LAT, WIN = (-18.5, 19.5), (-6.8, 15.8)
p = Play(trips_y(), their_usual(), los=40, to_go=None)
for dk, ok in (("LCB", "X"), ("RCB", "Z"), ("NB", "H"), ("WILL", "Y"), ("MIKE", "RB")):
    p.man(dk, ok)
p.highlight("WILL", color=RED)
f = draw(p, ax=axes[0], lateral=LAT, window=WIN)
panel_title(axes[0], "Their usual: nickel Cover 1")
point_to(f, p, (9.0, 11.5), (4.6, 3.6), "W has Y:\nour best matchup", color=style.DEFENSE_DARK)
say(f, p, 10.6, 0.0, "robber", fontsize=6.4)
p = Play(trips_y(), game_plan_dime(), los=40, to_go=None)
for dk, ok in (("LCB", "X"), ("RCB", "Z"), ("NB", "H"), ("DIME", "Y"), ("MIKE", "RB")):
    p.man(dk, ok)
p.highlight("DIME", "SS", "FS", color=PURPLE)
f = draw(p, ax=axes[1], lateral=LAT, window=WIN)
panel_title(axes[1], "This week: dime, Y bracketed")
point_to(f, p, (12.4, 0.5), (9.0, 4.2), "inside and over\nthe top of Y", color=style.DEFENSE_DARK)
point_to(f, p, (1.2, 12.6), (2.6, 7.9), "D outside\nand short", color=style.DEFENSE_DARK)
xg, yg = _P(f, p, 9.0, 1.5)
f.ax.add_patch(RegularPolygon((float(xg), float(yg)), 4, radius=0.95, orientation=np.pi / 4, fc="none",
                              ec=style.DEFENSE, lw=1.0, ls=(0, (2, 1.5)), alpha=0.8, zorder=6))
field_badge(f, p, 7.0, -1.6, "2")
field_badge(f, p, 9.8, -15.0, "1")
say(f, p, 11.8, -13.0, "no help", fontsize=6.6, color=RED)
fig.tight_layout(h_pad=1.0)
plt.show()
Figure 44.5: Taking away the best thing (illustrative). Top: this defense’s usual answer to a 3x1 set, nickel Cover 1. The rules hand the tight end (Y), the third receiver from the outside, to the Will linebacker (red ring), which is the offense’s best matchup on the board; the Mike has the back and the strong safety is the robber in the middle. Bottom: the game-plan alignment for this week. The defense goes to dime: a sixth defensive back (D) replaces the Will and lines up just outside Y, taking everything outside and short, while the strong safety sits inside and over the top of Y at nine yards, taking anything inside and deep, and the free safety shades to the three-receiver side (purple rings). Y is bracketed whichever way he breaks. The bill, numbered: (1) the cornerback on X has no deep help at all; (2) the robber is gone: the safety who used to fit the run in the middle (dashed outline) is tied to Y, and a defensive back has replaced a linebacker, so the run is cheaper than usual.

The bottom panel has taken the tight end away. If he breaks out, the dime back is already outside him; if he breaks in or runs deep, the safety is waiting inside and over the top. The quarterback’s favorite target becomes his worst read. But count the cost, because there is always a cost. The dime back came off the bench for a linebacker, so the defense is lighter against the run. The strong safety who used to rob the middle is now married to one player. The free safety has drifted toward the three receivers, so the cornerback on the single receiver, X, is alone on an island, and with nobody in the deep middle he has to protect against the post first. A defense that takes away the best thing has decided which other thing it would rather lose to. The animation below plays the game-plan coverage out.

Show the Python
p = Play(trips_y(), game_plan_dime(), los=40, to_go=None)
for k, dw in [("LT", -0.6), ("LG", -0.2), ("C", 0.0), ("RG", 0.2), ("RT", 0.6)]:
    p.block(k, pts=[(-1.4, dw)], speed=1.6)
for k, end in (("LDE", (-1.4, -4.4)), ("LDT", (-0.6, -0.9)), ("RDT", (-0.6, 1.5)), ("RDE", (-1.4, 4.4))):
    go_to(p, k, end, "rush", speed=1.8)
p.dropback(2.5)
go_to(p, "Y", (6.6, 2.4), "route", via=[(5.2, 6.4)])
go_to(p, "DIME", (5.6, 5.2), "path", via=[(4.2, 7.0)], speed=6.0)
go_to(p, "SS", (8.3, 2.0), "path", via=[(8.9, 3.8)], delay=0.7, speed=6.0)
go_to(p, "X", (12.0, -17.3), "route", via=[(14.2, -15.0)], speed=7.0)
go_to(p, "LCB", (16.0, -13.6), "path", via=[(11.0, -14.4)], delay=0.15, speed=5.4)
go_to(p, "H", (15.0, 12.0), "route", via=[(5.0, 11.0)])
go_to(p, "Z", (16.0, 16.5), "route")
go_to(p, "FS", (16.5, 8.5), "path", speed=3.0)
go_to(p, "RB", (1.0, -5.5), "route", via=[(-4.0, -3.5)], delay=0.3)
for dk, ok in (("RCB", "Z"), ("NB", "H"), ("MIKE", "RB")):
    p.man(dk, ok)
p.pass_to("X", t=2.35)
animation_or_strip(
    p, times=[0.0, 0.8, 1.7, 3.75],
    notes=["snap: D outside Y, SS inside\nand over; FS shaded to trips", "Y releases; D trails\nhim",
           "Y breaks inside into the\nsafety: the bracket closes", "the corner guarded the post;\nthe ball arrives at X's comeback"],
    lateral=(-21.5, 19.5), window=(-9.0, 19.5), rings=(("DIME", PURPLE), ("SS", PURPLE)),
    frame_rings={3: (("X", RED),)})
Figure 44.6: The game-plan coverage in motion (simulated, BDB-format tracking from gridiron). (1) At the snap the dime back (D) is outside Y and the strong safety (SS) is inside and over the top; the purple rings mark both. (2) Y releases; D trails him. (3) Y breaks inside on his option route, straight into the safety coming down: the bracket closes. (4) The quarterback has scouted it too and throws to the other side. With no safety in the middle, the corner on X has had to play the post, staying inside and deep, so X’s comeback (red ring) opens underneath him.
NoteFilm room: New York Giants, Super Bowl XXV (1990 season)

The most famous “take it away” plan sits in the Pro Football Hall of Fame. Before Super Bowl XXV, on January 27, 1991, Bill Belichick was the Giants’ defensive coordinator, facing a Buffalo offense that ran a fast no-huddle attack built on Jim Kelly’s passing. His plan used extra defensive backs to take away Kelly’s deep throws and told the defenders to hit the receivers hard to limit the short passing game; the handwritten plan, with sections such as “no-huddle adjustments,” went to the Hall afterwards.12 The Giants won 20–19.

What to notice: the plan conceded something on purpose. With more defensive backs on the field, the Giants were lighter against the run, and Buffalo’s running game had a good day. Belichick’s bet was that the Bills couldn’t run the clock and the scoreboard the way they could throw it, and that the Giants’ own ball-control offense would keep Kelly on the sideline. The plan in the Belichick case study shows how the alignments worked. The lesson for every game plan is the one in Figure 44.5: decide what you’ll give up, before the game, rather than finding out during it.

Offenses play the same game against the defense’s best player. If the opponent’s best player is a pass rusher, the plan “takes him away” with help: a tight end or back chipping him before releasing, a protection that slides toward him, quick throws on his side, runs straight at him (a rusher who is flying upfield is often vulnerable to a run right where he lined up), and screens that turn his speed against him. The cost: every helper on the rusher is one fewer receiver in the pattern.

A game plan can also be shaped by things that have nothing to do with the opponent’s roster. On a Monday night in December 2021, in winds strong enough to make passing a coin flip, Belichick’s Patriots beat the Bills in Buffalo 14–10 while throwing just three passes all game; the plan for that night was simply to run the ball and leave the throwing, into the wind, to Buffalo.13 Nothing was taken away that night; the whole plan was built around the weather, which neither roster could change.

The call sheet, organized by situation

On Sunday the whole week has to fit in one hand. What a Play Really Is showed a simple call sheet; Figure 44.7 is a denser one, closer to what a play-caller really carries, with the parts you’ve met in this chapter numbered. It’s a situational call sheet: organized by down, distance, field zone and clock rather than by play, with a short, ranked list of answers in each box.

Show the Python
SHEET = [
    # (col, row, rowspan, title, lines, badge)
    (0, 0, 3, "OPENERS  ·  1st & 2nd down only", [
        " 1 Ace Rt 24 Wide Zone",
        " 2 Gun Trey Rt Y Option  TEST: who has Y?",
        " 3 Ace Rt Fake 24 Keep Lt",
        " 4 Twins Lt Jet 28 Toss",
        " 5 Gun Empty Fly 62 Stick TEST: man/zone?",
        " 6 Ace Rt 24 Wide Zone",
        " 7 Gun Bunch Rt 70 Mesh",
        " 8 Tight Rt 36 Counter",
        " 9 Gun Trey Lt 61 Spot",
        "10 Ace Rt Fake 24 Y Leak  (SHOT)",
        "11 Gun Trips Rt Bubble",
        "12 Ace Lt 25 Wide Zone",
        "13 Gun Empty 64 Dagger",
        "14 Tight Rt Fake 36 Post-Whl",
        "15 Ace Rt 24 Wide Zone"], "1"),
    (1, 0, 1, "1st & 10", ["Ace Rt 24 Wide Zone", "Tight Lt 36 Counter", "Gun Trey Rt Y Option",
                            "Ace Rt Fake 24 Keep Lt", "Gun Trips Rt Bubble"], "2"),
    (1, 1, 1, "2nd & 7+", ["Gun Bunch Rt 70 Mesh", "Ace Rt 24 Draw", "Gun Trey Lt 61 Spot",
                           "Gun Rt Screen Rt"], None),
    (1, 2, 1, "2nd & 1-3   (shot down)", ["Ace Rt Fake 24 Y Leak", "Tight Rt Fake 36 Post-Whl",
                                           "Ace Rt 24 Wide Zone"], None),
    (2, 0, 1, "3rd & 1-2", ["Jumbo Rt QB Sneak", "Ace Rt 24 Wide Zone", "Gun Trey Rt Y Option"], None),
    (2, 1, 1, "3rd & 3-6", ["vs MAN:   Gun Trey Rt Zoom Y Opt", "           Gun Bunch Rt 70 Mesh",
                            "vs ZONE:  Gun Trey Lt 61 Spot", "           Gun Trips Rt 60 Stick"], "3"),
    (2, 2, 1, "3rd & 7+", ["vs MAN:   Gun Empty 66 Go-Whip", "vs ZONE:  Gun Empty 64 Dagger",
                           "vs 5+:    Gun Rt Screen Lt", "           Gun Rt 24 Draw"], None),
    (1, 3, 1, "RED ZONE  +20 to +10", ["Gun Bunch Rt 70 Snag", "Ace Lt Fake 25 Boot Rt", "Gun Trey Rt Y Option"], None),
    (1, 4, 1, "GOAL LINE  +5 and in", ["Jumbo Rt 36 Power", "Jumbo Rt Fake 36 Y Flat", "Jumbo Rt QB Sneak"], None),
    (2, 3, 1, "SHOTS   when SS walks down", ["Ace Rt Fake 24 X Post", "Tight Rt Fake 36 Post-Whl",
                                             "Gun Trey Rt Y Seam"], "4"),
    (2, 4, 1, "PACKAGE  vs their \"Viper\" blitz", ["Gun Empty 62 Hot Slant", "Gun Trey Rt Y Throwback",
                                                     "Gun Rt Screen Rt (vs Viper)"], "5"),
    (2, 5, 1, "SUDDEN CHANGE / after timeout", ["Ace Rt Fake 24 X Post", "Twins Lt Jet 28 Toss",
                                                "Gun Trey Rt Zoom Y Opt"], None),
    (0, 3, 1, "TWO-MINUTE", ["Gun Empty 64 Dagger", "Gun Trey Rt 60 Stick", "Gun Rt 24 Draw",
                             "CLOCK (spike)"], None),
    (0, 4, 1, "4-MINUTE  (protect a lead)", ["Ace Rt 24 Wide Zone", "Tight Rt 36 Counter", "Jumbo Rt 36 Power",
                                             "Ace Lt Fake 25 Boot Rt"], None),
    (1, 5, 1, "BACKED UP  own 1 to 5", ["Jumbo Rt 36 Power", "Tight Rt 36 Counter", "Ace Rt Max Fake 24 X Go"], None),
    (0, 5, 1, "4TH DOWN & 2-PT  (analytics)", ["4th & 1: go from our 35 on", "4th & 2-3: go from their 45 on",
                                               "2-pt after TD if: dn 2/5, up 1/5", "2-pt call: Gun Bunch Rt Snag"], "6"),
]
RH = 0.82   # row height
fig = plt.figure(figsize=(6.6, 7.9))
ax = fig.add_axes([0.0, 0.0, 1.0, 1.0]); blank(ax)
ax.set_xlim(0, 3); ax.set_ylim(-1.32, 5.32)
ax.add_patch(Rectangle((0, 0), 3, 5.32, fc="#fbfaf4", ec=style.BASELINE, lw=1.2))
ax.text(0.04, 5.2, "OFFENSE CALL SHEET  ·  vs. sample opponent  ·  Week 6  (illustrative)", fontsize=8.6,
        fontweight="bold", color=style.INK, va="center")
ax.text(2.96, 5.2, "best call first in every box", fontsize=6.4, color=style.INK_2, va="center", ha="right",
        style="italic")
for col, row, span, head, lines, num in SHEET:
    x0, top = col, 5.08 - row * RH
    h = span * RH - 0.04
    ax.add_patch(Rectangle((x0 + 0.02, top - h), 0.96, h, fc="white", ec=style.BASELINE, lw=0.8))
    hc = style.DEFENSE_TINT if head.startswith(("PACKAGE", "4TH")) else style.OFFENSE_TINT
    ax.add_patch(Rectangle((x0 + 0.02, top - 0.17), 0.96, 0.17, fc=hc, ec=style.BASELINE, lw=0.8))
    ax.text(x0 + 0.05, top - 0.085, head, fontsize=6.7, fontweight="bold", color=style.INK, va="center")
    step = min(0.145, (h - 0.24) / max(len(lines), 1))
    for k, ln in enumerate(lines):
        ax.text(x0 + 0.05, top - 0.28 - k * step, ln, fontsize=5.6, family="monospace", va="center",
                color=style.INK if not ln.startswith("           ") else style.INK_2)
    if num:
        badge(ax, x0 + 0.92, top - 0.085, num, fontsize=6.6)
KEY = [("1", "The opening script: ordinary downs only, called in order.\n\"TEST\" plays ask the defense a question the booth will chart."),
       ("2", "Ranked lists: the first call is the favorite against what the\ntendency report expects; the rest answer adjustments."),
       ("3", "Paired calls: a man beater and a zone beater for the same\nsituation (08-01), because the report says it's a coin flip."),
       ("4", "Shot plays, with a trigger: call them when the defense shows\nthe look the shot was built to beat."),
       ("5", "A game-plan package: built this week against this defense's\nfavorite blitz, and probably retired after the game."),
       ("6", "Decisions made in advance: the analytics staff's fourth-down\nand two-point chart, agreed on with the head coach midweek.")]
for i, (n, t) in enumerate(KEY):
    cx, cy = 0.05 + 1.5 * (i % 2), -0.22 - 0.42 * (i // 2)
    badge(ax, cx + 0.04, cy, n, fontsize=6.6)
    ax.text(cx + 0.13, cy, t, fontsize=6.3, color=style.INK, va="center", linespacing=1.2)
plt.show()
Figure 44.7: An annotated offensive call sheet (illustrative: invented play names in a realistic style, for the sample game in this chapter). Each box is a situation, and the play-caller’s eyes go to one box when the down-and-distance graphic changes. The numbered notes below the sheet explain the parts. Real sheets are denser still (often both sides of a large card, with more boxes and color codes), and every staff lays theirs out differently.

Walk the numbers.

(1) The openers. The first box is the opening script, which gets its own section next. Notice two things about it: it covers only first and second down (third downs and red-zone snaps go to their own boxes, and the script resumes afterwards), and some of its plays are marked as tests.

(2) Ranked lists. Each situation box holds a handful of calls, and the order matters. The first call is the one the staff expects to work best against what the tendency report says the defense will do. The others are the answers if the defense adjusts: the counter if the linebackers start flowing, the play-action if the safety comes down. A play-caller who has used the first call twice moves down the list, which is sequencing in printed form. Real boxes are split much further than this mock-up’s: by personnel group or formation, and often by hash (one list for the ball on the left hash, another for the right, because the wide side of the field changes what works). That’s why real sheets are so dense.

(3) Paired calls. Where the report says the defense is a coin flip (man or zone on third-and-medium), the sheet carries one coverage beater for each answer, and the play-caller picks by what the booth has seen today, or gives the quarterback both and lets him choose at the line once he sees the defense: the kill call from The Pre-Snap Phase.

(4) Shots with triggers. “Shot” plays are deep throws that need a specific look to work, so the box says when to call them: when the strong safety walks down into the box, when the defense has been crowding the line on second-and-short. A shot called into the wrong look is just a long incompletion.

(5) The package. A box built for this opponent only, which the Game-plan packages section below explains. “Viper” is not a football term you need to look up: in this example it’s the sample opponent’s own name for its favorite third-down blitz, the name the offense’s QC coaches heard or read on film and kept, so that everyone on the offense means the same blitz when they say it.

(6) The decisions already made. The analytics staff’s fourth-down and two-point chart lives on the sheet so the head coach isn’t doing arithmetic with 70,000 people screaming. Decoded, the box says: on fourth-and-1, go for it anywhere from our own 35-yard line on; on fourth-and-2 or 3, from the opponent’s 45 on; and after a touchdown, go for two if the score (before the try) has us down 2 or 5 or up 1 or 5. Down 2, a two-pointer ties; down 5, it makes a field goal enough to tie; up 1 or 5, it stretches the lead to 3 or 7, so a field goal or a touchdown-and-kick no longer beats us. (The numbers in the mock-up are invented; real charts change with the opponent, the score, the clock and the kicker.)

The unnumbered boxes are the situations every sheet carries: two-minute (hurry, with a spike to stop the clock), four-minute (runs that keep the clock moving when protecting a lead), backed up (inside our own 5, where a safety or a turnover is a disaster, so the list is runs and one max-protection shot), and sudden change (the first call after a timeout or a takeaway, which the scouting section said opponents chart). The play names are shorthand for the formation, motion and play, in an invented language built on the course’s numbering: “Gun Trey Rt Zoom Y Opt” is shotgun, three receivers to the right with the tight end inside, the back motioning out (here called “zoom”), and the tight end’s option route; “Post-Whl” is a post with a wheel route under it, “Go-Whip” a go route paired with a whip route that breaks in and then snaps back out, “Fly” a jet-style motion across the formation, and “vs 5+” means “if they rush five or more.”

The sheet is offensive. The defensive coordinator’s sheet across the field looks different: it is filed by situation and by the offense’s personnel and formation, because the defense has to answer what it sees. A box might read “3rd & 4-6 vs 11 personnel, 3x1: Cover 1 robber / sim pressure / 2-Man,” with the same ranked and paired logic underneath. Defensive coordinators plan their opening series too, though rarely as a strict list: a few fronts, coverages and pressures they want to show early, partly to see how the offense’s protection and quarterback answer them.

Sheets differ as much as the coaches who hold them. Andy Reid’s is famously big: during one of the Chiefs’ Super Bowls, Waffle House posted his play card next to its own menu.14 In Kansas City the call has traveled in two steps: Reid says a short name for the play into his headset, and his offensive coordinator finds it on his own copy of the sheet and relays the full call, about a dozen words of formation, motion and play, to the quarterback, ideally with 20 seconds left on the play clock. The rush is the radio: the coach’s voice in the quarterback’s helmet cuts off with 15 seconds left (see What a Play Really Is), so a call that isn’t in by then doesn’t arrive.15 Kyle Shanahan, by contrast, has refused to number his plays for a wristband system: a former quarterback of his said Shanahan’s view was that he “can’t put it numerically because he needs to watch the defense” before choosing the words.16

The opening script

The opening script is a list of plays, usually about fifteen, that the play-caller decides before the game to call in order at the start. It’s the most visible fingerprint of the game week on a broadcast, and it’s older than most of the people who use it.

Where it came from. Bill Walsh, the 49ers’ head coach from 1979 to 1988, is generally credited with popularizing the practice of scripting the first fifteen offensive plays of a game, and his scripts often ran into the twenties. Walsh was a former high school coach and an assistant to Paul Brown (the founding coach of both the Browns and the Bengals). He planned everything, and he gave several reasons for the script, all of them about the gap between a quiet room on Friday and a loud sideline on Sunday.17 Decisions made calmly during the week are better than decisions made in the emotion of the first quarter. A script guarantees that the plays the staff liked best actually get called, rather than forgotten when the game starts fast. Players who know the first plays in advance can rehearse them, and they start the game calmer. And a script lets the offense ask the defense questions in a deliberate order and watch the answers.

What’s on one. A modern script is still mostly first-and-second-down plays that the team runs well, chosen against the opponent’s tendencies, plus some deliberate questions. Each test play is there to find out how the defense will handle something the offense wants to use later:

  • A new formation, to see how the defense aligns to it (in the opening scene, two detached tight ends: does the defense keep its linebackers on the field and walk them out, or substitute?).
  • A motion, to see who runs with it (man or zone) and whether the safeties rotate.
  • A personnel group, to see what the defense sends on in response.
  • A shot, often early, to see whether the deep safety is disciplined, and to plant the idea that he has to be.

That’s the answer to the opening scene’s last question. The first play was a test, and it got its answer: this defense keeps its base personnel on the field against two split tight ends and walks two linebackers out to cover them. Running the same play again on the second snap would have spent the answer while the defense was still thinking about it. It’s worth more saved, for a third-and-5 when a tight end against a linebacker in space is exactly what the offense needs. The answer has a second half too: count the box. With two linebackers walked out, five defenders are left near the ball against five offensive linemen, so the same formation is also a good run look, and many offenses carry a run-pass option from it that reads the walked-out linebacker (throw to the tight end if he stays inside, hand off if he widens).

The booth charts every answer. By the end of the first quarter, the play-caller knows things that no amount of film could have told him, because the film showed this defense against other offenses and the script showed it against his. Those answers drive the calls for the rest of the game, which is the subject of In-Game Adjustments, Tempo, and the Sideline Battle.

What a script is not. It is not a promise to call fifteen plays in a row no matter what. Third downs, red-zone snaps, penalties and field position all interrupt it (many scripts carry “backed-up” alternatives, because a scripted shot or reverse is not what you call from your own 2): the play-caller goes to that situation’s box, then comes back to the next unused play on the script. And a play-caller who sees something he likes, or something that’s going badly, can leave the script at any time; most stay with it through the first couple of series because that’s the point of having one.

NoteFilm room: Kansas City Chiefs, 2019 season — Andy Reid’s index card

Before Super Bowl LIV, reporters asked how Andy Reid builds his openers. The answer was a 3-by-5 index card. Reid writes his first fifteen plays on it in red pen, first and second downs only (third down and red zone come from the sheet), and on Friday evening he texts a picture of it to his offensive coordinator with one word: “Thoughts?” They talk it through on Saturday morning, and the quarterbacks have their say. Matt Nagy, a longtime Reid assistant who had been his offensive coordinator in Kansas City, described how the list settles: “He’ll know there’s a handful of plays that everybody really likes, and those are the ones that end up in the first 15.”18 Reid also draws new plays on index cards early in the week and hands them to the quarterbacks, some of them with three players in motion at once, and the group picks a favorite or two to try in that week’s install; one former assistant remembered him sending more on a Friday night: “I’ll put on another 15, I’m feeling it.”19

What to notice: the script is a collaboration that ends with the quarterback’s comfort. A play the quarterback doesn’t like rarely survives to Sunday, because the opening plays are the ones the whole offense has rehearsed most and believes in. Watch the Chiefs’ first two drives for plays you haven’t seen from them before. Many of them come from those early-week index cards.

Does the script gain more yards?

If scripted plays are chosen calmly, rehearsed all week and aimed at the opponent’s tendencies, they should work better than the plays called later, shouldn’t they? You can check. Figure 44.8 splits every neutral-situation snap from 2022 to 2025 by where it fell in the offense’s game (its 1st through 5th snap, 6th through 10th, and so on) and asks two questions: how efficient were the plays, and how often did the offense show a look it hadn’t used in any earlier game that season?

Show the Python
style.apply()
fig, (a1, a2) = plt.subplots(1, 2, figsize=(6.6, 3.0))
x = np.arange(len(BIN_LABELS))
for a in (a1, a2):
    a.axvspan(-0.5, 2.5, color=style.OFFENSE_TINT, alpha=0.6, lw=0, zorder=0)
    a.set_xticks(x, BIN_LABELS, fontsize=6.4, rotation=45)
    a.set_xlim(-0.6, len(x) - 0.4)
    a.grid(axis="x", visible=False)
    a.set_xlabel("offense's snap number in the game", fontsize=7.6)
    a.tick_params(axis="y", labelsize=7.2)
a1.errorbar(x, BY_BIN["mean"], yerr=1.96 * BY_BIN["se"], fmt="o-", color=style.SERIES[0], ms=4, lw=1.4,
            elinewidth=0.9, capsize=2, zorder=3)
a1.axhline(0, color=style.BASELINE, lw=0.9)
a1.set_ylim(-0.11, 0.11)
a1.set_ylabel("EPA per play", fontsize=7.6)
a1.set_title("Efficiency", fontsize=9.2)
a1.text(1.0, 0.093, "first 15", fontsize=6.6, color=style.OFFENSE_DARK, ha="center")
a2.errorbar(x, 100 * NEW_BIN["mean"], yerr=196 * NEW_BIN["se"], fmt="o-", color=style.SERIES[2], ms=4, lw=1.4,
            elinewidth=0.9, capsize=2, zorder=3)
a2.set_ylim(0, 5.5)
a2.set_ylabel("% of snaps from a brand-new look", fontsize=7.6)
a2.set_title("Something new", fontsize=9.2)
fig.tight_layout(w_pad=1.4)
plt.show()
Figure 44.8: Does the opening script show up in the data? Neutral-situation snaps (win probability 20-80%, outside the last two minutes of each half), 2022-2025 regular seasons, by the offense’s snap number in the game; the shaded band is the first 15 snaps, a typical script. Left: expected points added per play, with 95% confidence intervals. Scripted snaps were no more efficient than later ones. Right: the share of snaps from a look (personnel group, quarterback alignment and number of backs) the team had not used in any earlier game that season, from each team’s fifth game on. New looks were somewhat more common early and became rarer as the game went on. Data: nflverse play-by-play, participation and FTN charting via nflreadpy; course calculation.

The left panel is a clear no. In neutral situations, offenses gained -0.006 expected points per play on their first fifteen snaps and +0.001 afterwards, and their success rates were 43.9% and 43.9%: no difference worth the name.20 Restricting both groups to first and second downs, the downs a script is written for, doesn’t change the answer (+0.002 against +0.008). A study by SumerSports that compared each team’s first fifteen plays with the rest of its plays found a mild edge at best: since 2007, about 60% of NFL teams were more efficient on scripted plays.21 That sounds like more than the 50% a coin would give, but it’s weak evidence: fifteen plays a game is a small, noisy sample for any one team-season, so a good share of those “wins” would happen by luck alone. Sixty percent is a hint, not a finding.

The right panel shows where the script’s fingerprint does appear. About 4.0% of early snaps came from a look the team had never used before that season, against 3.6% in the middle of the game and 2.9% late. That’s a modest difference, and the public data sees only the coarsest version of “new” (personnel, where the quarterback stands, how many backs). The novelty a defense notices, a motion it hasn’t seen, a tight end split out, a route combination from a familiar formation, is invisible in these columns.

Why doesn’t all that preparation buy yards? Three reasons, and they explain what the script is really for.

  1. The defense has a script too. Defensive coordinators plan their opening series as carefully as offenses do, with their own new wrinkles and their own questions. Prepared meets prepared.
  2. Test plays are asked, not won. A play called to see who follows the motion is chosen for what it reveals, not for its expected gain.
  3. The payoff comes later, and averages can’t see it. What the script buys is information, and the offense cashes it in after the script is over, when the play-caller calls the plays the answers pointed to. You might expect that to show up as better efficiency in snaps 16 to 60, and the left panel shows it doesn’t. That isn’t proof the information is worthless. League-wide averages can’t see it, because the defense is learning from the same snaps and adjusting too (that’s In-Game Adjustments), and one offense’s edge is the other side’s loss in the same game. The value of a good question shows up in single games and single calls, not in a 32-team average.

So the honest summary is: the script is a tool for asking good questions and starting calmly, not a magic list of plays that work better. That fits what Walsh said it was for.

Game-plan packages

Most of a game plan is the team’s stable core, aimed at this opponent. A small part is new: a game-plan package, a group of plays, formations or calls built for one opponent and one purpose, taught that week, and often retired after the game. The “vs Viper pressure” box on the call sheet is one: three answers to the opponent’s favorite third-down blitz, which the QC coaches flagged in their blitz report, drawn up on Wednesday night as part of the third-down plan and installed Thursday.

Packages come in many sizes:

  • An offensive package against one defensive habit: a set of motions to identify a man-heavy defense’s coverage, a screen built to punish a defense that blitzes the slot defender, a run designed against one defensive line’s stunt.
  • A matchup package: the formations that create the hunted pairing from Figure 44.3, such as the 3x1 set with the tight end inside, plus two or three plays from it that feature him.
  • A defensive package against one offensive threat: the dime bracket in Figure 44.5, a special pressure aimed at a protection weakness found on film, a disguise built for a quarterback who throws on rhythm.
  • A trick play saved for one opponent: a reverse or a throwback designed against a defense that pursues hard.

Packages are small for the reason this chapter started with: practice time. A package that needs new rules for every player won’t be ready by Sunday, so the best ones are built from things the team already knows: the same play in a new formation, the same coverage with one player’s job changed. That’s the illusion of complexity again, used for one week. And packages are retired because they go on film. The moment a package works, every future opponent’s QC coaches tag it, and the next defense will have a plan for it. Some come back later in the season, when the opponent least expects them, as the tendency breakers a self-scout recommends.

CautionCommon misconception

“Teams install a whole new offense every week.” They don’t, and they couldn’t. Players run their core plays hundreds of times over a season, and that’s what makes them fast and confident. What changes each week is the selection, the packaging (formations, motions, personnel) and the aim: which matchups, which tendencies, which situations. A few genuinely new plays appear each week, in packages small enough to learn in three days.

TipWatch for it on Sunday: the script detector

Pick one offense and watch its first fifteen offensive plays with a notepad. You’re looking for the parts of the plan that were written on Tuesday.

Before each snap

  • Write down the personnel group and formation. Is it something you haven’t seen from this team before? Mark it NEW.
  • Is there a motion? Watch who goes with it. That’s a test question: man or zone?
  • Are the plays alternating in a way that ignores the down? Scripts are built for ordinary downs, so a second-and-9 shot play or a first-down screen out of nowhere is often the script talking.

After each snap

  • Did the play seem designed to find something out (a quick throw to a detached tight end, a motion followed by a simple run)? Note what the defense did.
  • After the first two drives, look at your list. Every NEW mark is a part of this week’s game plan. Watch for those looks to come back later, especially on third down and in the red zone, with a different play attached.
  • Count when the script ends: when the offense starts repeating looks and the calls start following the down and distance closely, the play-caller has moved from the script to the sheet.

And on the sideline: watch the play-caller hold the sheet over his mouth while he talks into the headset. He is hiding his lips from anyone across the field, in the stands or on a broadcast close-up who might read the call, and that sheet in front of his face is the week you just read about.

Predict the play

TipPredict the play 1: the second time
Show the Python
def two_te_spread():
    return line() + [O("QB", "QB", -5.0, 0.0, "Q"), O("RB", "RB", -5.0, 1.6, "R"),
                     O("X", "WR", -0.7, -15.0, "X"), O("U", "TE", -1.5, -8.5, "U"),
                     O("Y", "TE", -1.5, 8.5, "Y"), O("Z", "WR", -0.7, 15.0, "Z")]


nickel_sub = dl_nickel() + [D("WILL", "WILL", 4.6, -2.2, "W"), D("MIKE", "MIKE", 4.6, 1.4, "M"),
                            D("NB", "NB", 4.6, 8.8, "$"), D("SS", "SS", 5.5, -8.8, "SS"),
                            D("FS", "FS", 13.5, 0.0, "FS"), D("LCB", "CB", 6.5, -15.0, "CB"),
                            D("RCB", "CB", 6.5, 15.0, "CB")]
p = Play(two_te_spread(), nickel_sub, los=30, to_go=10)
p.highlight("NB", "SS", color=PURPLE)
f = draw(p, lateral=(-18.5, 18.5), window=(-7.4, 16.5), width_in=5.6)
point_to(f, p, (11.0, 10.5), (4.8, 8.8), "nickel in for\nthe Sam, on Y", color=style.DEFENSE_DARK)
point_to(f, p, (11.0, -10.5), (5.6, -8.6), "safety down\nover U", color=style.DEFENSE_DARK)
say(f, p, -4.0, -12.0, "same look as\nthe first snap", fontsize=6.6, color=style.OFFENSE_DARK)
plt.show()
Figure 44.9: Second quarter, first-and-10 at the offense’s own 30. The offense comes back to the formation from its first snap of the game: two tight ends (Y and U) split off the line into the slots, one back. On that first snap the defense kept its base personnel and walked two linebackers out over the tight ends. This time it substituted: a nickel back ($) has come on for the Sam linebacker and lined up over Y, and the strong safety has come down over U (purple rings). The Mike and Will stay inside; one safety is deep.

What did the offense learn from this substitution, and what is it likely to call from this look now?

It learned that this formation pulls a linebacker off the field, so now it runs, at the nickel back. The first snap asked “what do you do when our tight ends split out?”, and the defense answered with linebackers. This time it answered with defensive backs, and that is the personnel conflict from Personnel Groupings in action: the offense still has two tight ends who can block, and the defense has replaced a 240-pound linebacker with a defensive back of about 190 pounds. The best call is a run toward Y, outside zone or a toss to the right, with Y blocking the nickel back from his split or after motioning back in tight. A big tight end blocking a small defensive back is a pairing the offense wins, and the nickel is the player who has to set the edge on that side.

Two other things are now in the booth’s notes. The tight ends are matched on defensive backs, which is a size mismatch the offense can throw at near the goal line (a back-shoulder throw or a fade to a tall tight end). And the defense has shown it will substitute for this look, so the offense can come back to it without substituting and at a fast tempo, which keeps whichever personnel the defense has on the field stuck there (more in In-Game Adjustments). Either way, the first snap’s question has paid off twice: once in the answer, and once in what the defense had to give up to change it.

TipPredict the play 2: the motion that finds the matchup
Show the Python
ofn = [q.moved(d=-5.0, w=-1.6) if q.key == "RB" else q for q in trips_y()]
dfn = dl_nickel() + [D("MIKE", "MIKE", 3.9, -0.6, "M"), D("WILL", "WILL", 3.9, 4.4, "W"),
                     D("NB", "NB", 5.0, 11.0, "$"), D("SS", "SS", 9.5, 1.0, "SS"),
                     D("FS", "FS", 14.5, 0.0, "FS"), D("LCB", "CB", 6.0, -15.0, "CB"), D("RCB", "CB", 6.0, 16.0, "CB")]
p = Play(ofn, dfn, los=55, to_go=5)
p.motion("RB", to=(-1.5, -9.0))
f = draw(p, lateral=(-18.0, 19.5), window=(-7.4, 17.5), width_in=5.6)
ghost(f, p, [(3.9, -1.6), (3.6, -4.8), (3.4, -8.2)], alpha=0.85, lw=1.5)
point_to(f, p, (10.4, -8.5), (3.6, -5.6), "M runs out\nwith R", color=style.DEFENSE_DARK)
plt.show()
Figure 44.10: Third-and-5 near midfield. The offense is in the 3x1 set with its star tight end (Y) as the inside receiver on the right. Before the snap the running back motions out to the left (dashed arrow), and the Mike linebacker runs out with him (faint arrow). The Will (W) has stayed over Y, a couple of yards inside him; the strong safety sits in the middle at about nine yards, and the free safety is deep.

Where is the ball most likely going, and why did the offense motion the back?

To the tight end, on an option route that breaks out, away from the Will. The motion did two jobs, which is why matchup-hunting plans love it. First, it asked the coverage question: the Mike ran out with the back, so this is probably man coverage. Second, it moved the matchups. The Will is now alone on the offense’s best receiver: exactly the cell circled on the matchup grid in Figure 44.3. Then read the leverage. The Will is lined up inside Y, and the strong safety is sitting in the middle as a robber, waiting for anything that breaks inside. So Y’s option route (he reads his defender and breaks away from him at five or six yards) goes out, toward the sideline, where neither the Will nor the robber can get to it in time. It’s the first call in the “3rd & 3-6, vs man” box of the call sheet, and it’s built for exactly this picture.

A caution about the tell. A linebacker widening with a motioning back suggests man, but zone defenses also “bump” a defender out with motion. The real tell is whether he runs all the way with the back and whether the rest of the defense rotates (see Disguise, Rotation and Split-Field), and on third down smart defenses disguise this on purpose.

Partial credit if you said the back: a running back matched on a linebacker in space is the other pairing the grid liked. And notice what the defense could have done instead: if this team had built the dime bracket from Figure 44.5 into its game plan, a defensive back would be on Y and a safety over him, and the right answer would be the other side of the field. Which picture you see before the snap tells you which game plan you’re watching.

TipPredict the play 3: they took away the run
Show the Python
def singleback():
    return line() + [O("QB", "QB", -2.3, 0.0, "Q"), O("RB", "RB", -7.0, 0.0, "R"), O("Y", "TE", -0.7, 4.8, "Y"),
                     O("Z", "WR", -1.5, 14.0, "Z"), O("X", "WR", -0.7, -15.0, "X"), O("H", "WR", -1.5, -9.0, "H")]


gp_run = [D("SDE", "DE", 1.0, technique("7", 1), "E"), D("SDT", "DT", 1.0, technique("3", 1), "T"),
          D("WDT", "DT", 1.0, technique("1", -1), "T"), D("WDE", "DE", 1.0, technique("5", -1), "E"),
          D("SAM", "SAM", 4.2, 3.6, "S"), D("MIKE", "MIKE", 4.6, 0.4, "M"), D("WILL", "WILL", 4.5, -8.6, "W"),
          D("SS", "SS", 7.0, 6.6, "SS"), D("FS", "FS", 13.5, -1.0, "FS"),
          D("LCB", "CB", 6.5, -15.0, "CB"), D("RCB", "CB", 6.5, 14.0, "CB")]
p = Play(singleback(), gp_run, los=48, to_go=3)
p.highlight("SDE", color=RED)
f = draw(p, lateral=(-18.5, 17.5), window=(-8.6, 16.5), width_in=5.6)
point_to(f, p, (-4.6, 10.4), (1.0, technique("7", 1)), "7-tech on the TE:\nY can't reach him", color=style.DEFENSE_DARK)
point_to(f, p, (11.2, 9.4), (7.0, 6.6), "SS down: a seventh man\nin the box, TE side", color=style.DEFENSE_DARK)
say(f, p, 13.5, 3.8, "one deep", fontsize=6.6, color=style.DEFENSE_DARK)
point_to(f, p, (10.6, -12.6), (4.5, -8.6), "a linebacker\non H", color=style.DEFENSE_DARK)
plt.show()
Figure 44.11: Second-and-3, second quarter. The offense’s best thing, all season, has been outside zone to the tight end side, and this defense’s game plan was built to stop it: its end lines up on the tight end’s inside shoulder, a 7-technique (red ring), so the tight end can’t reach him; the strong safety is down at seven yards outside him as the force player; the Sam and the 3-technique tackle load the same side; and the Will has walked out over the slot receiver (H). One safety is deep. The offense is in 11 personnel, quarterback under center, tight end on the right.

The defense has taken away the offense’s favorite run. What should the offense call, and what did the defense pay for this alignment?

The best call is play-action off the outside-zone look, with a shot behind the safety who came down. Start with what the defense did. The end is set inside the tight end, so Y can’t reach him and the edge is set hard; the strong safety is an extra run defender outside him; and the front is leaning that way. That’s a defense taking away the best thing. (Notice it did not do it with a wide-9 end: an end lined up far outside the tight end makes outside zone easier, because the tight end just pushes him wider and the back cuts up inside him, the trade from Even Fronts.)

Now find the bill. There’s one deep safety, and he has the whole deep middle. The Will has been pulled out of the box to cover H, so a linebacker is matched on a slot receiver. And everything is shaded right, so the left side of the line has fewer defenders than usual. Play-action that looks exactly like outside zone right attacks the biggest of those bills: the strong safety and the linebackers have spent all week being told to fly at that run, so they bite on the fake, and a receiver runs behind them into the space the one deep safety can’t cover alone. This picture is the trigger printed on the call sheet’s SHOTS box (“when SS walks down”). The other answers are good alternatives: a quick throw to H on the linebacker, the safest completion; or a run to the left, away from the crowd, such as inside zone or a counter. The constraint tree from Constraint Theory is a list of exactly these answers.

On second-and-3 the offense has a free down: even an incomplete pass leaves a manageable third-and-3. That’s why this is the classic moment for the play-action shot, and why the call sheet’s “2nd & 1-3 (shot down)” box exists. If you said “outside zone right anyway,” you’ve found the call this defense spent its week preparing for.

Takeaways

  • A game plan is a choice under a time limit. Three practices a week can’t cover a playbook, so each week a staff picks the slice aimed at one opponent: its habits, its best thing, the matchups available, and what it expects from us.
  • The week runs from common to rare. First and second down on Wednesday, third down on Thursday, red zone and two-minute on Friday, with the coaches’ Tuesday in front of it all and the QC coaches’ breakdown a week or more ahead.
  • Quality control describes; analytics decides. The QC staff’s tendency report and cut-ups say what the opponent did; the analytics staff’s fourth-down charts and efficiency splits say what to do about it. Both shift odds; neither reads minds, and the opponent has scouted itself too.
  • Matchup hunting is arranged, not found. A good pairing on paper only matters once formation, motion or personnel forces it onto the field; defenses avoid bad pairings by substitution, shadowing and help.
  • Taking away the best thing always sends a bill. Every defender added against the opponent’s strength is taken from somewhere else, and the best game plans decide in advance where they’d rather be beaten.
  • The opening script buys information and composure, not yards. In 2022–2025 neutral situations, scripted snaps were no more efficient than later ones; they were somewhat more likely to show something new, and the answers they get pay off later in the game.

Connections

Builds on: Constraint Theory (tendencies, the self-scout and sequencing, which this chapter turns into a week’s work); What a Play Really Is (the staff and the first look at a call sheet); What Beats What (the coverage beaters that fill the call sheet’s paired boxes); Man Coverage (brackets and shadow corners, the tools for taking away a receiver); and Personnel Groupings (the personnel conflict behind matchup hunting).

Sets up: In-Game Adjustments, Tempo, and the Sideline Battle (what the staff does with the script’s answers once the game starts); Third Down and Red Zone, Goal Line and Short Yardage (the Thursday and Friday installs in full); Win Probability and Fourth Downs (the math behind the fourth-down chart); Tendencies, PROE and Charting (building tendency reports from data); Film Study and Charting (charting a game the way a QC coach tags one); and the case studies Belichick and Game-Plan Football, where a defense is built from scratch for each opponent, and Walsh’s 49ers and the West Coast Offense, where the script began.

NoteGo deeper

Bill Walsh’s Finding the Winning Edge (with Brian Billick and James Peterson) is the inside account of how an NFL staff plans a season and a week, scripting included. Michael Lombardi’s Gridiron Genius (2018) draws on his years with Belichick and Walsh to describe how the best staffs prepare. For the week as a player lives it, John Wolford’s “A Week as an NFL Quarterback” posts (cited in the notes) walk through the install days hour by hour. For data, start with this chapter’s code: change "SEA" in tendency_report(DB25, "SEA") to any team’s abbreviation and you have the first page of that defense’s report, then go on to 13-04.


  1. Under the collective bargaining agreement, “padded practices for all players shall be limited to a total of fourteen” in the regular season, eleven of them in the first eleven weeks: NFL–NFLPA Collective Bargaining Agreement, Article 24, Section 1, via Over the Cap: https://overthecap.com/collective-bargaining-agreement/article/24/section/1. The limit was introduced in the 2011 agreement and carried into the 2020 one, whose text adds that the other three come in the last six weeks.↩︎

  2. The CBA guarantees players “at least one day off per calendar week” in the regular season (Article 35, Section 2(b), via Over the Cap: https://overthecap.com/collective-bargaining-agreement/article/35/section/2/(b)/) but doesn’t say which day. By custom, “most teams reserve Monday for treatment of injured players and give Tuesdays off”: Geoff Mosher, NBC Sports Philadelphia, Dec. 11, 2013, on Chip Kelly’s Eagles, one of the few teams that practiced on Tuesdays: https://www.nbcsportsphiladelphia.com/news/kellys-6-day-week-helping-eagles-down-stretch/293174/.↩︎

  3. John Wolford, “A Week as an NFL Quarterback: Wednesday” (Oct. 26, 2022) and “…: Thursday” (Jan. 12, 2023), Kinetex blog (Wednesday: normal down-and-distance install, play calls sent Tuesday night or Wednesday morning; Thursday: third-down install and blitz review, red zone installed on Friday, and the “hardest day” quote): https://blog.kinetex.co/a-week-as-an-nfl-quarterback-wednesday/ and https://blog.kinetex.co/a-week-as-an-nfl-quarterback-thursday/. Wolford was a quarterback for the Rams at the time.↩︎

  4. Course calculation: nflverse play-by-play, 2025 regular season, runs and dropbacks with a down recorded, counted per offense per game. Kicks, kneel-downs, spikes, two-point tries and plays wiped out by penalties are not counted.↩︎

  5. Super Bowl LX, Feb. 8, 2026, at Levi’s Stadium: Seattle 29, New England 13; Seattle’s staff was head coach Mike Macdonald, offensive coordinator Klint Kubiak and defensive coordinator Aden Durde (course fact sheet; see https://en.wikipedia.org/wiki/Super_Bowl_LX). The table’s method: nflverse play-by-play joined to participation (load_participation, FTN-sourced for 2025), regular-season dropbacks with a charted coverage (excluding BLOWN and COMBO); “two-deep shell” = Cover 2, 2-Man, Cover 4 or Cover 6; “5+ rushers” = number_of_pass_rushers of five or more; first down limited to first-and-10; “red zone” = snaps at or inside the opponent’s 20, any down.↩︎

  6. 2026 play-callers and coordinators from the course fact sheet (checked October 2026), with sources including the Pro Football Rumors 2026 coordinator tracker: https://www.profootballrumors.com/2026/02/2026-nfl-offensive-defensive-coordinator-search-tracker. Drew Petzing became the Lions’ offensive coordinator for 2026 after serving as Arizona’s; Jesse Minter is the Ravens’ 2026 head coach and Anthony Weaver their defensive coordinator. Weaver was Miami’s defensive coordinator in 2024 and 2025 (https://www.baltimoreravens.com/news/ravens-name-anthony-weaver-defensive-coordinator), and Minter the Chargers’ in the same two seasons (https://www.nfl.com/news/jesse-minter-ravens-hire-head-coach-chargers-dc).↩︎

  7. Pro Football Rumors, Sept. 2, 2024: “With Gannon a defense-oriented coach, Petzing is going into his second season as an NFL play-caller”: https://profootballrumors.com/2024/09/cardinals-oc-drew-petzing-expected-to-draw-2025-hc-interest. He held the Arizona job through 2025 before moving to Detroit (course fact sheet).↩︎

  8. Course calculation: nflverse play-by-play, regular seasons 2023–2025, first and second downs with win probability 0.20–0.80 and more than two minutes left in the half; nflverse’s pass_oe (dropback rate minus the model’s expected dropback rate, in percentage points), averaged per team-season and ranked among the 32 teams.↩︎

  9. The penalties and the league’s rule against videotaping signals, as reported at the time by Mike Reiss, Boston Globe (Sept. 13, 2007): https://web.archive.org/web/20081011112029/http://www.boston.com/sports/football/patriots/reiss_pieces/2007/09/final_ruling.html; summary and further sources: https://en.wikipedia.org/wiki/Spygate_(NFL). The taping happened at the Jets game on Sept. 9, 2007.↩︎

  10. Belichick was the Giants’ defensive coordinator from 1985 to 1990, the Browns’ head coach from 1991 to 1995 and the Patriots’ from 2000 to 2023: Pro Football Reference, https://www.pro-football-reference.com/coaches/BeliBi0.htm.↩︎

  11. Michael Lombardi, “The Belichick Way,” The Ringer, Feb. 1, 2017: https://www.theringer.com/2017/02/01/nfl/bill-belichick-game-plan-new-england-patriots-super-bowl-li-f25cf7de09e0. “Make them beat you left-handed” is an old coaching phrase rather than a Belichick quote, but opponents reach for it to describe his defenses; Vikings coach Mike Zimmer before facing New England in 2018: “They’re always going to try to make you beat them left-handed if they can”: https://www.patriots.com/news/what-they-re-saying-minnesota-vikings.↩︎

  12. Mark Mravic, “NFL History in 95 Objects: Bill Belichick’s Super Bowl XXV game plan,” Sports Illustrated, May 14, 2014: https://www.si.com/nfl/2014/05/14/nfl-history-in-95-objects-bill-belichick-super-xxv-game-plan (extra defensive backs against Kelly’s deep throws, hard hits on the short passes, the “no-huddle adjustments” section, and the plan’s place in the Hall of Fame). The plan “scribbled on note paper with sections like ‘no-huddle adjustments’” is Nora Princiotti’s description in The Ringer, Jan. 13, 2022 (cited again below for the 2021 wind game). Belichick told his players before the game that Thurman Thomas could run for 100 yards and the Giants would still win: Michael Eisen, “Belichick’s gameplan,” Giants.com: https://www.giants.com/news/belichick-s-gameplan. The Giants beat Buffalo 20–19 on Jan. 27, 1991.↩︎

  13. New England at Buffalo, Dec. 6, 2021 (Monday night): Patriots 14, Bills 10, with three New England pass attempts (Mac Jones completed two of three for 19 yards) in gusts reported at 40 to 50 mph: Associated Press game story, https://turnto10.com/sports/new-england-patriots/patriots-out-run-bills-in14-10-win-in-blustery-conditions; and Nora Princiotti on Belichick’s opponent-specific plans in The Ringer, Jan. 13, 2022: https://www.theringer.com/2022/01/13/nfl/bill-belichick-bespoke-game-plan-bills-patriots-nfl-playoffs.↩︎

  14. THV11 (KTHV, Little Rock) on Waffle House’s post setting Reid’s play card beside its menu: https://thv11.com/article/sports/nfl/superbowl/andy-reid-playcard-waffle-house-menu-super-bowl/85-1566f9b8-edba-4642-8716-ac5301f8773d.↩︎

  15. Jesse Newell, Kansas City Star (via Yahoo Sports), Feb. 7, 2025, on how Reid’s call reaches Patrick Mahomes through Matt Nagy (a shortened play name from Reid, then about 12 words from Nagy, aiming to reach the quarterback with 20 seconds on the play clock): https://sports.yahoo.com/andy-reid-matt-nagy-call-113000853.html. Nagy was the Chiefs’ offensive coordinator from 2023 through 2025; Eric Bieniemy returned to the job in 2026 (course fact sheet).↩︎

  16. Jeremy Fowler, “Stories from inside the offensive genius of Andy Reid and Kyle Shanahan,” ESPN, Jan. 30, 2020: https://www.espn.com/nfl/story/_/id/28592720/stories-offensive-genius-andy-reid-kyle-shanahan (Brian Hoyer on Shanahan’s unnumbered plays; Chad Henne on Reid’s index-card plays; Brad Childress on Reid’s Friday-night additions, “I’ll put on another 15, I’m feeling it”).↩︎

  17. The Associated Press obituary of Walsh (July 30, 2007) credits him with popularizing “the practice of scripting the first 15 offensive plays of a game”: https://www.nfl.com/news/hall-of-fame-coach-bill-walsh-who-won-three-super-bowls-with-th-09000d5d800879e1. Walsh’s reasons are set out in Bill Walsh with Brian Billick and James Peterson, Finding the Winning Edge (Sports Publishing, 1998); a coaching-association summary notes that he “believed in 10-15 opening plays but his script would often go into the 20’s”: https://www.alfca.com/post/bill-walsh-on-scripting-openers. Paul Brown founded and coached the Cleveland Browns and later the Cincinnati Bengals, where Walsh was an assistant: Pro Football Hall of Fame, Paul Brown: https://www.profootballhof.com/players/paul-brown/.↩︎

  18. Terez Paylor, “Here’s what goes into Andy Reid’s scripted first 15 plays,” Yahoo Sports, Feb. 1, 2020: https://sports.yahoo.com/heres-what-goes-into-andy-reids-scripted-first-15-plays-from-offense-173404777.html (the index card and red pen, first and second downs only, the Friday “Thoughts?” text, the Saturday meeting, Nagy’s quote).↩︎

  19. Chad Henne (Reid’s index-card plays, some with three players in motion) and Brad Childress (the Friday-night “another 15”), both in Jeremy Fowler’s ESPN piece of Jan. 30, 2020, cited in full in the note on Shanahan’s unnumbered plays above.↩︎

  20. Course calculation: nflverse play-by-play, regular seasons 2022–2025, runs and dropbacks with a down recorded; snap number counts each offense’s plays in the game in order; neutral = win probability 0.20–0.80 and more than two minutes left in the half. For the new-look panel, a look is personnel (backs then tight ends, from load_participation, plus a flag for six or more offensive linemen), quarterback alignment and number of backs (both from FTN charting); a snap’s look is “new” if the team had not used it in any earlier week of that season, counted from each team’s fifth game on so that early-season games don’t dominate. Personnel strings are coded differently before and after the 2022/2023 change of data source; the backs-and-tight-ends count is not affected.↩︎

  21. Quinn MacLean, “What’s on the Script? Looking at How Offenses Approach the First 15 Plays of the Game,” SumerSports, Oct. 18, 2024: https://sumersports.com/the-zone/whats-on-the-script-looking-at-how-offenses-approach-the-first-15-plays-of-the-game/. Its comparison uses all of a team’s plays, not only neutral situations, so the two calculations are not directly comparable.↩︎