skills/wc-clean-sheet-model/SKILL.md
--- name: wc-clean-sheet-model description: Computes clean-sheet probability and defensive-returns EV for a team in a specific fixture from team defensive strength (xGA) against opponent attacking strength (xG), then models the STACK CORRELATION that turns a Clean-Sheet Spine (BB2) — GK + k same-team defenders — into a single correlated bet rather than k independent draws. Returns p_cs(team,fixture), a stack_corr_bonus capturing the amplified joint ceiling and shared downside (sign interacts wit
npx skillsauth add lyndonkl/claude skills/wc-clean-sheet-modelInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Implements the clean-sheet and defensive-returns piece of footballfantasy/context/frameworks/scoring-rules.md, and supplies the cs_corr_bonus term that fitness-function.md adds and that the Clean-Sheet Spine (BB2) in building-blocks.md exists to capture. Defensive points are lumpy and team-correlated: the GK and the whole back line keep a clean sheet together or concede together. So a 3-defender-plus-GK stack from one strong defence is not four independent picks — it is one bet with an amplified ceiling and a shared floor. This skill is where that correlation is priced. Every defensive EV in the system traces back to the p_cs and expected_ga it emits; downstream agents read the signal, they do not re-derive it (signal-framework.md).
The two jobs:
- [ ] 1. Read inputs: team, opponent, fixture; the `fixture` signal (team xGA, opp xG, difficulty, p_advance); θ and k from the spawn prompt; the stack composition if scoring a BB2
- [ ] 2. Build expected_GA = blend(team defensive xGA-against-this-opponent, opponent attacking xG) × game-state and home/neutral adjustments
- [ ] 3. p_cs = Poisson P(opponent scores 0 | expected_GA) = exp(−expected_GA), refined by defensive-quality and low-block/game-state tilt
- [ ] 4. Per-player defensive EV: λ_cs(pos)·p_cs + clean-sheet-conditional save/defact terms − λ_concede(pos)·E[concede-bracket penalty]
- [ ] 5. Stack: model the joint via a shared team-defence latent; compute P(whole stack clean) and the marginal-vs-joint gap → stack_corr_bonus
- [ ] 6. Apply θ sign: amplify stack_corr_bonus when gain, discount when protect; flag Clean Sheet Shield fit if a full 3+1 stack vs a weak attack
- [ ] 7. Emit the `clean-sheet` signal: p_cs, stack_corr_bonus, expected_ga, conceding downside, confidence
Everything keys off expected_GA(team, fixture) — the team's expected goals conceded in this match. Build it from both sides of the fixture so neither a stout defence vs a great attack nor a leaky defence vs a poor attack is mispriced:
expected_GA = w_def · xGA_team_vs_this_style # how much this defence usually concedes (per-90, recent form-weighted)
+ w_att · xG_opponent_vs_this_style # how much this attack usually creates (per-90)
scaled by minutes-on-pitch context, then × adjustments below
xGA_team and xG_opponent from the fixture signal (wc-fixture-progression). Do not re-derive fixture difficulty — consume it.w_def = w_att = 0.5; tilt toward the side with the larger, more reliable sample (a defence with 6 tournament matches outweighs an opponent with 1). At the World Cup, samples are tiny — lean on pre-tournament reference-class xGA/xG and cap confidence accordingly (see Guardrails).mismatch_list from the fixture signal.scout signal) widens expected_GA; full-strength back line tightens it. Reference the scout's injury/rotation_risk, do not re-scout.Goals conceded in a match are well-approximated as Poisson with mean expected_GA. A clean sheet is the opponent scoring zero:
p_cs(team, fixture) = P(opponent scores 0) = exp(−expected_GA) # first-order Poisson estimate
Sanity anchors (use to gut-check, not to override the model): expected_GA = 0.7 → p_cs ≈ 0.50; 1.0 → 0.37; 1.3 → 0.27; 1.6 → 0.20; 2.0 → 0.14. A clean sheet is a minority outcome even for a big favourite — never carry p_cs above ~0.60 without a confirmed mismatch, and treat anything above 0.65 as a flag to re-examine expected_GA.
Refinements on the raw Poisson:
p_cs (+0.02 to +0.05) for a genuinely disciplined defensive setup, and a small negative one for a high-line side that bleeds chances even in wins. Keep the adjustment bounded; it tunes, it never dominates expected_GA.p60 from the scout signal — a stack's correlation is worthless on a player who comes off at the hour with the score 0-0 and concedes after.P(opp = 0) jointly with the win/draw distribution — useful when game state feeds back (a team protecting a lead drops deeper, lowering late expected_GA). Default to the simple exponential unless the fixture signal carries reliable two-sided xG.For a GK or DEF (and partial-credit MID per the scoring table), the clean-sheet-and-defence contribution to xEV (the term wc-player-ev consumes) is:
def_EV(player) = λ_cs(pos) · p_cs · p60 # clean-sheet point, gated on 60'+
+ λ_save · E[saves] (GK only) # E[saves] rises as expected_GA rises — partial hedge vs the CS miss
+ λ_defact · P(hit defensive-actions threshold) (DEF / ball-winning MID)
− λ_concede(pos) · E[concede-bracket penalty] # the downside — see below
λ_cs(pos) from scoring-rules.md (provisional GK/DEF +4, MID +1, FWD 0 — confirm). The MID clean-sheet rule is a real, often-overlooked floor on a defensive-minded midfielder in a strong side; surface it.expected_GA lowers p_cs but raises E[saves] — a keeper behind a busy-but-resolute defence has a partial floor even without the sheet. Estimate E[saves] ≈ shots_on_target_faced (scales with expected_GA and opponent xG); credit the per-3-saves point and the penalty-save tail.The mirror of the clean sheet. GK/DEF lose points per the concede bracket (provisional: −1 per 2 goals conceded). Because expected_GA is the same latent that drives both, the downside is mechanically tied to the upside — and for a stack it is shared (they all sit in the same conceding bracket together). Return it explicitly so the board can show the floor, not just the ceiling:
E[concede_penalty(team)] = Σ_g P(opponent scores g | expected_GA) · penalty_bracket(g) # Poisson over goals conceded
For a stack of k defenders + GK, the concede penalty is incurred by every stacked player on the same goals-against draw — so the downside scales ~linearly with stack size on the bad draws, while the upside also scales on the good draws. That two-sidedness is the heart of the stack bonus below.
This is the term BB2 exists for and that fitness-function.md adds as cs_corr_bonus. A naive model treats k defenders + a GK from the same team as k+1 independent clean-sheet draws. They are not — one match, one defence, one realised goals-against count. Model the shared outcome with a single team-defence latent:
clean-sheet indicator for the whole stack = 1{ opponent scores 0 } # a SINGLE Bernoulli, shared by all k+1 players
P(whole stack returns the CS point) = p_cs · Π p60(member) # ~p_cs, lightly haircut for any rotation-risk member
The fantasy value of the stack is the joint distribution of its total defensive points, which has two parts a naive sum misses:
(k+1) clean-sheet points at once — a correlated haul. A 3+1 stack vs a weak attack is a ~p_cs chance at 4 × λ_cs ≈ +16 from clean sheets alone, plus any set-piece CB goal or full-back attacking return riding on top. That correlated spike is exactly the ceiling a chaser wants and a naive independent-sum understates.Compute the bonus as the gap between the joint ceiling/variance the stack actually carries and the independent-draw approximation the rest of the EV pipeline would assume:
# correlation lift to the ceiling (what the joint buys over independent draws):
ceiling_lift = ρ · k · λ_cs(DEF) · p_cs · (1 − p_cs) # ρ→1 within a team; grows with stack size k and with how live the CS is
# shared-downside term (the cost of zero internal diversification on the CS axis):
shared_floor = ρ · k · E[concede_penalty_per_player] # the same bad draw hits all k+1 together
stack_corr_bonus = sign(θ) · ceiling_lift − damp(θ) · shared_floor
where ρ ≈ 1 for same-team back-line members (perfect within-team CS correlation is the modelling premise of BB2), decaying toward 0 as you split the stack across teams — splitting a stack across nations destroys the bonus, which is precisely why crossover keeps BB2 intact and repair downgrades enablers before breaking the spine (building-blocks.md).
The θ sign flip (selection pressure):
| θ | stack_corr_bonus treatment | football logic |
|---|---|---|
| gain (chasing) | sign(θ) = +, damp(θ) small → amplify the correlated ceiling | you need the swing; a 4-up clean-sheet haul the field's spread defences don't get is a rank earthquake |
| protect (leading) | sign(θ) = + but small, damp(θ) large → discount for the shared floor | a team blank that zeroes four of your players at once is exactly the variance a leader is trying to avoid; prefer spreading defenders |
| neutral | symmetric, modest | let the marginal p_cs EV lead; small bonus for coherence |
This makes the same 3+1 stack score as an asset for a chaser and as a liability for a leader — the selection-pressure dial of fitness-function.md, applied to the defensive module.
Chip and building-block synergy (surface, do not decide):
p_cs chance into a near-locked correlated haul and caps the shared floor — the single highest-leverage home for the chip (chip-catalog.md §4). Set shield_fit: true and report the lift to p_cs and the floor the shield would underwrite, so wc-chip-strategist can size the deployment.clean-sheet signal)Emit via wc-signal-emitter to signals/<round>-clean-sheet-<team>.md:
---
type: clean-sheet
round: <round id, e.g. 2026-grp-md2>
date: <YYYY-MM-DD>
emitted_by: wc-clean-sheet-model
confidence: <0.00–1.00> # cap 0.35 if expected_GA rests on unconfirmed xGA/xG or scoring values
source_urls:
- <url for team xGA / opponent xG / predicted back line / venue> # or "manager-provided"
---
team: <team>
fixture: { opponent: <opp>, round: <round>, venue: neutral|host|home, kickoff_utc: <ts> }
expected_ga: <float> # expected goals conceded this match (the engine quantity)
p_cs: <0.00–1.00> # = exp(−expected_ga), refined; clean sheet is a minority outcome
p_cs_60: <0.00–1.00> # p_cs × representative p60 (the actually-bankable CS prob for a starter)
defensive_ev:
gk: { cs_point: <n>, e_saves: <n>, save_points: <n>, concede_penalty: <-n>, def_ev: <n> }
def: { cs_point: <n>, defact_floor: <n>, concede_penalty: <-n>, def_ev: <n> } # per typical stacked DEF
mid_partial_cs: <n> # the MID clean-sheet floor, if relevant
concede_downside:
e_concede_penalty_per_player: <-n> # Poisson over goals-against × bracket; SHARED across a stack
bracket_note: <"−1 per 2 conceded; provisional — confirm scoring-rules.md">
stack:
composition: { team: <team>, defenders: <k>, gk: <bool> } # the BB2 spine being priced
is_real_stack: <bool> # ≥3 same back line + that GK; false = split pseudo-stack (ρ→0, no bonus)
rho: <0.0–1.0> # within-team CS correlation (~1 same team)
p_stack_clean: <0.00–1.00> # P(whole stack returns the CS point together)
ceiling_correlated_haul: <n> # points the stack pays AT ONCE on the good draw (e.g. (k+1)·λ_cs + set-piece spike)
shared_floor: <-n> # points all members lose together on the bad draw
stack_corr_bonus: <+/- n> # the fitness term, AFTER the θ sign flip
theta_applied: { theta: protect|gain|neutral, k: <n> }
shield_fit: <bool> # true = full 3+1 stack vs weak attack → prime Clean Sheet Shield slot
shield_note: <lift to p_cs and floor the chip would underwrite, for wc-chip-strategist>
assumptions:
blend_weights: { w_def: <n>, w_att: <n> }
adjustments_applied: [ mismatch, game_state, venue, personnel, low_block ]
recheck_near_lock: <bool> # true if predicted back line / keeper not yet confirmed
The decomposition is mandatory: wc-fitness-eval reads stack_corr_bonus, wc-player-ev reads the per-position def_ev and concede_downside, wc-chip-strategist reads shield_fit/shield_note, and the Director shows ceiling-vs-floor on the board so the manager sees both sides of the stack bet.
p_cs drift above ~0.60 without a confirmed mismatch (mismatch_list); above 0.65 is a flag to re-examine expected_GA. Big favourites still concede.p_stack_clean, not p_cs^(k+1).shared_floor alongside ceiling_correlated_haul. A correlation that lifts the ceiling lifts the floor's depth equally — hiding the downside would mislead a protecting manager into a single fragile bet.θ isn't supplied, ask — do not silently default to neutral on a stack-heavy candidate; the sign flip is the whole point of the term for fitness.p60 from the scout signal; a defender subbed at the hour is not a clean-sheet bank. A rotation-risk member also drags ρ-adjusted p_stack_clean.p_advance come from the fixture signal; injuries/minutes from the scout signal. Re-deriving them violates signal-framework.md and risks contradicting an upstream agent.scoring-rules.md confirmed: false). If expected_GA rests on unconfirmed xGA/xG or the clean-sheet point value is unverified, cap confidence ≤ 0.35 and set recheck_near_lock: true until the back line and keeper are confirmed.testing
Cluster a conference's event records into a small set of coarse themes with finer sub-clusters, an explicit outlier bucket, and soft (multi-membership) affinities — using the hybrid embed-then-label pipeline (embed abstracts, reduce, density-cluster, then LLM-label the clusters) when embedding libraries are available, and an LLM-reasoned hierarchical fallback when they are not. Embeddings do the grouping; the LLM only names the groups. Conference-agnostic. Use when turning structured event records into a navigable theme map for preference elicitation and scheduling, when you need 6-8 reasonable themes rather than 20 muddy ones, or when overlapping talks must belong to more than one theme. Trigger keywords - theme clustering, cluster talks, embed then label, soft membership, outlier talks, conference themes, topic map.
development
Build a personal conference schedule as a constraint-optimization problem — hard constraints (no time overlap, room-to-room travel time, capacity/registration, the attendee's own must-attends and blackouts) plus a user-owned weighted objective trading interest against breadth, pacing (maximize contiguous free time), and serendipity. Surfaces unbreakable conflicts (two high-value overlapping talks the model cannot rank) as decisions for the human rather than silently picking, and reports what each choice traded away. Conference-agnostic. Use to turn a preference profile plus a theme map into a day-by-day plan, to resolve overlapping sessions, or to balance a packed vs paced schedule. Trigger keywords - schedule optimization, conference schedule, constraint optimization, overlapping talks, contiguous free time, conflict surfacing, packed vs paced.
development
Parse a heterogeneous conference program (markdown, HTML, PDF-derived text, or JSON) into normalized event records with per-field confidence scores and independent classification axes (topic, depth, format, prerequisites, recorded, capacity). Detects the program's format before extracting, treats every inferred field as uncertain (present vs inferred vs missing), and flags thin or missing abstracts so downstream enrichment can target them. Conference-agnostic. Use when ingesting a conference or event schedule into a structured store, normalizing a talk/session list, or extracting per-session metadata with calibrated confidence. Trigger keywords - program ingestion, parse schedule, session extraction, event records, conference program, talk metadata, per-field confidence.
development
Build a personalized preference profile from a small number of well-chosen, cluster-grounded questions instead of a long survey. Represents the person's interests as an uncertainty region over the theme map, picks the single highest-information-gain choice-based question (contrasting real talks from different clusters), balances exploiting known interests against exploring uncertain ones, deliberately injects outlier probes to fight selection bias, and stops as soon as the schedule would be stable. Also elicits the user-owned objective weights and hard constraints. Interactive — runs where it can actually ask the person. Conference-agnostic. Use to turn a theme map into a preference profile, to decide what to ask a conference attendee, or to elicit scheduling priorities. Trigger keywords - preference elicitation, ask few questions, information gain, choice-based questions, selection bias probe, objective weights, attendee preferences.