Matchmaking Pipelines — Status Tracker
Status: Active — living tracker, update in place as fixes ship Last reviewed: 2026-07-18 Purpose: the single place to check how healthy match generation is and what the next highest-leverage fix is — one row per pipeline at a glance below, one detail section per pipeline further down. Scope: the 7 pipelines as they stand after Slices 0–3 shipped (rotation core · feasibility forecast · availability v1 · the 4 tournament engines wired · the minimal tournament-experience wedge · the pluggable registry, now activated). Score-driven formats (P7): Americano + Mexicano + the 순위 standings/rating-impact surface + the 친선 rating-gate + King-of-the-Court all shipped — P7 complete. Grounding: 3 read-only adversarial audit passes over the shipped system, verified against source — ~45 findings regrouped into the 7 pipelines below. Live against preview OTA 559eaaa9 (HEAD a8aab7b5, 2026-07-15). Companion artifacts + the Slice 1 spec + match-generation canon: see Links.
How to update this doc
This tracker is living — edit it directly as fixes ship. Don't wait for a new audit pass to keep it honest.
- A fix ships: move the finding(s) it closes from that pipeline's Gaps list to a Resolved list directly under it (add one if the section doesn't have one yet). Keep the finding ID and add a one-line "what changed" + commit ref. Delete the matching line from Fixes → contribution once every finding it closed is resolved, or narrow the line to whatever's left.
- Health %: bump the number in both places it appears — the pipeline's row in the status table and the bold status line directly under its
## PNheading (e.g.**Mature · 85% · 0 high**) — moving toward that pipeline's stated Target. The heading text itself (## P1 — Rotation generation) stays fixed on purpose, so its anchor link never breaks. Health is a judgment call weighted toward High/Correctness findings, not a formula; don't over-precision it. - High-count: after any fix, recount the remaining
[High …]tags in that pipeline's Gaps list and update the status-table column. - Status label: move along the same ladder the 7 rows already use —
Not started→v1 · incomplete/Wedge · unfinished→Wired · sound core/Built · half-live→Functional · IA pending/N of M · <remainder> pending→Solid→Shipped/Activated→Mature. Reuse a rung rather than inventing a new label; if none fits, add the new label to this sentence too. - A pipeline hits its Target: flip Status to
Mature(orShipped/Completeas fits), set Health to the target %, confirm Gaps is empty, and leave the Fixes/Target lines in place as a record of what closed it. - A new finding surfaces (regression, or the next audit pass): add it to the right pipeline's Gaps with a severity tag and an ID that doesn't collide with existing ones in that letter series (
A#/B#/C#/D#/E#/T-#, matching the flat audit inventory's numbering). - Keep Recommended sequence in sync if a batch's scope changes — mark a step DONE rather than deleting it, so the doc still reads as history.
Severity tags: High (fix before real use) · Correctness (wrong output/copy today) · Moderate (UX/rough edge) · Behavioral (latent trap, invisible until triggered) · Cosmetic (dead code / doc-drift).
Status at a glance
| Pipeline | Status | Health | High-count | Note |
|---|---|---|---|---|
| P1 · Rotation generation | Mature | 100% | 0 | A2b/E1 closed (2026-07-18): fixed_pairs is deterministic circle-method + shared-scorer ELO/gender — no random search, no flaky coverage. |
| P2 · Feasibility forecast | Mature | 100% | 0 | T-9/T-10 closed (2026-07-18, approved wireframes): all-graphic stat tiles + 2-row court grid to 8 courts — the readout is symmetric, no cliff. |
| P3 · Availability / late-arrival | Mature | 100% | 0 | D4a/D3a/D5a + D1a closed (2026-07-18): session-relative rounds, scoped host RLS (00433), rollback, and the inline arrival round/time picker. |
| P4 · Tournament engines | Mature | 95% | 0 | A6 closed (2026-07-18): fake 0점 column removed, Buchholz draws-corrected + surfaced, head-to-head + competition ranking — honest standings. |
| P5 · Tournament experience | Complete | 95% | 0 | Chooser ship closed T-3/T-4; cosmetic-tail wave (2026-07-18) closed D3 (live board header) + the 조별리그 knockout-knob threading. |
| P6 · Pluggable registry | Activated | 100% | 0 | styleId now threads into generateRound's resolveRoundStrategy; registry-driven selection is live on the round path — P7's prerequisite is met. |
| P7 · Score-driven formats | Complete | 100% | 0 | All formats shipped; T-17 closed (2026-07-18) — dead results_driven policy values retired, canon corrected to the shipped styleId/registry seam. |
P1 — Rotation generation
Mature · 100% · 0 high
The core round-by-round 경기 만들기 loop — resolve pool → apply constraints → strategy builds the round → preview → commit. The mature spine; every session's matches come through here.
Shipped
- Shared pipeline + registry + constraint model (Slice 1)
- 혼복 (mixed-doubles) gender fix (
rotate_all/skill_balanced, Slice 1 soft courtScore penalty) fixed_pairsopponent-variety fix- 혼복+랜덤 gender-first pairing (quick-win batch, 2026-07-15) —
generateRandomnow forms every doubles team as 1M+1F viapairDoublesTeamsGenderFirstwhenrequireMixedGender+ real gender data are present, with best-effort maximization on skewed rosters (rotation.strategy.ts; tests inrotation.rules.test.ts)
Resolved
[Correctness A4]혼복+랜덤 yields same-gender teams — FIXED:generateRandomthreadsctx+constraintsand routes doubles through gender-first pairing instead of the flat gender-blind shuffle-and-slice; falls back to plain random when there's no gender data (quick-win batch, 2026-07-15;rotation.strategy.ts)[Cosmetic]Dead-code sweep — CLOSED (2026-07-18):skill_balancedneeded NO code change — the tracker's "= rotate_all alias" phrasing was the stale part; the code already self-documents the alias and the 실력별 preset IS behaviorally differentiated at the config layer (balanceByElo/eloToleranceconsumed by the sharedcourtScore).benchMode:'volunteer'stays as a documented future seam, but its orphanedbenchModesi18n dict (zero consumers, ko+en) was deleted.MatchFormatConfig'mixed'deleted from union + zod (verified unpersisted —RotationConfigis in-memory only, never crosses a mapper/adapter).avoidPairs/lockedPairsdeleted fromAssignmentConstraints/buildConstraintSet/3 test fixtures (no partial wiring existed to preserve). Stale comments fixed:constraints.ts"not yet wired" (it runs unconditionally ingenerateRound),types.tsphantomscoreArrangement/applyConstraintsnames,resolve-pool.ts"dormant" framing (the availability seam is live)[Moderate A2b]+[Behavioral E1]— FIXED together (2026-07-18):generateFixedPairs's bounded RANDOM shuffle-and-score replaced with the deterministic enumerate-and-score templatefreedomGreedySinglesalready established —circleMethodPairingsover synthetic per-PAIR tokens enumerates every pair-vs-pair grouping, each candidate scored via the sharedcourtScore(pairA, pairB, history, config, ratings, genderMap), lowest wins (ties → first-enumerated; zeroMath.random()in the path). This routesbalanceByElo/eloTolerance(and the grouping-invariantrequireMixedGenderterm, documented) through the shared scorer — closingA2b— AND makes opponent coverage exact rather than ~99.2%-probable — closingE1(the flaky exact-coverage test now holds with probability 1; the old "does-NOT-minimize" probabilistic test was replaced by a strictly stronger byte-equality assertion; +ELO-balance and determinism tests)
Gaps
None.
Fixes → contribution
Route— DONE 2026-07-18 (see Resolved)fixed_pairsgender/ELO through the shared scorer → closesA2bHarden— DONE 2026-07-18 (deterministic construction; see Resolved)fixed_pairscoverage, or emit a repeat warning → closesE1Dead-code sweep — honest aliases, remove/wire the dead flags— DONE 2026-07-18 (see Resolved)
Target: consistent constraint model, zero silent drops, no dead scaffolding → 100% — HIT.
P2 — Feasibility forecast
Mature · 100% · 0 high
The pre-generation "will this session work" readout — games/player, sit-outs, max-late budget, "need N more." A market-first surface (no competitor ships it).
Shipped
computeAttendanceViability+ the graphical readoutcomputeFairnessReportsurfaced- Availability-aware games/player estimate (quick-win batch, 2026-07-15) —
gamesPerPlayernow accumulates each player's own per-round slot share (respectingfromRound) instead of dividing one full-pool figure evenly; unchanged when noavailabilitymap is passed (feasibility.ts)
Resolved
[Correctness C1]"예상 게임 수" (estimated games) was availability-blind — FIXED:gamesPerPlayeris built round-by-round from the same present/courtsUsed numbersperRoundreports, crediting each player only for rounds they were actually present for (quick-win batch, 2026-07-15;feasibility.ts)[Behavioral C3]A second, deadMatchmakingStrategy.feasibility()— FIXED (2026-07-18): deleted the losing (dead) side of the name collision wholesale — the narrow Slice-1 stubFeasibilityReportinterface, thefeasibility()method from bothRoundStrategy/TournamentStrategycontracts, all 8 strategy implementations (which carried hardcoded raw-Korean strings, a domain-layer violation), theStrategyFeasibilityReportbarrel alias, and the 7 test blocks exercising it. Grep-verified ZERO production call sites existed (add-round-sheet.tsxcallscomputeAttendanceViabilitydirectly; the chooser's per-format gates are deliberately UI-owned constants).feasibility.ts's rich shape is now the singleFeasibilityReport[Moderate T-9/T-10]Readout asymmetry + >4-court cliff — FIXED (2026-07-18, owner-approved wireframe options A/A): all three stat tiles now carry a 28px graphic + bold value + micro label (games → ascending range bars, filled = min; 지각 여유 → head-count dots) so the CapacityRing stops being the odd one out; the court strip wraps 5-8 courts into a 4-col×2-row grid of 16pxCourtLinesTiles and only falls back to ratio+bar above 8 — a deliberate, commented ceiling instead of a cliff at 4. 불가 state untouched
Gaps
None.
Fixes → contribution
Consolidate to one feasibility source — wire or delete the dead one → closes— DONE 2026-07-18 (deleted; see Resolved)C3UI polish (tile symmetry, large-court treatment) → closes— DONE 2026-07-18 (see Resolved)T-9,T-10
Target: accurate, late-arrival-aware forecast from one source → 100% — HIT.
P3 — Availability / late-arrival
Mature · 100% · 0 high
Per-player arrival → the per-round generation pool. A player marked "from round 3" is benched until then. The pool-constraint seam Slice 1 built, with a Slice 2 marking UI. The 2026-07-18 wave closed the round-anchoring, RLS-scope, and rollback gaps; the wall-clock time-picker (D1a) is in wireframe review.
Shipped
available_from_round+ host tap-to-cycle rosterresolvePoolwiringstartRoundNumberfix- Honest catch-up copy (quick-win batch, 2026-07-15) — the late-arrival hint always shows the real catch-up behavior regardless of the stored
late_arrival_policyvalue, via the newlateArrivalHint()(presentation/utils/late-arrival-hint.ts); the dead/dishonestlateArrivalPolicyEqualResti18n key is removed; the schema default site documents thatequal_rest_forwardisn't implemented yet (session.entity.ts)
Resolved
[Correctness D2]Every session showed "균등 휴식" (equal rest) late-arrival copy, but the engine only ever does catch-up — FIXED via the "fix the copy" branch (not "make the policy real" —resolvePoolstill only implements catch-up, now documented inline): the UI always shows the honest catch-up hint (quick-win batch, 2026-07-15;presentation/utils/late-arrival-hint.ts,add-round-sheet.tsx)[Behavioral D4a]Absolute round constants — FIXED (2026-07-18):computeAttendanceViabilitygained an optionalstartRound = 1param (defaults byte-identical for every pre-existing call); add-round-sheet passesnextRoundNumber, so the forecast window and the host arrival cycle are anchored at the session's REAL next round ([nextRoundNumber+1 … +5], wrap → 도착). New purecycleArrivalRoundhelper + tests; the wrap maps to the round-1 "not late" sentinel so the roster badge convention holds, and a stale out-of-windowfromRoundclamps cleanly into the window on the next tap[Behavioral D3a]Whole-row host UPDATE — FIXED (2026-07-18): migration00433_rsvp_host_availability_scope(ON PROD) adds aprivate.protect_rsvp_columns_from_non_admin_host()BEFORE UPDATE trigger following 00074'sprotect_columnssilent-revert idiom with actor-conditional branching (SECDEF/self/club-admin bypass; a non-admin host's writes to anything exceptavailable_from_round/arrives_atsilently revert). RLSWITH CHECKcan't see OLD — a trigger is the only column-scoping mechanism. pgTAPrsvp_host_availability_scope.test.sql(17 asserts; suite now 27 files / 269 asserts). NOTE: 00303'ssession_paymentshost policy is the same whole-row shape but its host flow legitimately writesstatus— deliberately not remediated, flagged in the migration header[Behavioral D5a]No rollback on the availability tap — FIXED (2026-07-18):updateAvailability.mutatenow passes a per-callonErrorthat deletes the optimistic override key —effectiveAvailabilityfalls back to the server-sourced map, so deletion IS the rollback[Behavioral D1a]Wall-clock arrival never wired — FIXED (2026-07-18, owner-approved wireframe option A + alignment directive): the tap-to-cycle badge is replaced by an inline expansion card (flush with the roster's avatar/badge columns — no arbitrary indent, per owner) with session-relative round chips (도착 + next 5) and a 시간으로 chip opening the already-linked nativeDateTimePickerModal(mirrors create-session-step1; OTA-safe). A picked time writesarrives_atAND derivesavailable_from_roundvia the newarrivesAtToRound(arrivesAt, sessionStart, rotationMinutes ?? 30)util (+tests); a round chip writes the round and clears the time; the badge then shows시간 · N라운드부터viaformatTimeOfDayKo12. Same onError override-delete rollback;cycleArrivalRounddeleted with its sole consumer. Modal-over-ModalPanel is first-of-its-kind — flagged for on-device QA
Gaps
None.
Fixes → contribution
Session-relative round numbering → closes— DONE 2026-07-18 (see Resolved)D4aBuild the wall-clock time-picker → closes— DONE 2026-07-18 (see Resolved)D1aColumn-scoped host RLS + pgTAP + optimistic rollback → closes— DONE 2026-07-18 (see Resolved)D3a,D5a
Target: honest policy, round+time marking, correct mid-session numbering, tight auth → 95%
P4 — Tournament engines
Mature · 95% · 0 high
The four bracket/swiss algorithms (Swiss · Knockout · Round-Robin · Compass) + their round math + standings — the compute layer behind a tournament.
Shipped
- 4 engines wrapped as strategies
- 3 latent bugs FIXED in the wiring: bracket silent-drop, tournament dead-end knockout, compass non-8-player
Resolved
[High A2]Round-robin group-stage overflow stamped overflow pairings to a permanentcourtNumber: 0(invisible, no way to enter a result — so the group stage could never see every match'completed'and never reached knockout) — FIXED:generateGroupStagenow splits each schedule round intoceil(pairings/courtCount)wave-slots, the same fix shape already applied to the bracket engine; the round-robin-groups strategy inherits it transitively viagenerateTournamentBracket(commit58319217;tournament.rules.ts:103-158)[High A3]"다음 라운드" (next round) with partial results silently orphaned matches and dropped players — FIXED: Swiss/Compassadvance()now throws"Cannot advance: N matches unfinished"instead of silently continuing past them (commit58319217;swiss.rules.ts:348-353;compass.rules.ts:268-271,384-389)[Moderate A6]Fake standings — FIXED (2026-07-18) without adding score entry (winner-pick-only is deliberate, spec §13.5): (a) the always-0 "득점" column REMOVED from the board (pointsFor is structurally unpopulatable —TournamentMatchcarries no score); (b)calculateBuchholznow counts opponents' draws (wins + 0.5*draws, matching the file's own score convention) andswiss.strategy.ts'stoGroupStandingforwards the already-persistedbuchholzto the new optionalGroupStanding.buchholz(read-side only, zero migration); Swiss rows render a 부흐홀츠 meta chip; (c) round-robincalculateStandingsbreaks exactly-2-way score ties by head-to-head (3+-way/cyclic falls through unchanged); (d) the board uses competition ranking (competitionRankshelper — equal key ⇒ shared rank, 1-2-2-4) so bracket/compass ties stop implying false precision
Gaps
None — score-based standings remain intentionally out of scope while the board is winner-pick-only; a seed/rating tiebreak for bracket/compass would need a state-shape change and stays unscoped.
[Cosmetic E2]Compass gets stuck at small N (compass.rules.ts); orphaned second recommender —recommendFormat(4-tier,bracket.rules.ts:514) is fully orphaned while onlyrecommendStrategy(3-tier,recommend-format.ts:48) is wired
Fixes → contribution
Wire real standings — show Buchholz, stable tiebreak → closes— DONE 2026-07-18 (see Resolved; score entry deliberately NOT added)A6
Target: complete, safe engines with real standings → 95% — HIT.
P5 — Tournament experience
Complete · 95% · 0 high
The run-loop a host actually touches: create → board → advance → standings — plus how it lives in the app's IA and who's allowed to use it. Wave A+B (2026-07-15) closed the return-path/dedup/RLS/entitlement/concurrency/pairing gaps; the 2026-07-17 chooser ship closed the IA consolidation (T-3/T-4); the 2026-07-18 cosmetic-tail wave closed D3 + the 조별리그 knob threading.
Shipped (deliberately minimal wedge — "enough to inspect, not pixel-final")
- Board + create/advance mutations
- Create-tournament entry point
- Tournaments-real Wave A+B (2026-07-15) — return path (이어보기) + dedup + host-only cancel/delete recovery, host-OR-admin RLS, compare-and-swap advance, entitlement-gated create, an advance confirm dialog, ELO-balanced doubles pairing, and the bracket ko-label fix (commits
58319217+27cbea02) - P5 chooser ship (2026-07-17) — the unified 경기 방식 chooser (
format-chooser-sheet.tsx: ONE registry-backed ModalPanel, 라운드 방식 presets+score-driven group · 토너먼트 strategy group, eligibility badges/gates) replacing session-detail's triple-CTA;CreateTournamentModalgained a 대회 setup step (조별 인원 · 진출 방식 top-N/전원 시드 · 조 편성 레이팅/랜덤 · read-only 경기 시간 → match-rules hint; knobs render ONLY for bracket ≥9 entrants —round_robin_groups.inithas no options param, so knobs there would be silently ignored); the persistent 이어보기 chip renders for any ACTIVE tournament at EVERY session status; engine knobs (TournamentConfigadvancementPolicy/seeding +snakeSeedByCapacity, defaults byte-identical); naming v3 (research-grounded region-friendly names: KDK 복식/단식 · 짝바꿈 복식 · 순위매칭전 · 코트승급전 · 전적매칭 토너먼트 · 전원부활전 · 조별리그/토너먼트 kept);session.styleretired (SessionStylenarrowed to'social'|'competitive', SessionTemplate vertical deleted —generation_style_idis the real matchmaking field)
Resolved
[High T-1]One-way door — no return path to a live tournament;findBySessionexisted end-to-end but nothing called it — FIXED: session detail resolvesuseTournamentBySessionand shows a "토너먼트 이어보기" button straight to the board whenever one exists (commit27cbea02;session-detail-screen.tsx:265-267,1473-1483)[High T-2/E3]No duplicate-tournament guard (re-tap → raw Postgres unique-violation toast) + no delete/cancel mutation anywhere, so a stuck/1-player tournament was permanently undeletable — FIXED: the sameuseTournamentBySessionlookup resolves re-tap to "이어보기" instead of a second create, and the newuseCancelTournamentmutation (wraps the previously-uncalledTournamentRepositoryPort.delete) gives the host a destructive-confirm escape hatch back to a fresh create (commits58319217+27cbea02;use-cancel-tournament.ts;tournament-board-screen.tsx:210-229)[High D6]RLS required club role owner/admin/match_director, but the button showed for the literal session host, so a non-admin 번개 host hit a raw Postgres RLS-denial toast — FIXED: migration00427(APPLIED TO PROD) adds additive permissive host policies (tournaments_insertable_by_host/_updatable_by_host/_deletable_by_host,WITH CHECKpinned toclub_id), mirroring 00303's pattern, plus pgTAP00029(8 assertions) (commit58319217;supabase/migrations/00427_tournament_host_rls.sql:54-94)[High C5]No concurrency control on advance — two devices/tabs could read-then-write with no version predicate, so the second submit silently clobbered the first — FIXED:updateIfCurrentRound(id, expectedRound, input)is now an atomicWHERE id AND current_round = expectedRoundcompare-and-swap that throwsConflictErrorinstead of clobbering (commit58319217;tournament.repository.port.ts:10-19;tournament.supabase.ts:146-175;use-advance-tournament.ts:68)[High T-6]tournamentsEnabledpaid-tier entitlement was defined but unenforced — every plan could create real brackets — FIXED: the create branch (not continue/이어보기) is now gated behinduseFeatureGate(session.clubId).entitlements.tournamentsEnabled(commit27cbea02;session-detail-screen.tsx:267,1484)[Moderate D1]Doubles tournament pairing was sequential-RSVP, not ELO-balanced, able to stack two high-rated players on one team — FIXED:pairForDoublesnow sorts entrants bybuildEloSnapshotrating and cross-pairs strong+weak (rank 1 with rank N, etc.) — the standard balance technique; an odd player out still sits out unpaired (unchanged rough edge) (commit27cbea02;create-tournament-modal.tsx:73-82,105)[Moderate D4]Winner-pick had no confirm and was only zero-blocked (any single pick, not all picks, unblocked "다음 라운드") — FIXED:allWinnersPickednow requires every pending match to have a pick before the button is even pressable, and advancing routes through auseConfirmdialog first (commit27cbea02;tournament-board-screen.tsx:137-158,191-201,380-381)[Moderate T-5]토너먼트↔토너먼트 Korean name collision between the screen and the bracket-format label — FIXED: the bracket strategy's Korean label changed from "토너먼트" to "녹아웃" (commit27cbea02;bracket.strategy.ts:375-377) — superseded 2026-07-17: naming v3 restored 토너먼트 (owner-locked — in Korean it specifically means knockout); disambiguation now comes from the chooser's group structure + the 한 번 지면 탈락 subtitle[High T-3/T-4]Triple-CTA + deadsession.style+ open/locked-scoped 이어보기 — FIXED (2026-07-17): session detail's two full-width buttons collapsed into ONE 경기 방식 선택NavRow(gated host/canManageSchedule/canManageMatches × open/locked/in_progress) opening the unifiedFormatChooserSheet; a round pick seeds this screen's ownAddRoundSheetmount (settle-before-play-gated), a tournament pick preselect-skips intoCreateTournamentModal's setup step; the 이어보기 chip (TournamentContinueChip, live PulseDot + current round) renders for any active tournament at EVERY session status — a running tournament can never strand again;session.styleretired outright (entity narrowed, mapper coerces legacy rows to'social')[Moderate D3]Static "토너먼트" board header — FIXED (2026-07-18):DetailShellnow carries a live subtitle + status-gatedPulseDottitleLeading, mirroring session-detail-screen's canonical header treatment exactly.in_progressreusestournaments.continueChipLabel(formatLabel, latestRoundNumber)so the header and the session-detail 이어보기 chip say the same words about the same tournament; every other status composesformatLabel · status.<key>from keys that already existed — zero new i18n (tournament-board-screen.tsx)[Cosmetic]조별리그 silently discarded the 대회 knobs — FIXED (2026-07-18):round_robin_groups.strategy.ts'sinitnow acceptsTournamentInitOptionsand honors the ONE honestly-applicable axis —advancementPolicy/advancersPerGroupmap onto the pre-existinggenerateKnockoutStage(standings, topN)parameter via a new optionalTournamentBracket.knockoutFieldSize('all_seeded'→ full-field seeded knockout; default?? 4byte-identical to the legacy semis+final).groupSize/seedingare deliberately NOT read — the kernel runs ONE exhaustive round-robin (no pools to size or seed), so wiring them would be theater. UI knob exposure for 조별리그 stays deferred (a wireframe-first design decision: the shared panel's advancers default 2 / Stepper max 3 can't represent this strategy's default 4)
Gaps
None — the moderate/cosmetic tails above are closed; the only remaining product decision is whether to expose 조별리그's knockout-size knob in the setup UI (deferred, wireframe-first).
Fixes → contribution
IA cleanup — one lifecycle-aware entry point (covering every session status, not just open/locked), decouple/fix the style label → closes— DONE 2026-07-17 (the P5 chooser ship above)T-3,T-4,D5Live board header (— DONE 2026-07-18 (see Resolved)D3) + 조별리그 knob threading
Target: a recoverable, correctly-gated, coherent tournament experience → 95% — HIT. No High-severity items remain in any pipeline.
P6 — Pluggable registry
Activated · 100% · 0 high
The config-driven strategy architecture — the owner's original ask ("choose from algorithms, keep adding"). Adding an engine = one registry entry.
Shipped
- Split
RoundStrategy | TournamentStrategycontract - Registry + 5 strategies
recommendStrategy- Registry-driven round-path activation (2026-07-15) —
GenerateOptions.styleIdnow threads intogenerateRound'sresolveRoundStrategy(falls back toround_robinwhen unset, so today's output is byte-identical); proven live end-to-end by P7'samericano/mexicanoregistrations (commit8413c450;rotation.rules.ts:99,237-239)
Resolved
[Behavioral B1]generateRoundhardcodedDEFAULT_GENERATION_POLICY;styleIdwas never set anywhere, so the round path couldn't actually select a strategy — FIXED:GenerateOptions.styleIdnow threads intogenerateRound'sresolveRoundStrategy; registry-driven selection is live on the round path (byte-identical when nostyleIdis set) (commit8413c450;rotation.rules.ts:99,237-239)
Gaps
None — B1 was the only gap and it's resolved above.
Fixes → contribution
- Thread the session's
GenerationPolicy(withstyleId) intogenerateRound→ closedB1— DONE (commit8413c450): activated registry-driven selection on the live path; was the exact seam P7 needed
Target: fully config-driven selection → 100% — HIT. Was the prerequisite for P7 — now satisfied.
P7 — Score-driven formats
Complete · 100% · 0 high
Americano · Mexicano · King-of-the-Court — the net-new formats where pairings adapt to accumulated points and courts move by result.
Shipped
- Americano — rotate-all generation (delegates to
rotationStrategy'srotate_allpath,americano.strategy.ts:31-42); its market identity is individual-point scoring, computed by the sharedcomputePlayerPointsledger (score-driven.ts:33) (backend, commit8413c450) - Mexicano — rank-adaptive: ranks the pool by
pointsMapeach round and pairs rank-crossover (1&3 vs 2&4 in doubles, adjacent-rank in singles); falls back torotate_allon round 1 / before any points exist (backend, commit8413c450;mexicano.strategy.ts:36-41,66-83) - The
styleIdselection seam — both formats registered inSTRATEGY_REGISTRYand selectable in the 방식 picker alongside the rotation presets (UI wave, uncommitted-but-verified;add-round-sheet.tsx:116-117,128-132) - The
rollingGenerationLifecycleenum value — KOTC's future foundation; no strategy sets it yet (commit8413c450;generation-policy.entity.ts:38) - King-of-the-Court (2026-07-16) — the rolling court-ladder:
king-of-court.strategy.tssnake-seeds round 1 by ELO (balances court strength), then each round's ascender/descender is derived from the prior court results; a 5:5 draw tiebreaks for MOVEMENT ONLY (higher cumulative points → deterministic 가위바위보; the match stays a real draw for score/points/ELO), partners reshuffle within each court. Court-primary 킹코트 ladder scorecard tab (reusesMatchScoreboard+ anElapsedClockambient timer) with a ▲승격/▼강등/👑방어 movement preview + 가위바위보/점수 badges; 방식 picker entry gated to 2+ courts;kingOfCourtMatchRules(allowDraw + no tiebreak). Rides the score-driven 순위 tab + the draw-aware foundation — no new migration. - The 순위 standings tab (2026-07-15) — the session points ladder (
computePlayerPoints) + a personal "오늘의 영향" rating-impact cascade (복식/단식 레이팅 before→after · 클럽 내 순위 movement · 전국 순위 movement), read from storedelo_history(no new rating math); composed only from shared atoms (PlayerIdentityChip/EloTierBadge, no sibling-feature import), friendly sessions render ladder-only (commitf51e9f7b;standings-tab.tsx) sessions.generation_style_idpersistence (migration 00429) + theisScoreDrivenStylepredicate that gates the tab; set-on-generate wires the host's chosen style onto the session record (commitf51e9f7b)get_session_rating_impactRPC (migration 00430, SECURITY DEFINER +search_path=''+ a union session-visibility guard) +useSessionRatingImpact— per-participant session ELO delta + national/club rank movement, the cascade's data source (commitf51e9f7b)- The 친선 rating-gate (migration 00428) —
apply_match_ratingsnow skips ELO onis_friendlysessions, so the long-promised "ELO 미반영" label is finally true; the correctness prerequisite that makes honest impact rendering possible, and it also covers therecompute_all_ratingsreplay. pgTAP-proven 8/8 (commitfcb4bcb1)
Resolved
[Cosmetic T-14]Registry had only a stub comment for americano/mexicano, androllingwasn't aGenerationLifecyclevalue yet — FIXED: both strategies are realSTRATEGY_REGISTRYentries now (only King-of-the-Court remains a stub comment) androllingis a real enum + zod value (commit8413c450;registry.ts:51-53;generation-policy.entity.ts:38)[Moderate T-15]No points-standings surface + scoring mode not persisted — FIXED: the 순위 tab renders the points ladder + the personal rating-impact cascade;sessions.generation_style_idpersists the host's chosen score-driven style, and the impact reads realelo_historydeltas (migrations 00428/00429/00430; commitf51e9f7b)[Cosmetic T-17]Deadresults_drivenpolicy value — FIXED (2026-07-18): RETIRED the never-assigned/never-read'results_driven'member from thecompositionaxis AND its identically-dead twin on thetriggeraxis (generation-policy.entity.tstypes + zod). Routing was ruled out on evidence: the only reader of anyGenerationPolicyfield isresolveStrategy, which reads.styleIdexclusively, and the live classification already exists in three consumed places (each strategy'sfamilyfield,isScoreDrivenStyle(), the picker'sSelectedStyle.kind) — a policy-layer mirror would just re-drift. The axes themselves stay (six-axis canon intact);docs/canon/match-generation.mdcorrected — its planned KOTC-via-composition activation was superseded by the styleId/registry seam that actually shipped 2026-07-16
Gaps
None.
Build set → contribution
Points-standings/leaderboard surface + persist the scoring mode → closes— DONE (commitT-15f51e9f7b; migrations 00428/00429/00430; the 순위 tab + rating-impact cascade)King-of-the-Court — the— DONE 2026-07-16 (snake-seed → ascender/descender movement → 가위바위보 draw-tiebreak; court-primary ladder tab)rollinglifecycle's court-movement rulesRoute Americano/Mexicano through the— DONE 2026-07-18 (retired; see Resolved)composition:'results_driven'axis, or retire the axis → closesT-17
Target: full format coverage → HIT. Americano + Mexicano + the 순위 standings/rating-impact surface + the 친선 gate + King-of-the-Court (rolling court-ladder) all shipped. P5's T-3/T-4 IA consolidation closed 2026-07-17 — no design-led items remain across the 7 pipelines.
Recommended sequence
① Correctness quick-win (P1 · P2 · P3) — DONE (2026-07-15). A4 (혼복+random gender), C1 (games estimate), D2 (late-arrival copy) — all three fixed + unit-tested in one batch: rotation.strategy.ts + rotation.rules.test.ts (A4), feasibility.ts + feasibility.test.ts (C1), presentation/utils/late-arrival-hint.ts + add-round-sheet.tsx (D2). All JS-only, no migration.
② Make tournaments real (P5 + P4's A2/A3) — DONE (2026-07-15; T-3/T-4 IA consolidation closed 2026-07-17 via the wireframe-approved chooser ship). Two waves: Wave A (backend/data layer, commit 58319217) — round-robin wave-slots (A2), the Swiss/Compass all-results-in gate (A3), the useTournamentBySession/useCancelTournament seam (T-2/E3), compare-and-swap advance (C5), and host-OR-admin RLS (D6, migration 00427, applied to prod). Wave B (UI, commit 27cbea02) — wired Wave A's hooks into session detail (T-1/T-2, the T-6 entitlement gate), the advance confirm dialog (D4), ELO-balanced doubles pairing (D1), and the bracket ko-label fix (T-5). Several were copy-paste of patterns already in the codebase (00303 RLS, the bracket overflow fix), as planned. Moved P5 from unusable to usable — remaining: T-3/T-4's fuller IA consolidation, deferred to a wireframe pass.
③ Activate the registry (P6) — DONE (2026-07-15). Threaded styleId into the round path — the pluggable spine is fully live (per-session algorithm choice); the seam is proven by Americano/Mexicano's own registrations. One focused change (commit 8413c450).
④ Score-driven formats (P7) — COMPLETE. Americano + Mexicano + registry (commit 8413c450), the 순위 standings + rating-impact + generation_style_id + 친선 gate (f51e9f7b/fcb4bcb1; migrations 00428/00429/00430), then King-of-the-Court — the rolling court-ladder + 가위바위보 draw-tiebreak — 2026-07-16. All shipped; no design-led remainder in P7.
⑤ Cosmetic tail — DONE (2026-07-18). One research-grounded wave over the remaining cosmetic/dead-code/doc-drift items: D3 live board header, 조별리그 knockout-knob threading, T-17 dead results_driven retirement (+ canon correction), the P1 dead-code sweep (dead i18n dict, 'mixed', avoidPairs/lockedPairs, stale comments; skill_balanced confirmed already-honest — no code change), and the C3 dead feasibility-stub deletion. The 2026-07-18 behavior wave then closed A2b/E1, D4a/D3a/D5a, A6, and — after the owner approved the wireframes — T-9/T-10 and D1a. Every finding from the original audit inventory is now closed; what remains anywhere is deliberately-scoped future work (score-based tournament standings if score entry ever ships, seed-persisted bracket tiebreaks, 조별리그 knob UI exposure).
Links
Companion artifacts (claude.ai Artifacts — require the same claude.ai account access as the one that published them; not publicly hosted):
- Matchmaking Pipelines — visual tracker — the graphical twin of this doc (rollup strip + per-pipeline cards); update both when a pipeline's numbers change
- Matchmaking Audit — flat inventory — all ~45 findings ungrouped, one card each, plus the full surface/inspection map ("how to reach it on preview")
- Matchmaking Engine Architecture — brief — the shared-pipeline + registry plan this tracker's P1/P6 gaps trace back to; the "engine-architecture brief" the spec below cites
- Earlier research passes, superseded, kept for provenance: matchmaking-architecture (Korean-language architecture brief, superseded by the engine-architecture brief above) · match-gen-audit (pre-Slice-3 wireframe-vs-code requirements audit)
Repo docs:
docs/architecture/matchmaking-pipeline-spec.md— the Slice 1 implementation spec (shared pipeline + registry + constraint model) that P1/P6 are built fromdocs/canon/match-generation.md— the six-axisGenerationPolicyconventions doc (the spine P3/P6/P7 activate axes on)
Source: 3 read-only adversarial audit passes over the shipped system (Slices 0–3), verified against source, live on preview OTA 559eaaa9. This tracker regroups ~45 findings into the 7 pipelines above — see the flat audit inventory (linked above) for the complete, ungrouped list with the full surface/inspection map.