skills/wc-captain-ladder/SKILL.md
Computes the optimal rolling-captaincy LADDER across a matchday's staggered kickoff sequence — the ordered captain candidates, the explicit bank-or-switch rules at each kickoff, and the captaincy_uplift that wc-fitness-eval folds into raw_xEV. Because the armband doubles the round's points AND can be MOVED during the round to a player who has not yet kicked off, captaincy EV is the expected value of the best outcome reachable by switching across the sequence — not a naive 2x of the top xEV player. Call from wc-matchday-tactician (to write MB2 of the matchday plan), from the strategists (to value a candidate's captaincy when building a matchday plan), and from wc-fitness-eval (which consumes captaincy_uplift). Returns the ladder, the switch thresholds, and the uplift against a stated baseline; the ladder is moot under the Maximum Captain chip (value = max of candidates).
npx skillsauth add lyndonkl/claude wc-captain-ladderInstall 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 MB2 — Captain Ladder from footballfantasy/context/frameworks/building-blocks.md, the captaincy clauses of league-config.md ("Captaincy — the rolling-captain edge"), and the captaincy-in-EV note in scoring-rules.md. It supplies the captaincy_uplift term that wc-fitness-eval adds inside raw_xEV (see frameworks/fitness-function.md).
This is the single biggest structural difference from FPL and the largest single lever in wc-matchday-tactician's claimed ~20–40 pts/round active-management edge. In FPL the captain is locked at deadline; here the armband doubles the round's points and can be moved during the round to any captain candidate who has not yet kicked off. So the question is never "who is the highest-xEV captain?" — it is "what is the expected value of the best armband I can realise by watching the early games and re-deciding before each later candidate kicks off?"
That makes captaincy a sequential decision under uncertainty, not a point estimate. A merely-good captain who plays in the late kickoff is worth more than a marginally-better one who plays first, because the late slot lets you bank an early hauler and only ride the late man if the early games disappoint. This skill prices that optionality.
Define xEV_cap(p) as the (already fixture-scaled, minutes-weighted) expected captain bonus from player p — i.e. the extra points the armband adds, which equals one more copy of p's round score: xEV_cap(p) = xEV(p). The naive captaincy value is max_p xEV(p): pin the armband on the top player and never touch it.
The ladder beats that by an option premium. With candidates on different days you observe the early candidate's actual return, then keep-or-switch. You capture the maximum of correlated-but-staggered draws rather than a single fixed draw:
naive = max_p E[score(p)] # one fixed bet, set at lock
ladder = E[ max over the switching policy of the realised captain score ] # best reachable, decided live
uplift_vs_naive = ladder − naive ≥ 0
The premium is largest when (a) candidates kick off on different days (you actually get to see results before committing the late slot), (b) the candidates have comparable xEV (no runaway favourite, so the live info changes the decision), and (c) each candidate's outcome variance is high (boom/bust attackers — the max of two volatile draws is well above either mean). It collapses to ~0 when everyone kicks off simultaneously (no information, no switch) or when one candidate dominates so heavily that you would never move off him.
- [ ] 1. Inputs: the squad/XI, this round's kickoff schedule (UTC, per match), θ, and
the player-ev signals (xEV, variance, ceiling, floor) for every captain candidate
- [ ] 2. Build the candidate set: 2–3 from BB1 Captain Core, ideally on DIFFERENT days.
If <2 candidates, or all on one day → no ladder; flag it (see Guardrails)
- [ ] 3. Reconstruct each candidate's per-match point DISTRIBUTION from its player-ev moments
- [ ] 4. Order candidates by kickoff time (earliest first = the first decision node)
- [ ] 5. Solve the optimal switching policy by backward induction over the kickoff sequence,
applying the BANK-OR-SWITCH thresholds, MATCHUP-CONTINGENT
- [ ] 6. Read off the ladder: lock pick + the explicit per-kickoff switch rules
- [ ] 7. captaincy_uplift = E[optimal ladder] − baseline (STATE which baseline)
- [ ] 8. Maximum-Captain override: if that chip rides this round, value = max of candidates;
the ladder is moot — emit it flagged
- [ ] 9. Emit the captain-ladder signal (ordered ladder + switch rules + uplift + baseline)
Take the captain candidates from the squad's BB1 Captain Core (the 2–3 premium FWD/attacking-MID; building-blocks.md). A valid ladder wants:
max.start_prob/p60 from the player-ev signal; do not put a rotation-risk player on the ladder as a primary). A bench player can be a contingent late candidate only if a manual sub (MB3/MB4) would promote him into the XI before his kickoff.wc-player-ev emits xEV, variance, ceiling, floor per player. Captaincy is driven by the tails (a captained 2-pointer is a disaster; a captained 15 wins the round), so do not reduce a candidate to its mean. Reconstruct a working distribution from the moments:
Use a 3-point discretisation that matches the player-ev moments — a blank/floor node, a base/return node, and a ceiling/haul node — with probabilities chosen to reproduce xEV (mean), variance, and to put mass at floor and ceiling:
| node | value | typical attacker meaning |
|---|---|---|
| floor (blank) | floor (≈ appearance only, e.g. 1–2) | played, no attacking return |
| base (return) | near xEV | one goal-or-assist game |
| ceiling (haul) | ceiling (e.g. 13–20+) | brace / goal+assist / penalty haul |
Solve the three node-probabilities (p_floor, p_base, p_ceil) from the three constraints Σp=1, Σ p·v = xEV, Σ p·(v−xEV)² = variance. If the player-ev signal already carries a dist field, use it directly and skip the reconstruction.
These are the captain bonus draws (one extra copy of the round score), and they map points→keep/switch through the thresholds below.
Note on correlation: candidates on the same team or in the same match are positively correlated (a team-wide good day lifts both); treat them as effectively one ladder node for switching purposes (you cannot bank one and ride the other on fresh information — they resolve together). The option premium lives in candidates whose matches are independent and staggered.
Order the candidates c_1, c_2, …, c_n by kickoff time, earliest first. The armband is on someone at lock; after each candidate's match finishes you may move it to any not-yet-kicked-off candidate. Solve backwards:
Let s_i = realised captain score of candidate c_i (drawn from its Step-2 distribution).
Terminal: once only the last candidate c_n remains un-played, its value is E[s_n].
Induction at node i (c_i has just finished, armband currently notionally on the
banked-best-so-far b = max realised score among c_1..c_i that the armband could hold):
keep_value = b # bank what's already realised
switch_value = E[ max over the remaining optimal policy of s_{i+1..n} ]
decision = SWITCH if switch_value > keep_value else KEEP
node_value = max(keep_value, switch_value)
E[optimal ladder] = the value at the first decision node, integrated over all
early-result realisations (compute exactly over the 3^k discretised outcome grid,
or Monte-Carlo if n and node-count make the grid large).
Crucially the rule is state-dependent: you keep an early haul and switch away from an early blank. The lock pick is the candidate that maximises the whole policy's value (usually an early candidate so you preserve the option to bank or bail) — but if the strongest candidate is the latest kickoff, the policy is "provisionally captain the early man, bank a haul, otherwise roll to the late favourite."
The backward induction is driven by these thresholds on the realised captain score of the candidate who just played — the operational rules the manager executes live, and the ones written into MB2:
| Realised captain score | Action | Rationale | |---|---|---| | 2–5 | Switch to the next candidate (if any remains un-kicked-off) | A captained ~3 is a wasted armband; almost any live candidate's mean beats banking it. | | 6–7 | Close call / contingent | Bank it only if the next candidate's matchup-adjusted mean is below the banked score; otherwise switch. This band is where matchup-contingency decides. | | 8+ | Usually keep | A captained 16 (8 doubled) is a strong round; only switch for a clearly superior remaining matchup (e.g. a penalty-taker vs a minnow at home, still to play). | | 10+ | Almost always keep | Chasing more variance off a banked 20 is −EV against any realistic remaining candidate; never throw it away (Guardrail). |
Matchup-contingency shifts every threshold. Switch more freely (treat 6–7, even a soft 8, as switchable) when the next candidate has the materially easier assignment — at home, vs a weak defence (high opponent xGA, low PPDA resistance), on penalties, with their team a heavy favourite to dominate territory and pile up shot volume. Switch less freely (hold even a 5–6) when the only remaining candidate faces a tough, low-block opponent or carries rotation/minutes risk — a captained blank from a benched-at-half forward is worse than a banked 6.
Encode matchup as a per-candidate switch-readiness multiplier on its xEV when evaluating switch_value: scale up the easier-fixture candidate (e.g. ×1.15 for a home favourite vs a leaky defence), down the harder one (×0.85 vs an elite defence or with rotation_risk). Source the fixture difficulty from the fixture signal (wc-fixture-progression) and the minutes/role risk from the scout signal — do not re-derive them here.
captaincy_uplift is what wc-fitness-eval consumes; it must declare its baseline so two candidates are compared like-for-like:
captaincy_uplift = E[optimal ladder] − baseline
Emit both baselines and label which one fitness should use:
baseline = no_captain → uplift = E[optimal ladder] (the captain bonus is one extra copy of the captained score; with no captain the XI scores its base once). This is the term that enters raw_xEV as the captaincy contribution. Use this by default for fitness's raw_xEV so captaincy is counted once, cleanly.baseline = static_chalk → uplift = E[optimal ladder] − E[score(chalk captain held all round)], where the chalk captain is the highest effective-ownership captain (the field's default armband, from wc-ownership-meta). This is the rank-relevant number — what the ladder gains over the field's set-and-forget armband — and is the more honest figure to show on the decision board's captaincy line and to compare across the population.State the baseline explicitly in the output; a captaincy_uplift without its baseline is meaningless to fitness.
θ interaction (consistent with the fitness variance dial): the expected ladder value is objective-neutral, but report the ladder's captain variance so fitness can sign it. When θ=gain, the board may prefer the higher-ceiling lock pick even at marginally lower mean (the switch policy already banks upside); when θ=protect, prefer the lock pick whose policy has the higher floor (more candidates that let you bail off a blank). Do not bake θ into the EV here — emit the mean and the variance and let wc-fitness-eval apply ±k·variance.
If the Maximum Captain chip rides this round (chip-catalog.md #3, MB5), the game retroactively captains your highest-scoring player — captain-pick risk is removed entirely. The ladder is moot:
captaincy_value(Max-Captain) = E[ max over ALL eligible players of their realised round score ]
This is the expectation of the maximum over the whole XI (not just BB1), with no switching constraint and no kickoff ordering — strictly ≥ the ladder. Emit it flagged chip: maximum_captain, with ladder_moot: true, and pass this value (not the ladder) to fitness for that round. (Conversely, this skill's own output is a signal of how much the ladder is worth — if the ladder's uplift over static chalk is already large, the Maximum Captain chip adds little and is better held; surface that for wc-chip-strategist.)
captain-ladder signal)type: captain-ladder
round: <round id, e.g. 2026-grp-md2>
date: <YYYY-MM-DD>
emitted_by: wc-captain-ladder
confidence: <0.00–1.00> # cap 0.35 if a candidate's start/minutes is web-unconfirmed
source_urls:
- <kickoff schedule url>
- <predicted-XI / minutes url per candidate> # or manager-provided
ladder_valid: true|false # false if <2 candidates or all same slot
no_ladder_reason: <if invalid: "single candidate" | "all candidates same kickoff slot">
lock_pick: <player> # who wears the armband at deadline
ladder: # ordered by kickoff (earliest first)
- order: 1
player: <name>
team: <nation>
kickoff: <UTC>
xEV: <n> variance: <n> ceiling: <n> floor: <n>
matchup: <one football line — opponent xGA, home/away, pen duty, fav?>
switch_readiness: <multiplier, e.g. 1.15> # higher = switch toward this one more freely
- order: 2
player: <name>
# …same shape…
switch_rules: # the live, executable instructions (MB2 content)
- "Lock the armband on <lock_pick>."
- "After <c_1> (<kickoff>): if his captain score ≤5 → move to <c_2>; 6–7 → switch only if <c_2> faces the softer matchup; 8+ → keep."
- "After <c_2> (<kickoff>): bank anything ≥8; below that, roll to <c_3> if <c_3>'s adjusted mean is higher."
- "Never move off a banked 10+."
captaincy_uplift:
ladder_ev: <E[optimal ladder]>
vs_no_captain: <n> # = ladder_ev; the term raw_xEV consumes (default)
vs_static_chalk: <n> # ladder_ev − E[chalk-captain held all round]; the board figure
chalk_captain: <player> # field's default armband (highest EO)
baseline_for_fitness: no_captain # which one wc-fitness-eval should use
ladder_variance: <n> # captain-score variance, for fitness's ±k·variance term
chip:
maximum_captain: <true|false>
ladder_moot: <true|false> # true when maximum_captain is on
max_captain_value: <E[max over XI] — only when the chip rides>
(Frontmatter follows the common signal schema in frameworks/signal-framework.md; wc-signal-emitter validates and persists it.)
ladder_valid: false, price captaincy at the naive max of the candidates, and flag it loudly so the manager (or a strategist on a squad build) knows the squad has captaincy fragility: BB1 and BB6 are mis-laid out. This is exactly the linkage failure building-blocks.md warns about ("reshuffle them onto one day and the ladder is dead").start_prob/p60 is web-unconfirmed.wc-player-ev; fixture difficulty from wc-fixture-progression (the fixture signal); minutes/role from wc-scout; chalk/effective ownership from wc-ownership-meta. This skill sequences and prices; it does not re-scout players or re-rate fixtures.captaincy_uplift is meaningless without it — vs_no_captain for fitness's raw_xEV, vs_static_chalk for the board and population comparison. Emit both; never a bare number.max_captain_value and ladder_moot: true, and let fitness use the max value for that round only.scoring-rules.md still has confirmed: false, mark the uplift provisional — the absolute magnitudes (especially the doubled goal/haul values) depend on the unverified point table.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.