Ghost-GK Sweeper v1 — Extended-Grid Goalkeeper Positioning (Rest-Defense)
Predicts where a league-average goalkeeper would position themselves, including the in-possession
high-sweeper regime that the standard Ghost-GK v1
model cannot represent. Point-estimate (ghost_gk_x/y) served as the exact pickle-free boosted
HistGradientBoostingRegressor mean.
Part of the silly-kicks soccer analytics library
(TF-60 rest-defense; ADR-083).
Why a separate model
The bundled Ghost-GK default/full variants train on a fixed goal-relative box x ∈ [0, 30] m
from the defended goal: a keeper who has swept far upfield falls outside it and is dropped as a
"sweeper rush". That is correct for normal in-goal positioning, but it makes the model hard-saturate
at 30 m — it cannot place an in-possession keeper who has pushed up to 30–45 m to blunt
a counter-attack (the rest-defense regime). This sweeper variant lifts the label ceiling to
x_max = 52.5 m so it represents the high-sweeper regime, for TF-60 rest-defense / TF-19 GKDV
counterfactual arms.
The frozen default/position_only/full variants are unchanged — this is an additive
variant; consumers opt in via from_variant("sweeper").
Variants
| Variant |
Repo |
Features |
Use |
sweeper (this repo) |
silly-kicks/ghost-gk-sweeper-v1 |
26 (velocity-bearing) |
Continuous tracking (Sportec, SkillCorner, Gradient Sports) |
sweeper_position_only |
silly-kicks/ghost-gk-sweeper-position-only-v1 |
21 (5 velocity features dropped) |
Velocity-less StatsBomb-360 freeze-frames |
Model description
- Point estimate only.
ghost_gk_x/y serve the exact boosted HGBR mean, reconstructed pickle-free
(baseline + Σ_trees leaf_value; no sklearn at inference). The KDE density read-out
(predict_density) is not supported on this extended grid and raises — the density path
stays on the default 30 m grid (ADR-083). Use compute_ghost_gk / serve_ghost_gk_positions.
- Grid: first-class per-model
GhostGridSpec — x ∈ [0, 52.5] m, y ∈ [18, 50] m,
0.5 m resolution (105 × 64 cells).
- Parameters-only, pickle-free: npz (tree ensembles + baselines) + JSON metadata + SHA-256
checksums. No per-sample training data, no raw provider tracking data (ADR-044).
- Hyperparameters:
HistGradientBoostingRegressor, 500 trees, max depth 8, 5-fold CV.
Metrics
| Metric |
Value |
| Held-out CV euclidean MAE |
1.135 m (per-provider: Gradient Sports 1.071 / SkillCorner 1.166 / Sportec 1.717) |
| Boosted-reconstruction parity vs sklearn |
1.14e-13 (exact — safe to publish) |
| > 30 m high-sweeper stratum MAE |
1.96 m (the sweeper places high keepers where the default is blind) |
| Training corpus |
179 games / 1048834 frames |
The > 30 m (high-sweeper) coverage of the training corpus is IDSSE/Sportec-dominated (11.5 %);
SkillCorner 0.24 %; Gradient Sports 0.0 % — see the data caveat below.
Both-axes goal-relative re-fit (silly-kicks 4.111.0 / ADR-089; training_commit=4bda048). These
weights were re-fit after the ghost-GK feature extractor was corrected to apply the full 180°
goal-relative point reflection (both axes) — the previous convention flipped only x, leaving the
signed-y features and the target gk_y in absolute frame coordinates, mislocating away-team keepers
in y. The served ghost_gk_x/y change (chiefly for away-team keepers) and the feature-contract +
chirality fingerprints change, so pre-4.111.0 artifacts fail closed on load. The aggregate MAE is
essentially unchanged (the correction affects the away-team subset).
Training data
179 matches / ~1.05M frames of licensed professional tracking (Sportec/DFL Bundesliga, SkillCorner,
Gradient Sports FIFA World Cup 2022). Only the learned parameters are published; no raw provider
tracking data is redistributed (parameters-only artifact). Trained from a clean, CI-green commit.
Gradient Sports goalkeeper clamp. Gradient Sports' tracking clamps the goalkeeper's
position to a hard 27.5 m from goal (a source-data limitation, verified on the raw provider data;
silly-kicks passes it through faithfully). Consequently GS contributes no high-sweeper training
signal, and any GS goalkeeper-depth analysis is invalid past 27.5 m. The high-sweeper regime this
model represents is learned from IDSSE/Sportec. silly-kicks flags this at conversion time via
validate_gk_position_clamp / GoalkeeperClampWarning. See docs/research/gs_keeper_clamp/.
Usage
import silly_kicks.tracking as tracking
# The extended-grid sweeper (velocity-keyed within its family on velocity-less frames)
model = tracking.GhostGkModel.from_variant("sweeper")
out = tracking.compute_ghost_gk(frames, model=model, home_team_id=1) # ghost_gk_x / ghost_gk_y
# predict_density is NOT supported on the extended grid (raises); use the mean/serve path above.
Input frames must be LTR-normalized (home attacks right). All coordinates are goal-relative.
Limitations
- League-average positioning, not a specific keeper's style; no shot-stopping ability.
- No KDE density on the extended grid (mean/serve path only).
- Inherits tracking-system noise; Gradient Sports keeper positions are unusable past 27.5 m (above).
- LTR normalization required; static per-frame estimate.
References
Le et al. 2017 (Data-Driven Ghosting, MIT Sloan); Dutta et al. 2024 (NFL Ghosts, arXiv:2406.17220);
Pospisil & Lee 2018 (RFCDE, arXiv:1804.05753). See the silly-kicks NOTICE for full citations.
More information
- License: MIT · Library:
silly-kicks (v4.111.0+ for these both-axes weights) ·
GitHub · ADR-083 / ADR-089.
TF-65 velocity-gap re-fit (4.134.0)
TF-65 velocity-gap re-fit (silly-kicks 4.134.0 / ADR-120; training_commit=a34e992). Re-fit on the dense-grid-corrected floor velocities (TF-65 removed fabricated through-gap velocity on broadcast providers — the prior F1b float32 re-fit at 4.128.0 / ADR-106 is superseded); the metrics table above reflects this re-fit, and the mirror is republished to match the wheel.