Quick-reference action items. Architectural decisions live in docs/superpowers/adrs/.
Last updated: 2026-08-12. Current release: silly-kicks 4.80.0 (ADR-051 D3 closed -- every identity-keyed direction site is re-keyed. Direction now comes from the GoalMap (sites serving BOTH teams) or acting_team_attacks_rtl (sites serving ONE), never from which team is labelled home; same_id(team, home_team_id) was correct only while frames were home-attacks-right and silently inverted otherwise. Scope was SIX sites, not the two the tracker recorded -- found by running the scope predicate rather than trusting the list, which had ratcheted 2 -> 4 -> 6 across three plan revisions. BREAKING: home_team_id removed from compute_defensive_line, compute_packing_metrics, compute_structural_pass_metrics, compute_player_influence, detect_line_breaking, six add_*/*_xfns surfaces across tracking/atomic/calibration/causal, and eleven per-Series helpers in features.py — those last are the finding worth carrying: they never call same_id, they only forwarded the argument, so the predicate that correctly bounds the DEFECT does not bound the API MIGRATION, and the second set is strictly larger. Also BREAKING: acting_team_attacks_rtl returns a NULLABLE boolean — a resolved left-to-right team and an unresolvable one used to be the same False, so no consumer could tell them apart; 21 call sites now state their policy explicitly. It paid for itself immediately, exposing an is_ball.astype(bool) string-qualifier trap that had been selecting NO player rows for string-is_ball providers, and a duplicate resolvability test that disagreed with the authority in both directions (deleted, not repaired). Consequence to expect: UNORIENTED frames now yield NaN where they used to yield a confident wrong number. Also BREAKING, and larger in call sites than the re-key itself: every DEAD home_team_id in the direction family is gone — 25 signatures (8 dead at the base commit, 17 killed by the cascade; 62 across the whole cycle), residue from earlier re-keys that removed the use and left the parameter, driven to a fixpoint because the dead ones formed forwarding chains. AST-verified unread before removal, so no value moves. Two fences were checked, not assumed, and went opposite ways: add_xt_gk's "signature parity" rationale was measurably stale, while _off_ball_runs_kernel KEEPS its unread copy because its Gate B green is the standing measurement that it is unread. Per-version history lives in CHANGELOG.md.
Items are ranked top-to-bottom by specification completeness, then by additional implementation effort needed. No blockers — each build still gets its own brainstorm → spec cycle before code. Full course-derived detail (exact definitions, verified repo deltas, spec-time decisions, validation plans, rejected alternatives) lives in docs/superpowers/specs/2026-07-16-soccermatics-pro-future-work-plan.md.
| Size | What it means |
|---|---|
| Monstah | Multi-phase epic |
| Wicked | Looks small, surprisingly impactful |
| Dunkin' | Quick run, keeps things moving |
| # | Task | Size | Source | Notes |
|---|---|---|---|---|
| TF-24 | Re-run the tracking-defaults calibration on CORRECTED geometry | Wicked | silly-kicks (ADR-009; ADR-051 spec §11.7) | The ADR-028 cycle moved away-team geometry under TF-24's feet. Every sweep to date consumed at least one mixed-convention input: RC4 fed it SkillCorner frames with team_attacking_direction NULL on 100% of rows, and RC1/RC2/RC3 changed cover-shadow, _gk_geometry and space-creation values for away actions. So any Stage-2 recommendation on record was computed on geometry that no longer exists. What is ALREADY settled — do not re-measure it: no shipped constant is invalidated. Stage 1's infer_ball_carrier params are tolerance_m=3.0, beta=0.0, gamma=0.25, of which only beta and gamma are Optuna-calibrated — at a held tolerance_m=3.0, an engineering default (_ball_carrier.py:352-353) — against a fold that included the unoriented SkillCorner frames. They stand unchanged because carrier inference is orientation-invariant, now asserted by tests/tracking/test_ball_carrier.py::test_carrier_inference_is_orientation_invariant (40/40 identical assignments under an exact point reflection) instead of by an unsourced figure. Stage 2's k3 (pressure.py:61) and min_displacement_m (_off_ball_runs.py:100, _run_values.py:121) ship as engineering defaults TF-24 never set. What remains is therefore a recommendation refresh, not a defect repair: re-sweep so the harness's advice is derived from corrected frames, and decide separately whether any recommendation should become a default (ADR-009's standing rule is that TF-24 recommends and never changes library constants). DGX; naturally sequenced AFTER the ghost-GK re-fit below so one corrected-geometry corpus pass serves both. |
| TF-51 Item 4 | Defensive-credit atomic-SPADL mirror | Wicked | silly-kicks (ADR-047 §11; split 2026-07-24) | Its OWN representation-port spec — distinct from Track B. Needs the _packing_atomic_adapter-class lookahead bridge synthesizing std type_id+result_id from the next atom, a documented preserve_native=[…] caller contract + loud raise, per-representation window semantics (atomic is denser, so recovery_max_actions means something different), and a PREREQUISITE fix for the duplicate "interception" id in atomic/spadl/config.py (std idx 10 + atomic idx 24 → the dict-comp keeps 24, silently excluding std interception). compute_bravery's atomic mirror rides with it. |
| TF-51 Track B | Defensive-credit DPA / role-responsibility model | Monstah | silly-kicks (arXiv:2606.19931) | The Paper 2 model — its OWN later spec, distinct from Item 4. xDT-valued Defensive Pressure Area + R=(r−d)/r proximity distribution (r=5 m) + formation-role averaging (template-match + Hungarian, 20-role taxonomy) → "expected involvement" = responsibility, replacing v1 nearest-defender attribution; + the individual cross_block credit rule (folds in). New primitives: formation/role detection + failed-pass expected-receiver (Power 2017). Pass-only + aggregate-grain → complements v1. Needs the reference-code read (github.com/jonas-bischofberger/defensive-network) before spec. |
| — | Ghost-GK re-fit onto the canonical box constant (completes ADR-050) | Wicked | silly-kicks (ADR-050 §6) | DO THIS NEXT — it is the PR that finishes the penalty-area unification. _ghost_gk still uses 40.3 (half 20.15) while spadlconfig is the Law's 40.32: its weights were fit on the old value and attackers_in_box is a real trained feature, so flipping the constant without a re-fit is train/serve skew. The pin is a MECHANISM now, not prose — the artifact records the constant, so an unaccompanied flip makes load() raise. Blocked ONLY on compute (DGX + pining token for the 179-match owner corpus); the 4.64.0 cache_token() work means the re-fit can no longer silently reuse the 40.3-extracted features. Steps: (1) flip the constant; (2) migrate _ghost_gk onto in_penalty_area_goal_relative, incl. its strict < → non-strict boundary; (3) re-extract + re-fit; (3b) max_abs_delta = 0.0 across all 69 features of all three bundled contracts (docs/research/pr5_platform_atol/, run_commit 35a6667), so atol=1e-6 / rtol=0 stands and this re-fit INHERITS the baseline rather than owing one. Two caveats travel with it: the legs confound architecture with interpreter, and atol cannot transfer to the quantized xCross features. Re-measure only if the ghost extractor's own probe changes.; (4) re-stamp on x86 via scripts/stamp_feature_contracts.py; (5) validate + ship. Sequence this AHEAD of any downstream recompute, so the new weights are picked up in that pass rather than forcing a second one. |
| TF-30 (b) | Cover-shadow RQ1 real-data validation → then σ/λ recalibration | Wicked | silly-kicks (Cascioli et al. 2025 §RQ1; TF-24) | Own research cycle, separate from (a); recalibration queued BEHIND it. The design doc says a false-positive test is impossible because it "requires ground-truth pass outcomes we don't have in fixtures" (2026-05-10-tf30-cover-shadows-design.md:485-497) — true of fixtures, false of real data. A blocked pass is never attempted, so the paper evaluates indirectly on passes that WERE played: predicted-blocked + actually-completed = false positive; predicted-open + failed = false negative. Feasible today — outcomes are already in SPADL result_id, scripts/validate_*.py is an established convention (6 existing), pining GS WC2022 is the obvious cohort. Deliverable = a REPORTED-not-gated research artifact in the docs/research/xcross_causal/ mould; converts the current 10–60% plausibility smoke band into measured precision/recall. Their recovered class-conditional rates for comparison — Lane Control (Majority) recall 36.9% / precision 22.0%, (All) 34.2% / 28.8%; balanced accuracy 68% is ~98.5% specificity averaged with ~37% sensitivity, i.e. a low-false-positive filter, not a detector. ⚠ Those rates are DERIVED from their Appendix B, not printed — recompute independently rather than trusting the handoff table, and expect their Table 1 caption (98.74% successful) to disagree with Appendix B (98.84%). Fair caveat both ways: a good passer threading a genuinely screened lane scores as a false positive by construction, so precision is a lower bound. Their Appendix C prices our SoccerMap substitution — pass-selection accuracy SoccerMap 0.536 / XGBoost 0.547 / naive nearest-teammate 0.366 (do not cite their SoccerMap LogLoss 6.521; not credible for an ~11-way choice). THEN, blocked on the above: recalibrate CoverShadowParams σ=0.20 / λ=4.3 — the paper fitted them on StatsBomb-360 freeze frames with estimated velocities (event-frequency position deltas), we run 25 Hz measured velocities, a different noise profile entirely. Deferred to TF-24 with that reason written down; this harness supplies the objective function TF-24 needs. Separately sized — do not start it before the harness reports. |
| — | Metrica GK identification: derive once per match, not per batch | Wicked | silly-kicks (PR-S105 follow-up) | Metrica is anonymized (no roster) so is_goalkeeper must derive positionally — giving it the same per-250-frame-batch contamination the 4.38.0 SkillCorner roster-trust fix cannot help (a transiently goal-parked outfielder gets flagged; the union grows across the lakehouse's batch builds). Needs a derive-once-per-full-match path: a separable silly-kicks API (derive-once / accept pre-derived is_goalkeeper picks) + a lakehouse change (derive once, feed picks into the per-batch builds). The S2 n_implausible_gk_teams guard already surfaces it. |
| — | SkillCorner keeper-origin: real-data validation + S1/S4 CI rate-gates | Wicked | silly-kicks (ADR-024 4.37.0 / PR-S104) | On real pining SkillCorner data (DGX): confirm goal-kick origins ≈100% own-box + scatter-SD collapse; validate-then-maybe the open-play own-half misdetection bound (add a generous own-half bound only if pass origins still land in the attacking half — beyond → unresolved, never clamped); measure-before-optimize the _tracking_gk_xy_detected ±window loop. Then land the standing CI rate-gates (a count nothing routinely checks is the silent-guard failure mode): (i) S1 off-pitch rate → player-margin / ball TOL + a batch/CI gate hard-failing above the measured baseline + margin; (ii) out-of-region native-goalkick rate → a CI gate asserting no provider exceeds a small %. Refs docs/superpowers/{specs,plans}/2026-06-30-skillcorner-keeper-origin-resolution*. |
| — | ADR-code reconciliation sweep (recurring, once per minor release) | Dunkin' | silly-kicks | Periodically verify documented ADRs (docs/superpowers/adrs/ADR-*.md) still match the codebase — stated constraints (e.g. "zero Spark imports in domain") hold in practice, superseded decisions are updated. |
| — | Course-derived validation/QA bundle (xT solver cross-checks · magnitude anchors · pass-risk calibration · retention diff · repeatability harness · chain-convention check) | Dunkin' | Soccermatics modules 3/8/10/15; Twelve glossary | (a) Test-side exact-solve oracle np.linalg.solve(I−A, g) (+ optional seeded MC) vs value_iteration on the course's toy chain (A=[[.25,.20,.10],[.10,.25,.20],[.10,.10,.25]], g=[.05,.15,.05] → ≈[.150,.252,.120]) AND a real fitted grid; perturb transition → red; keep monotone-from-below raw-diff convergence untouched. (b) xT magnitude ORDER/ratio plausibility gates from Sumpter's published fit (goalmouth ≈37.7% ≫ deep corner ≈0; GK zone ≈2.5% between own-half 0.5% and mid-pitch 1%) — absolute values doc-only (corpus-dependent). (c) Pass-risk calibration report = pitch_control_at_target + result_id through the extra-free _calibration_metrics.py (AUC/ECE/slope; owner-run script or @e2e); flags the "technically complete, functionally lost" low-p tail. (d) Diff xtgk/_retention_labels.py::retains() vs the course 5 s reference rules (shot→retain; out-for-set-piece→lost; ignore-list; Twelve 2-touch recovery rule) + optional statsbombpy-gated @e2e fixture (WEuro-2025 QF: 76→70 analysable→80.0% retained). NOTE: the packing-seam hardenings (foul/NaN-team skip, both-attested possession) were APPLIED 4.50.0 with a probe-verified zero-training-label-flip gate → no retrain (ADR-036 amendment 2026-07-17); only the course-rules diff remains here. (e) Season-repeatability harness: pure-numpy Type-II/major-axis regression + Pearson r (longitudinal complement to the ICC discipline); blocked on multi-season data; documented caution: G−xG ≈ zero year-to-year r — never surface as "finishing skill". (f) Verify VAEP window="possession" vs the 2-consecutive-opponent-touch chain-break + foul-stitching convention; confirm whether xGChain is materially covered — else add it as a near-zero-cost extra baseline for the xT-GK v2 construct-validity harness. |
| TF-53 | Match-outcome simulation — win probability / xPoints from per-shot xG | Dunkin' | Twelve match report (Soccermatics module 3) | Injected per-shot xG (port pattern — silly-kicks ships no xG) → per-team goal distribution = Poisson-binomial over that team's shots (exact DP convolution; MC optional for parity) → joint under independence (document the assumption; the course's model shares it) → P(win/draw/loss) + xPoints = 3·P(win) + 1·P(draw). Face-validity anchor: Arsenal 33/25/41%, xPoints 1.25, in a match won 1-0 on lower xG (1.57 vs 1.72). Analytic fixtures: probabilities sum to 1; symmetric shot lists → symmetric probs; single-shot edges. Small pure module; event-only; C4: new small container, no aggregator. |
| TF-54 | "Van Dijk" territorial-dominance metric — trimmed defensive-area hull + xT into area | Dunkin' | Sumpter/Twelve Earpiece (Soccermatics module 10.2) | Defensive area = convex hull of the 70% of a player's own-half defensive-action locations nearest their centroid (trimmed hull; per player per match/season window). Opposition passes targeted into the hull → success/fail (forward flagged) × xT: xT-conceded (completed) vs xT-prevented (course worked 0.93 in / 0.11 prevented / 0.35 net but never formalized "prevented" — spec pins it; proposal: prevented = xT of failed opposition passes into the hull). Separates "territory opponents can't pass into" from "excellent once the ball arrives" — two orthogonal defensive qualities. Event-only + injected fitted ExpectedThreat; scipy ConvexHull + point-in-hull; SPADL defensive-action set (tackle/interception/clearance) pinned in spec. |
| TF-55 | Glicko-2 duel-rating module (StatsBomb HOPS pattern) | Dunkin' | Glickman (Glicko-2); StatsBomb HOPS (Soccermatics module 10.1) | Pairwise rating updates (rating + RD + volatility; pure-python ~100 LOC, no new dep) over winner/loser duel outcomes. sportec already emits tackle_winner_*/tackle_loser_* (ADR-001 — qualifier-derived dedicated columns); SPADL tackle/take_on adjacency (result decides winner) as the derivation helper for other providers; aerial duels are provider-specific (no SPADL type) — start with ground duels, document coverage honestly. Rating period = match. C4: new module, not an action-coupled aggregator. |
| TF-52 | Event-only team-KPI module (Twelve match-report glossary + Tigres/Clemson practitioner set) | Wicked | Twelve.football glossary (Soccermatics module 3); Bootcamp Webinar 3 (Tigres Femenil + Clemson); Coventry academy KPI; MSC module 1 | v1 (Twelve glossary, exact definitions + worked values in the plan doc): PPDA (opponent passes in their defensive 60% ÷ our defensive actions there; 4.17), defensive intensity (def. actions/min out of possession; 7.16), field tilt % (63%), pass tempo (passes/min of possession; 19.97), defensive-action/recovery/turnover line heights (mean x; 41.15/66.68), time-to-defensive-action/-recovery (7.30/7.69 s), recoveries + within-5s % (14%), possessions-retained-after-5s (62%), conversion chain (poss→final third→box→shot), long-ball % (>32 m own half), high-opportunity shots (npxG>0.15; injected xG), "within 10 s after recovery" windowed family. v2 (practitioner set): counter-press window as seconds= XOR passes= with named presets (Barcelona ~6 s; Coventry regain-5s + compact-after-7s; RB Leipzig 10 s-to-shot; Tigres "Hunt" = regain ≤3 passes, 60%+ standard); interception-height High/Med/Low (line-relative: High = only opponent back line remains in front); 6-state build-up outcome taxonomy (worked 19→7, 36%); breakout-past-halfway by 3 channels; switch-of-play-conditioned press success; aerial first→second-ball chaining; post-regain security (2nd-pass completion, failed first pass, fwd vs back/side option); compactness-recovery-time (the one tracking-flavored item — team_shape consumer); block-classification anchors (high press ⇔ line near halfway; low block ⇔ 18-yard box). Possession attribution + possession-minutes = the definitional spec section (decide once, use everywhere). Boundary: single-source definitions in the library per delete-and-depend. No retrain; C4: no action-coupled aggregator. |
| TF-50 | Physical/locomotor metrics (speed bands, HSR/sprint efforts, accel events, PSV-99, distance) | Wicked | Soccermatics module 13; SkillCorner physical-data glossary (skillcorner.crunch.help); docs/research/skillcorner_corpus/schema_new_physical.txt |
Constants as taught: running 15–20 / HSR 20–25 / sprint >25 km/h (high-intensity = HSR+sprint); efforts ≥1 s sustained; accel/decel events 1.5–3.0 m/s² sustained ≥0.7 s; PSV-99 = 99th pct of pooled per-sprint top speeds (robustness construct; raw max unreliable); >12 m/s ⇒ tracking error; m/min; prefer m/s (÷3.6). Build: extend derive_velocities with deriv=2 → ax/ay/accel_magnitude = vector norm (never a scalar speed-diff — the exact circulating-reference-code bug the course flags); bout detection via run-length encoding (period + undetected-frame breaks); pure per-(game, player) summary module — NOT action-coupled, C4 aggregator count unchanged; frozen params dataclass with for_provider promotion; visibility gating on the native route's is_detected (ADR-038; the kloppy gateway is unusable — hardcodes visibility: None) + per-player coverage fraction (total distance weakest under broadcast tracking, PSV-99 most robust — echo in docstrings). Tier-1 validation: recompute the SkillCorner corpus vs the unconsumed physical.parquet (29×36 per player-match; respect physical_check_passed; PR-S86 roster-validation pattern; coordinate with part-deux/Stage B for corpus access). Genre-expansion scope call goes in the ADR. Additive; no retrain. |
| TF-19 | GKDV — GK Deterrent Value composition + audit harness | Monstah | Le et al. 2017 (ghosting); arXiv:2505.11841 (causal-crossing); arXiv:2512.10355 (DEFCON-GNN — prevention-focused EPV-reduction); novel synthesis — closes 2026-05-01 deterrent-investigation literature gap | GKDV Layer 3 — headline metric. STATUS: PARTLY SHIPPED (physics arms 4.53.0, ADR-043; attempt arm gated tf19_ready=false — a routing decision on GK feature-engineering, not 'no signal'). This row tracks status + remaining work; the arc, the full gate/measurement history and references live in the GKDV research program note just below — they complement, not repeat. Shipped: the silly_kicks/gkdv/ gate-independent physics arms (build_ghost_frames / provenance_to_targets + delta_das + delta_threat_suppression, attacker-value units so negative = deterrent). The §6.1/§3.3 corpus runs are COMPLETE (4.68.0 — results, provenance and full reasoning in docs/research/{tf19_signoff_power,tf19_entanglement,tf19_pr3b_xs_v2}/ and CHANGELOG 4.68.0): the ICC gate's detection precondition is DISCHARGED, N_MIN_MATCHED is measured-and-None (underpowered, not unmeasured), and §3.3 entanglement is measured inside_band — closing ADR-037 F6 by confirmation. REMAINING: (a) owner validation run — held-out expected-sign test on known sweeper- vs line-keepers (Alisson / Neuer should score strongly negative; Ter Stegen played 0 WC2022 min; Onana is descriptive-only under the ≥2-match rule); (b) spec §6.4 Layers 0–3, the composed headline metric PR-3 excluded (two PR cycles + its own ADR; ~400 LOC + audit notebook + held-out validation set). Design for the remaining work: Δ_attempt(action) = P(action | actual_GK) − P(action | ghost_GK) for action ∈ {shot, cross, key_pass}, weighted by realized-or-expected outcome value and summed across the build-up window (negative ⇒ deterrent); the counterfactual MUST substitute predict_mean() (the deterministic boosted HGBR in ghost_gk_x/y), not the predict_density KDE mode, so no train/serve backend pin is needed (ADR-016). Depends on: TF-15, TF-16, TF-17, TF-18. |
GKDV research program (TF-15..TF-19). TF-15 through TF-19 form a coherent research arc towards the GK Deterrent Value metric — a per-frame measure of how much the defending goalkeeper's actual position depresses opponent attempt-probabilities (shot / cross / key pass) relative to a league-average "ghost" GK in the same frame state. Origin: 2026-05-01 deterrent-effect investigation; closes the published-literature gap that no GK-evaluation framework today (StatsBomb four-pillar, Lamberts GVM, Sloan SAC data-driven framework, Anzer & Bauer xGOT, Driblab Goals Prevented) measures positioning-as-deterrent. Foundation: TF-7 (pitch control) + TF-13 (frame-based GK ID) + TF-14 (defensive-line geometry). Layer 1: TF-15 (threat-weighted GK influence primitives; shipped 3.10.0). Layer 2: TF-16 / TF-17 / TF-18 (decision-probability surfaces for shot-occurrence and cross-attempt + ghost-GK regression; TF-18 fully shipped 3.24.0 — model code 3.19.0, training pipeline 3.20.0, bundled trained weights 3.24.0; TF-16 FULLY shipped — code 4.1.0, trained weights 4.9.0 (PR-S80): bundled public xS model trained on 81 matches against the 4.7.0 carrier defaults; the pre-registered two-candidate test found owner-tier GS data degraded public-held-out PR-AUC in all 5 folds, so the reproducible public-only model shipped; TF-17 fully shipped — code 4.11.0 (PR-A), trained weights + causal ATT/ATNT harness 4.18.0 (PR-B/PR-C); see the Sequencing note below). Layer 3: TF-19 (composition + validation harness) — attempt arm GATED; physics arms SHIPPED (ADR-043, PR-3). Re-gate RAN and shipped (PR-S114 code 4.47.0 + PR-S118 weights 4.51.0; ADR-037 / ADR-040). Cross-arm verdict gated_clean_fail; the xS arm was NOT measured (below). The pre-registered TF-19 viability gate (_xcross_eval.gk_substitution_probe, ratio ≥ 2.0 AND absolute floor ≥ 0.01, frozen before the run) FAILED on the real corpus: every figure previously quoted here (median 0.00107, ratio 2.59×, floor miss ~10×) was from the retired 4.18.0 weights and is superseded by PR-S118. Measured on the artifacts as of 4.51.0 — bundled default (Stage A public): gk_median_abs_delta 0.002417, ratio 1.41× the nearest-defender control, i.e. it MISSES the ratio prong (2.0× bar) and sits 4.1× short of the 0.01 absolute floor; Stage-B sc_extended (+98 owner SkillCorner matches, NOT bundled): 0.009697, ratio 2.21× — it clears the ratio prong but misses the floor by ~10% relative. tf19_ready: false on BOTH (the gate is an AND). Note the retrain made the GK signal stronger absolutely (0.00107→0.002417) yet worse on the ratio, because the nearest-defender control grew faster (0.000414→0.001718) — so the earlier “clears the ratio leg” reading is now false for the shipped model. sc_extended is published and independently checkable as of 2026-07-18 (silly-kicks/xcross-attempt-v1, silly-kicks/xshot-occurrence-v1; from_variant("sc_extended") verified end-to-end through the fail-closed loader). "tf19_ready": false is recorded in silly_kicks/tracking/_xcross_weights/default/metrics.json, and the ADR-015 causal harness recorded gk_clears_placebo_band = False — but that artifact (docs/research/xcross_causal/metrics.json) was re-run in 4.74.0 on the corrected geometry (run_commit 35a6667) and gained provenance for the first time; gk_clears_placebo_band remains False, so it now corroborates the CURRENT model. Note its corpus also changed between the two runs (23,966 -> 52,978 opportunities; 669 -> 4,193 treated), so the magnitudes are not a before/after of the geometry fix -- only the verdict is comparable. ADR-011 states the consequence: TF-19 consumption is gated on GK feature-engineering. The xS arm was unmeasured at 4.51.0 (resolved by v2, below) — its bundled metrics.json carried no substitution probe, no GK-block ablation and no permutation importance (contrast xCross, which carries all three). This is now blocked, not missing: the registered xS probe rule and its locked constants shipped in PR-1 (tracking/_model_eval.py::evaluate_xs_probe, PROBE_WRAPPERS["xs"]), but xs_substitution_probe consumes ghost-substituted targets from the silly_kicks/gkdv/ engine, which is ADR-037's PR-3 — shipped in THIS release (ADR-043). The probe is therefore unblocked; PR-3b RAN it 2026-07-22 — docs/research/tf19_pr3b/ (64 GS matches, baseline ed20ac7≡4.55.3 for this arm): verdict no_valid_placebo → re-gate unmeasurable_at_dose, but NOT a null effect — the GK effect is real + dose-responsive (median |ΔxS| 2m→0.0154 / 3m→0.0200 / 4m→0.0222, ≈3.1× the nearest-defender control, only 5.3% zero-fraction). The blocker is a degenerate random-outfielder placebo (placebo_p95=0.0, 66.5% of placebo deltas zero — the aggregate xS features barely respond to a single distant player's 2m move), so the probe can't certify the apparent effect. ✅ RESOLVED (xS-placebo v2, code 4.58.0/PR-S129 + run 4.60.0/PR-S131): the model-relevant-defender placebo IS the principled GK-appropriate null — v2=pass (ρ=0.436/p=0.001, re-gate joins_with_caveat); the methodology gap is CLOSED and the xS arm is now MEASURED. The two attempt arms now DIVERGE: xS pass/joins_with_caveat (real dose-responsive effect, causally entangled inside_band) vs xCross gated_clean_fail (tf19_ready=false) — a TF-19 spec must reflect that, not assume both are dead. §6.1/§3.3 CORPUS RUNS — RESULT (4.68.0, 2026-07-28/29, all clean-provenance). The two §6.1 legs SPLIT: the ICC leg discharges its registered detection precondition (power 1.0 at all three ICC_ANCHORS; mean_observed_icc_at_zero = −0.00034, so it detects rather than manufactures; 41 keepers, 8 single-match and reported), closing ADR-037 F2 — the promise that had shipped as a docstring no code could keep. The ATT leg does not: N_MIN_MATCHED stays None but now means measured and unreachable (max power 0.055 vs the required 0.80, every anchor, both outcomes; 37,086 spells / 151 treated, prevalence 0.0041), and the degenerate counts are what make that legible — 0/200 at n≥4000, an estimable design with no power rather than a positivity failure. Per §6.1 the response is adjust floors/sampling FIRST; the 16.5 m Layer 2 threshold is not retuned to raise prevalence (Law-defined so the decider stays untuned — re-registration, not implementation). This vindicates F3, which split the two estimands: they answer OPPOSITELY, so a merged criterion would have had one wrongly veto the other. §3.3 entanglement MEASURED inside_band (179 matches / 98,789 opportunities; GK ablation shift −0.006999 vs cluster placebo band 0.004690 vs the registered 0.01 floor — it exceeds the permutation null but not the absolute floor) → regate_verdict(shot, pass, inside_band) = joins_with_caveat. The measured value EQUALS the default 4.60.0 assumed, so F6 closes by CONFIRMATION, not reversal — the verdict is unchanged but now rests on two measured inputs. The xS-v2 probe was re-run with verifiable provenance (the 4.60.0 artifact stamped a bare git rev-parse HEAD, which reads clean on a dirty tree, and carries no run_tree_dirty field) and reproduces v1=no_valid_placebo, v2=pass. Getting the §3.3 run to execute at all required 4.66.0: pooling providers exposed that game_id is int for GS and str for the others, and that game_id alone is not a valid cluster key for a pooled arm — the crash was the lucky failure mode, silent cross-provider cluster fusion the other. Sequencing / status: the current shipped-vs-remaining state — physics arms shipped 4.53.0; attempt arm gated (tf19_ready=false); PR-3b ran → unmeasurable_at_dose; owner validation run + spec §6.4 Layers 0–3 outstanding — is tracked in the TF-19 row above; this note carries the evidence and rationale behind that status, not a second task list. External complements: DAS (Bischofberger & Baca 2026, accessible-space on PyPI; TF-28, shipped 3.8.0) provides physics-based counterfactual space valuation; cover shadows (Cascioli et al. 2025; TF-30, shipped 3.11.0) provide lane-specific pass-obstruction metrics. Both can serve as independent cross-checks on the probability-surface decomposition in TF-19.
Surfaced by the audit (shipped 4.75.0 / PR-S143 / ADR-053), which deliberately reports rather than repairs. Report: docs/research/sb360_coverage/.
- The
visible_areaCONSUMING seam exists; WIRING it into the count features does not. 4.77.0/ADR-055 shipped the primitives --point_observed(bool | None),region_observed_fraction(an(M, 2)polygon, never a bbox) andadd_visible_area_coverage-- so the previous framing of this row ("a seam for the DATA but not for CONSUMING it") is discharged. What remains is deliberately NOT done:defenders_in_triangle_to_goal,receiver_zone_densityandnearest_defender_distancestill treat "nobody there" and "nobody VISIBLE there" as the same observation. Wiring them changes existing values AND decides for the consumer what a partial observation means, which is the ADR-009 line -- so it needs a consumer asking for it, not a library decision. - Four boundary entry points are unaudited, each with its reason in
tests/sb360/test_registry_surface.py::UNAUDITABLE_BOUNDARYbehind a strict xfail. The blocking one isxtgk.compute_xt_gk_v2: it needs an xG-calibratedMarkovPossessionValueport and silly-kicks ships no xG model, so any port supplied would audit the stub rather than the library. - Check whether the lakehouse already ingests StatsBomb open data. The question has CHANGED now
that 4.76.0 ships
providers/statsbomb: it is no longer build-or-reuse but whether the lakehouse should ADOPT the port so both read SB360 the same way. Not answerable from this repo. - SB360 goal-kick frame availability is the collaboration's real constraint — only 32.6% of goal kicks carry a freeze-frame (per-match median 21%, IQR 18–50%, range 8–61% over 16 matches), while shots and saves carry one ~98% of the time. Not a code issue; a planning input. Extending the pass beyond 22 matches is a driver flag, and the shards are additive.
sportec_slim.parquetis MIRRORED relative to its own direction labels (found 4.77.0).team_attacking_directionsaysDFL-CLU-00000Pattacks +x, so it should defend x=0, while that team's keeper mean x is 98.1 (p1) and 77.0 (p2). Confirmed independently byorient_frames_to_ltr_by_geometry, which MIRRORS this slice. Only sportec: gradientsports agrees; metrica/skillcorner have one-sided keeper coverage so the check is inexpressible there. Recorded as a strict xfail (test_provider_inputs_convention.py::test_direction_labels_agree_with_keeper_geometry), so repairing the slice is forced to delete the marker. Repair movessportec_expected.parquetand the lakehouse-parity goldens, which is why it is its own change.
- Missing ball-touch detection to enrich event↔frame sync (candidate future enhancement; anchors on
TF-43 ELASTIC). External prior-art PathCRF (KDD 2026, arXiv:2602.12080; code
github.com/hyunsungkim-ds/pathcrf, MPL-2.0 → reimplement, do NOT lift into MIT) recovers ball touches that provider event feeds DROP: run RDP (Ramer-Douglas-Peucker) on the ball trajectory → direction-change points = candidate touches, then Needleman-Wunsch align them to the ELASTIC-synced events and insert the unmatched points as extra touch events. A pure tracking-derived primitive (ball trajectory → candidate-touch timestamps) that fits the hexagonal style and anchors ontracking/_elastic_sync(TF-43). Value: improves event↔tracking sync / catches dropped touches for any provider — independent of the (separately-recorded, lower-priority) tracking-only-ingestion front-end option. Also serves the lakehouse sync layer. Needs owner scoping (silly-kicks TF-43 enhancement — new TF-number — vs a lakehouse pipeline step); no spec/plan yet. Source + the front-end option recorded in the part-deux session'sreference_pathcrf_event_detection.md. - Re-confirm
test_xshot_gradientsports_e2eunder xgboost 3.x at the next xS re-fit. The 4.51.0 pass ran under local xgboost 2.1.4 vs the shipped 3.2.0 artifact; thebase_score2.x/3.x guard handles the skew but the pass hasn't been reproduced under 3.x (owner-run, real GS data, never in CI). - xT-GK v2 interpretation-fork decision (owner/Eyestone; blocks any further v2 metric work).
Re-measured in 4.46.0 (PR-S113) on RESOLVED origins — the 4.45.0 numbers were taken on a cohort where
~24% of the GS GK-distribution domain was scored at a fabricated grid zone, so they are superseded. On the
corrected cohort (
docs/research/xtgk_v2_construct_validity/README.md): (a) the "keeper-flat" leg is WITHDRAWN — GS keeper-discrimination ICC v2 −0.0020 → +0.0256, now above v1's 0.0193 (a fabricated origin is keeper-INDEPENDENT, so it compressed between-keeper variance toward zero); (b) the outcome-AUC leg STANDS — lift GS −0.1474 / SC −0.0268, i.e. v2 still loses toraw_completion. Verdict: still not construct-validated by outcome-AUC, but no longer keeper-flat. The two flagged forks remain the candidate explanations for the remaining gap and still need a decision before more v2 work: (1) V reward =E[first-shot xG]vs Jeff §2.1 remainder-of-possession threat (would require re-implementing V); (2) PEV dormant (p′=p) pending the receiver-pressureqframes-side feature (would light up the pressure-value term). Both out of scope until the decision — which can now be taken on trustworthy numbers. Lakehouse: re-materializext_gk_v2_*+ adoptapply_resolved_gk_geometry(opt-in — not a forced VAEP retrain). - Ask SkillCorner which pitch length their coordinates are normalised against (PR-S115 / ADR-038 §4;
2026-07-14). The metadata declares
pitch_length(104 m on the affected matches) and our events converter has always used it; kloppy's tracking map is non-affine and assumes ~103.5 m effective (measured 103.48 m / a reviewer's 103.71 m — the residual is a fit artefact because the map isn't affine). Nobody has characterised what kloppy's SkillCorner transform actually does. If the declared length is NOT the one the coordinates are normalised against, events and tracking are both off by the same small factor and this cycle does not fix it. The cheap empirical routes are closed (image_corners_projectionall-null; no fixed-geometry set-piece landmark in the taxonomy). Provenance is the registered choice until answered; carry it as a question to SkillCorner. - Gradient Sports own-goal codebook confirmation (residual from 4.13.0 / ADR-018; blocked on an
external artifact). The shipped
RE+G≡ own-goal rule +nonEventvoided-event semantics rest on the full WC2022 empirical catalog (the PFF FC Change Log is not a semantic data dictionary); confirm against an official PFF codebook if one becomes available, and whether own goals can surface under other event types (e.g.CLdeflection — none seen in WC2022). flat_zonesnan_okhardening (deferred from 4.46.0 / PR-S113). The NaN→zone-176 trap is currently closed by a corrected docstring plusfinite_coord_maskat the one scoring seam. Anan_ok: bool = Falseparameter (default raises; the three NaN-tolerant fit seams_markov.py:65,_empirical.py:83,_diagnostics.py:123passTrue) would make the pit-of-failure structurally hard to enter rather than merely documented. Deferred because it perturbs the exact fit seams whose byte-identity licenses "the deep-zone gate need not be re-run" (ADR-036 non-goal #1); do it in a cycle where that gate is being re-run anyway.
ReSpo.Vision tracking adapter — licensing-blocked. Track here when licensing clears.
TF-37 (Wicked): Pose-estimation features for xG, VAEP, and GKDV. Data-availability-blocked; high value when unblocked. Body keypoint data (trunk angle, knee flexion, foot orientation at ball contact) from pose estimation enables three concrete value-adds: (a) Player-adjusted xG — current xG treats all shooters as interchangeable; pose features at shot moment make xG per-shooter per-shot. Slots into lakehouse xG-v2 as additional input features. Hirn et al. (FAU Erlangen-Nürnberg, 2026; LISS SAC poster, paper forthcoming) demonstrated improved Brier score on 101 controlled-drill shots using tree-based models with pose features vs. positional-only baselines. (b) GK pose at shot moment — GK body orientation, set position, weight distribution at shot time is a stronger signal than centroid position alone. Feeds directly into GKDV research program (TF-15..TF-19); a GK leaning left vs. set square has different save probability regardless of (x, y). (c) VAEP pose features — body orientation at action time (not just shots) could inform pass technique quality, dribble body lean, aerial duel posture. New _frame_aware xfn category in tracking.features. Blocker: no tracking provider silly-kicks currently supports (Sportec, Metrica, SkillCorner, Gradient Sports, kloppy gateway) ships body keypoint data. When a provider makes pose features available, the silly-kicks schema (TRACKING_FRAMES_COLUMNS) would need per-player-per-frame keypoint columns or a linked keypoint DataFrame. Hirn et al. limitations: 9-participant controlled drill (not match data), right-foot only, self-acknowledged need for real-game validation. Watch for the published paper. Revisit if/when: a tracking provider supported by silly-kicks ships body keypoint features, or a new provider with pose data is added via kloppy. Origin: 2026-05-13 LISS SAC poster investigation.
TF-38 (Monstah): CDF center-origin coordinate system — switch internal representation from bottom-left [0, 105] × [0, 68] to center-origin [-52.5, 52.5] × [-34, 34]. Next-major (5.0.0) breaking change; bundle with other breaking-change candidates. (4.0.0 was consumed by the symmetric fail-loud ET-direction change, PR-S70 — ADR-010; the two breaking changes were not bundled because TF-38 is not yet ready: it has its own prereqs below.) The CDF coordinate system (arXiv:2505.15820, v1.0.0, Anzer/Bekkers/Davis/Van Haaren, Dec 2024) is already the de facto industry standard: 8/14 tracking provider aliases natively use center-origin meters (SecondSpectrum, SkillCorner, PFF, GradientSports, Sportec:tracking, HawkEye, Signality — per fast-forward alias table). LTR invariant is preserved (home attacks positive-x); only the origin shifts. Simplifies: GradientSports events/tracking + Sportec tracking become passthrough (drop +52.5/+34.0 offset); SkillCorner rescale simplifies; DAS _to_das_coords() deleted entirely; mirroring becomes negation (-x instead of field_length - x); midfield checks x < 0 instead of x < 52.5. Blast radius: every coordinate-touching constant, constraint, converter, feature, test, and downstream consumer. ~30 files, ~2800 test assertions. Prereqs: (1) inventory all 4.0 breaking-change candidates to bundle; (2) kloppy center-origin target coordinate system support (currently targets bottom-left 105×68). IEEE 3715 status: PAR approved 2025-11-04 (active working group, chair: Jesse Davis, IEEE EMB Society); not yet published as ratified standard. The arXiv paper (v1.0.0) is the pre-standard spec; IEEE ratification is in progress. The center-origin convention itself predates IEEE submission and is independently justified by provider convergence. Trained-model impact (xS TF-16 / Ghost-GK TF-18): NO retrain required. Both train on goal-relative features in physical units (metres/radians) via the shared _geometry transform, which is translation-invariant: once goal_x parameterizes to ±pitch_length/2, every goal-relative feature value is numerically identical pre/post origin-shift, so the booster/KDE weights (which live in goal-relative space) are untouched. TF-38 updates the transform code, not the weights — and this is a positive argument for training these models now rather than waiting for 5.0.0. Required of the TF-38 PR: (a) a goal-relative feature-parity regression gate (old-origin vs new-origin frames → byte-identical extracted features) that also proves no raw-coordinate leak in any feature; (b) honour each model's metadata.json-recorded pitch_length/pitch_width (added by the TF-16 weights cycle as the metadata template) and fail loud on mismatch. Caveat: invariance holds ONLY because TF-38 is a pure translation (105×68 m preserved); a future rescale or unit change WOULD force a retrain. Established 2026-06-02 (TF-16 weights brainstorm). Origin: 2026-05-26 lakehouse CDF investigation. CDF-as-input-format extension (added 2026-06-18): TF-38 adopts CDF coordinates internally; the sibling long-term direction is CDF as the canonical tracking input format (cdf.football/latest/tracking.html). Vision: the consumer (lakehouse) normalizes ALL providers' raw tracking → CDF tracking ONCE, stores it in a lakehouse table + publishes to HF (so anyone can load() standardized tracking), and silly-kicks treats CDF tracking as just another input — eventually the ONLY input (convert-to-CDF-then-compute-on-CDF), collapsing N per-provider adapters into one CDF→frames reader and moving provider-specific parsing entirely consumer-side (cleaner hexagonal boundary: silly-kicks grows ONE CDF reader, not N provider parsers). Gated on CDF maturity (IEEE 3715 PAR-approved 2025-11-04, not yet ratified — spec churn risk). The TF-23 SkillCorner/Metrica bronze->frame builders (SHIPPED 4.33.0, ADR-034; the TF-23b geometric orientation backstop followed in 4.34.0, ADR-035) were built CDF-ready for exactly this: their shared-builder seam is where a CDF reader slots in beside skillcorner/metrica. TF-23's own row was deleted on ship per grooming, so this no longer cross-references it.
TF-46 (Monstah): Set-piece (corner) defensive-role inference + role-conditioned ghosting. Research direction; strategic — silly-kicks has no set-piece layer today; needs its own brainstorm/spec before any build. Source: Groom, Belo, Rice, Anderson & Wang (2026), "A Machine Learning Framework for Off-Ball Defensive Role and Performance Evaluation in Football," arXiv:2601.00748. Core = a covariate-dependent Hidden Markov Model (CDHMM) inferring, label-free, time-resolved corner defensive roles (man-marking defender→attacker + zonal) + team/delivery-specific (inswing/outswing) zonal structures from tracking; coach metrics (marking tightness γₒ per 3×3 m bin, zonal dwell, β transition dynamics). Valuation: role-conditioned ghosts (counterfactual = average same-role/same-team defender) + role-based defensive credit attribution (couples roles with outcome models). Architectural fit (the appeal): the role-inference core is an HMM + linear covariate-dependent transitions (EM), not a neural net — compatible with silly-kicks' no-NN scipy/sklearn idiom (unlike the corner-RL/TacticAI GNN line, arXiv:2606.06353, which is out of scope); the valuation layer is outcome-model-agnostic → slot in silly-kicks' own xT / xShotOccurrence / VAEP in place of the paper's GNNs. Connects to the GKDV program (TF-15..19): role-conditioned ghosting is the tactically-contextual answer to ghost-GK's "average behavior lacks tactical context" limitation (see the ghost-GK section of NOTICE); role-based credit attribution sits next to TF-19 + DEFCON (Kim et al., already a TF-19 comparator). Scope/blockers: Monstah multi-component (CDHMM + EM + per-team/per-delivery fitting + role inference + ghosting + attribution); trained-model feature (ADR-011, per-team/per-delivery CDHMMs); new corner domain = strategic expansion decision, not a drop-in feature; data-availability (per-team/per-delivery models need many corners/team — paper used 14,678 corners across 4 PL seasons; assess vs WC2022 GS + pining corpora). Methods EM math + covariate set + appendix need a full re-read at spec time (the 2026-06-05 audit read the conceptual sections; docling truncated the Methods). Revisit if/when: a deliberate decision to open a set-piece feature layer in silly-kicks, or consumer demand for corner defensive-role / man-marking metrics materialises. Origin: 2026-06-05 LinkedIn/PDF audit; companion to the out-of-scope corner-RL paper arXiv:2606.06353.
TF-47 (Wicked): KNN / per-source-context conditional xT — the follow-on research arc for the 4.17.0 pluggable xT (xthreat/, SK-xT-1, ADR-021). Extend the transition family with a method="knn" flavor: KNN over (source_zone[, context]) → destination, the path to a per-source-context conditional xT (the headline of the ExT poster). Deferred — not blocked by silly-kicks, but: the method (Salimi, Salmankhah & Nodin 2026, "ExT," LISS Football Analytics Symposium) is a pre-publication poster, not implemented anywhere (the lakehouse has docstring stubs only), and the conditional context features (e.g. last-defender position, opponents between ball and goal) are tracking-join-dependent — the event↔tracking join stays consumer-side, so only the KNN consumer is library-shaped. The SK-xT-1 string-dispatch transition family + GridSpec + held-out-NLL evaluator make this a clean drop-in method="knn" + KNNParams when it lands. Revisit if/when: the ExT paper publishes (update NOTICE from the poster to the paper), or consumer demand for conditional xT materialises. Decision context: ADR-021 (scope: KNN/conditional deferred). Origin: 2026-06-07 SK-xT-1 promotion (lakehouse-proposed).
Restart-coordinate enrichment — Phase 2: canonical promotion (deferred; follow-up from 4.22.0 / ADR-025). Phase 1 shipped 4.22.0 (add_restart_coordinates emits imputed coords as new enriched_* columns; additive, no retrain). Phase 2 promotes those to the canonical start_x/start_y/end_x/end_y so VAEP/xT/calibration consume them directly. Blast radius (why deferred): a coordinated retrain of every trained model (VAEP, xT, calibration) + re-baseline of every coordinate-touching golden + promotion of the geometry tripwire to a hard converter-level gate. Recipe + the enriched_*-is-NaN-for-unresolved caveat are in ADR-025 ("Phase-2 promotion recipe"). Revisit if/when: a consumer needs imputed restart coords inside a trained model, or the coordinate-quality gap is prioritised for the canonical contract. Origin: 2026-06-10 general-enrichment Phase 1.