skills/wc-matchday-timing/SKILL.md
Builds the structural support for the in-round levers in FIFA World Cup Fantasy — scores how the squad's players are distributed across the round's match days (a good build spreads them, NOT stacked on one day), orders the 4 bench players by kickoff time (latest highest, so they stay promotable after early results land), and generates conditional manual-substitution triggers ("if early starter scores ≤ T and a later bench player hasn't kicked off, promote them"). Implements BB6 (matchday spread) and MB3/MB4 (bench order + sub triggers), and the manual-substitution rule in league-config.md. Returns a kickoff_spread assessment with over-stack flags, a ranked bench_order (1–4), and a sub_triggers list. Use when laying out a matchday plan or vetting a candidate squad's timing layout — called by wc-matchday-tactician and the strategists, especially A6 Matchday Mechanic.
npx skillsauth add lyndonkl/claude wc-matchday-timingInstall 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 BB6 matchday-spread constraint in footballfantasy/context/frameworks/building-blocks.md and the manual-substitution rule in context/league-config.md. This skill owns the structural plumbing under the two point-winning levers: the captain ladder (owned by wc-captain-ladder) and manual subs. Neither lever works on a squad whose players all kick off in the same window — there is nothing to switch to, nobody left to promote. This skill measures whether the build has that material, and if so, lays out the bench and the triggers that turn it into points.
The edge is concrete: league-config.md states an active manager working the captain ladder + manual subs gains ~20–40 points per round over a set-and-forget manager. That edge is only available when the squad is spread across the round's match days. This skill protects that availability.
One hard fact this skill is built around: a manual sub only works if the bench player has not yet kicked off. Once a match has started, that player's score is locked — promotable players are exactly the ones whose kickoff is still in the future. So bench order is primarily a kickoff-time problem, and only secondarily a points problem. Order by kickoff first; break ties by xEV.
- [ ] 1. Load the round's match-day calendar (which days, kickoff times UTC) — web-search, cite
- [ ] 2. Load the candidate's XI + bench, each player's nation, fixture, kickoff slot, and xEV (read player-ev signals; do NOT re-derive)
- [ ] 3. KICKOFF SPREAD — bin the XI (and squad) by day/slot; score the spread; raise over-stack flags
- [ ] 4. BENCH ORDER — rank the 4 bench by kickoff time (latest = highest), break ties by xEV; drop dead-fixture benchers to the floor
- [ ] 5. SUB TRIGGERS — for each early starter, generate a conditional promotion rule; set the threshold T by position and the bench player's own xEV
- [ ] 6. Cross-check against the captain ladder (shared kickoff sequence) — flag conflicts, don't re-solve the ladder
- [ ] 7. Emit the matchday-timing signal: kickoff_spread + over_stack_flags, bench_order[1–4], sub_triggers[]
Bin every starter by the match day (and, within a packed day, by the kickoff slot — early / late) on which their nation plays this round. The unit of analysis is the XI (those are the live captain-ladder candidates and the players a sub can replace); compute the same over the full 15 to read bench promotability.
The spread score. A good build distributes players across days so that on any given day there are both players still to play (ladder/sub fodder) and results already in (information). Stacking everyone on one day collapses both. Score the spread with normalised entropy over the day-bins:
Let n_d = number of starters who play on day d, N = 11 (the XI).
p_d = n_d / N.
H = − Σ_d p_d · ln(p_d) # Shannon entropy of the day distribution
spread_score = H / ln(D) # normalise by ln(#days in the round) → [0,1]
spread_score → 1.0: players evenly across all the round's days — maximal ladder/sub material (e.g. 3 Tue / 4 Wed / 4 Thu across a 3-day round → H/ln3 ≈ 0.99).spread_score → 0.0: all 11 on one day — the levers are dead. No captain to ladder to, no later bench to promote; you are effectively set-and-forget for the round whether you like it or not.Interpretation bands:
| spread_score | read | lever availability | |---|---|---| | ≥ 0.85 | well spread | full ladder + sub optionality | | 0.65–0.85 | acceptable | levers work, some days thin | | 0.45–0.65 | concentrated | one lever likely available, the other weak | | < 0.45 | over-stacked | levers largely dead this round — flag |
Over-stack flags (raise each that applies):
single_day_stack: any one day holds > ~55% of the XI (max_d p_d > 0.55). The round's captaincy and subs are decided in one window with no recourse.captain_core_same_slot: all BB1 captain candidates kick off in the same slot (no day/slot separation). The captain ladder (MB2) has no switching room — surfaced here for wc-captain-ladder, which owns the fix.last_day_empty_bench: the latest kickoff day has no bench player on it. Manual subs lose their best asset — the late promotable option — because every bencher has already played by the time early results are known.front_loaded: ≥ ~70% of the XI plays on day 1 of a multi-day round. You learn nothing before most of your points are banked; the in-round levers can't act on information that arrives too late.A flag is advisory — it is a property of the squad build (BB6), not a matchday error. The right consumer is usually a strategist (rebuild the timing layout) or the tactician (note that this round is a low-lever round and manage expectations). For a candidate squad being vetted, a persistent low spread_score across the next 1–2 rounds is a real BB6 weakness worth a board annotation.
The bench is an ordered list (MB3). Auto-subs and manual subs both walk it in order, promoting the highest-ranked eligible bencher. The ordering rule is lexicographic — kickoff first, xEV second:
For each of the 4 bench players, compute the key (kickoff_rank, xEV):
kickoff_rank = how LATE the player kicks off this round (latest kickoff → highest rank)
xEV = the player's expected points (from their player-ev signal)
Sort DESCENDING by kickoff_rank; within an equal kickoff slot, sort DESCENDING by xEV.
Bench position 1 = first to be promoted; position 4 = last.
Why latest-kickoff is highest, not highest-xEV. A bench slot's value as an option is the chance you can still act on it after seeing results. A high-xEV bencher who has already kicked off is no longer promotable — his number is locked; the option is spent. The latest kickoff is the one that survives longest as a live lever, so it sits at the top of the order where a manual sub can reach it. xEV only sorts players who share a kickoff window.
Two overrides on the pure kickoff sort:
Emit the final order as bench_order[1..4] with each player's (kickoff, xEV, reason) so the tactician and the board can see why a lower-xEV player outranks a higher one (it's the option value).
For each early-kickoff starter, generate a rule that promotes a later, still-to-play bench player if the starter underperforms. The canonical shape:
IF [early starter S] has finished his match AND scored ≤ T(S)
AND [bench player B] has NOT yet kicked off
AND promoting B keeps the XI formation-valid
THEN promote B for S before B's kickoff.
Setting the threshold T(S) — by position, then adjusted by B's own xEV (you only sub down to something better):
Base threshold by the started player's position (points already banked at which it's no longer worth chasing — calibrate magnitudes against scoring-rules.md, which is still confirmed: false, so treat absolute values as provisional):
| Position of S | base T | rationale | |---|---|---| | FWD / attacking MID | ≤ 2 | a forward who returns only the appearance tier (no goal/assist contribution) has whiffed his entire reason for starting; a live bencher with real attacking xEV is +EV to promote | | MID (central / two-way) | ≤ 2 | floor came from minutes + maybe a defensive-actions return; if he didn't clear it, a better-fixtured B wins | | DEF / GK | ≤ 1 | the clean sheet is already gone (conceded → CS points lost and he's into negative concede territory); little left to bank, promote if B's fixture is live |
Then adjust T by the gap to B:
T_eff(S, B) = T_base(S) + clamp( round(xEV(B) − xEV(S)) , −1, +2 )
T_eff so high you'd bench a starter who's already returned (e.g. don't generate a trigger that fires on a captain-tier 6). The trigger is for busts, not for chasing variance off a decent score.Trigger hygiene:
wc-captain-ladder's, not a sub trigger. A manual sub removes the player and his (doubled, if captained) score entirely — only trigger a sub on a captain after the ladder has already moved the armband off him. Flag any S that is both a sub-trigger subject and a live captain so the tactician sequences them correctly; do not resolve it here.This skill and wc-captain-ladder share one object: the kickoff sequence of the round. Read its signal if present; do not re-solve the armband. Surface conflicts only:
captain_core_same_slot) that kills the ladder's switching room.
Hand these to the tactician; the two skills are one system (per MB2/MB3/MB4 linkage in building-blocks.md) but each owns its half.Emitted via wc-signal-emitter. Type matchday-timing. Frontmatter per signal-framework.md (kickoff times are load-bearing facts — cite the source URL or mark manager-provided; if a kickoff or a predicted bench can't be web-confirmed, cap confidence at 0.35 and flag "confirm before lock").
type: matchday-timing
round: <round id, e.g. 2026-grp-md2>
date: <YYYY-MM-DD>
emitted_by: wc-matchday-timing
confidence: <0.00–1.00>
source_urls:
- <kickoff-schedule url | manager-provided>
kickoff_spread:
days: [<day/slot ids this round, e.g. Tue-early, Tue-late, Wed, Thu>]
xi_distribution: { <day>: <count>, ... } # e.g. {Tue: 3, Wed: 4, Thu: 4}
spread_score: <0.00–1.00> # normalised entropy of the XI day distribution
band: well-spread | acceptable | concentrated | over-stacked
over_stack_flags: # [] if none
- flag: single_day_stack | captain_core_same_slot | last_day_empty_bench | front_loaded
detail: <one line — which day/slot, what % of the XI, what lever it kills>
bench_order: # ordered 1 (first promoted) → 4 (last); kickoff-first, xEV-second
- rank: 1
player: <name>
kickoff: <UTC time / slot>
xEV: <n>
reason: <e.g. "latest kickoff Thu 20:00, live fixture — most promotable">
- rank: 2 { player: ..., kickoff: ..., xEV: ..., reason: ... }
- rank: 3 { player: ..., kickoff: ..., xEV: ..., reason: ... }
- rank: 4 { player: ..., kickoff: ..., xEV: ..., reason: <e.g. "dead fixture — demoted to floor"> }
auto_sub_fallback: <position whose player is the formation-valid automatic fallback, if not 1>
sub_triggers: # [] if the spread gives no manual-sub window
- if_starter: <S>
starter_kickoff: <slot>
threshold_T: <T_eff> # position base, adjusted by xEV(B) − xEV(S)
promote: <B>
promote_kickoff: <slot> # MUST be later than starter_kickoff
keeps_xi_valid: true
note: <e.g. "raise T: B is +3 xEV over S; sub even on a quiet 2">
ladder_conflict: <none | "S is live captain — move armband first">
notes: <e.g. "Low-lever round: spread_score 0.41, everyone Wednesday. Manage as set-and-forget; the captain pick is the only live decision.">
If spread_score is in the over-stacked band, say so plainly in notes and emit sub_triggers: [] — be honest that there is no in-round material this round rather than inventing triggers that can never fire.
wc-captain-ladder). Verify the actual MD3 kickoff schedule from the round calendar — the simultaneity applies within each group, while different groups still stagger through the day.wc-captain-ladder's; only flag the sequencing where a sub and the ladder touch the same player, and require the armband to move off a captain before a sub removes him.player-ev, kickoff times from the round calendar, predicted benchings from scout. Re-compute nothing already emitted this round (signal-framework.md).scoring-rules.md is confirmed: true. The T values encode the points structure; treat magnitudes as calibration-caveated and re-verify the 60' tier and goal/clean-sheet values before trusting them numerically.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.