International Rating-System Audit — JP / US / EU (adversarial)
Status: Active · Last reviewed: 2026-08-11
Owner-commissioned adversarial audit (2026-08-11): three parallel agents each researched a target market's real rating ecosystem, then attacked the shipped code by attempting to write that market's
RatingRegionConfig+tierCredentialsentry, documenting everything inexpressible, mislabeled, or silently dishonest. Draft attack configs live in the session scratchpad; this doc is the distilled verdict + fix roadmap.
Verdict
The rating ENGINE is genuinely portable; the display/calibration layer is KR-only in three structural ways; the market-selection plumbing is 0% wired. No target market ships today. The registry pattern itself survived the audit — every agent independently concluded the shape is right and the wiring/field-vocabulary is what fails.
One live KR-facing bug was found and fixed immediately (76b75349): session cards carried pre-recalibration NTRP bands (마스터 5.0–6.5) contradicting the wizard's ratified table (4.0–4.5) — two NTRP ladders in one app. skill-band.ts now derives from TIER_RATING_TABLE (drift structurally impossible, DRIFT PIN test added); the dead stale ranking.tierDesc/tierNtrp/tierUtr keys were deleted.
What the markets actually run
| Market | Dominant scale | Direction | Discipline split | Credential culture | Elite segregation |
|---|---|---|---|---|---|
| KR | (none official) NTRP folklore + division ladders | ascending | doubles-primary culture | division podiums (신인부→오픈부, 개나리→국화, KASTA groups) | YES — formal (선출 sanctions, amateur-only groups) |
| JP | WTN (JTA adopting); 草トー per-organizer 級 labels; JOP for competitive | WTN: 40→1 (reversed) | WTN splits S/D | non-standardized per-platform levels (テニスベア Lv.1–9 etc.) | partial, per-organizer, unenforced |
| US | USTA NTRP (ONE number, not discipline-split) + UTR (S/D split) | ascending | NTRP: none; UTR: S/D | NTRP-banded leagues/events; mixed = PAIR-SUM cap (7.0 = sum≤7.0) | none — college/HS play is a normal self-rate input |
| DE | DTB LK 1–25 | REVERSED (1=best) | NONE — one LK fed by all formats | your LK IS the credential (orders Medenspiele lineups) | none |
| FR | FFT classement (NC, 40…30/2…15/1…0, séries) | ordinal, NON-NUMERIC | singles-primary | your classement IS the credential | none |
| UK | WTN (LTA fully adopted) | 40→1 reversed | S/D split | WTN IS the credential | none |
Convergent findings (all three audits independently)
- BLOCKER — the registry is decorative: zero market plumbing. Every
getRatingRegionConfig()/getMarketConventions()call site passes no argument (grep-verified). Nothing threads a market from profile (nationalityCodeexists, never read for this) or club (adm1Slugis a different concept).TierInfoSheetbypasses the registry entirely (literal'doubles'/'singles'against the module table). A perfect JP/DE config entry would be inert code. - BLOCKER —
TierRatingBandhardcodes the NTRP+UTR vocabulary. Fields arentrpMin/ntrpMax/utrMin/utrMax;formatNtrpBand/formatUtrBandprint literal"NTRP "/"UTR ";primaryScale/secondaryScaleon the config are DEAD parameters (only a shape test reads them). Inexpressible: WTN (reversed 40→1), LK (reversed 25→1), FFT classement (non-numeric ordinal). Storing them in the NTRP slots would be a fabricated scale. - BLOCKER — discipline-axis assumptions don't universalize. US NTRP is ONE number (the dual-column table would fabricate a "Doubles NTRP"); DE LK has NO discipline split; US mixed is a pair-sum GATE on two players' ratings, not a per-player track (inexpressible in
player_ratings' one-row-per-player shape). The INTERNAL three-track ELO ruling stays correct — the mismatch is between internal tracks and each market's EXTERNAL anchor axes, which the config must be able to declare per market. - MAJOR —
EliteBackgroundis KR-pipeline-shaped and DB-frozen. The 7-value CHECK constraint is global (a market variance needs a migration, breaking the config-only promise). Content-wise: maps well to JP (실업단 ≈ pro_team), badly to US ('college' collapses D1..juco — a range wider than KR's whole ladder; no segregation culture) and EU (no analog at all — ex-pros just have low LK;eliteLadder: []correctly expresses "no ladder" but the enum/write-path can't hold EU realities). - MAJOR — the credential-ROW concept itself is KR-shaped. In DE/FR/UK your rating IS the credential (no division ladder exists);
tierCredentialsrows would need to switch semantic mode per market ("podium evidence" vs "rating range"), and a missing region subtree currently degrades SILENTLY (row just disappears). JP's 草トー labels are per-platform, non-standardized — citing them as if universal would be a specificity lie. - MAJOR —
SKILL_DISPLAY_MODEis a global constant (tier.config.ts), not per-region config, despite being the canonical per-market display decision.
What genuinely held up
- Internal ELO engine + five earned tiers + promotion math: region-agnostic, no change needed for any market.
- Three-track independence (internal): sound everywhere; the ruling stands.
- The registry PATTERN (
Record<string, RatingRegionConfig>+ KR fallback) andeliteLadder: []. market.config.tsJP/DE rows are content-correct (unreachable, but correct).- Anchors are BETTER abroad than at home: DTB LK and FFT classement are public per-player numbers; WTN/UTR have APIs; USTA NTRP is queryable. KR's claim→corroborate design is the hard case — every target market has an easier verification story once
player_external_ratings(planned, not yet built) exists.
Fix roadmap (proposed S7 — owner decisions marked ◆)
- S7a Scale-generic bands. Replace NTRP/UTR-named fields with per-region scale descriptors:
{ scale: { id, name, direction: 'asc'|'desc', kind: 'decimal'|'ordinal', levels? }, bands: Record<EloTier, {min,max}| {levelRange}> }; formatters read the descriptor (kills the deadprimaryScaleparams). Ordinal support covers FFT. ◆ approve shape. - S7b Market plumbing. One resolver (club's country → rating region; profile fallback) threaded to
getRatingRegionConfig(...)/getMarketConventions(...)call sites +TierInfoSheetde-hardcoded +SKILL_DISPLAY_MODEinto the region config. Mechanical once the resolver rule is decided ◆ (club-country vs user-country precedence). - S7c External-anchor axes per market. Config declares how external scales map onto internal tracks (
external: [{scale, disciplines: 'per-discipline'|'unified'|'pair-sum'}]) so US-NTRP (unified) and UTR (split) coexist honestly; UI stops fabricating discipline-split labels where the market has none. - S7d Elite background v2. Either widen the CHECK to a per-region-validated TEXT + region config owns the value set, or keep the 7 rungs as a universal coarse scale with per-region display mapping ◆. US 'college' sub-granularity (D1/D3) decision rides here.
- S7e Credential-row modes.
tierCredentialsentries become{mode: 'podium'|'rating-range', rows}; missing region falls back to rating-range mode derived from bands (kills the silent-undefined). - S7f
player_external_ratings(already in the overhaul blueprint §4) — unblocks the superior US/EU verification stories.
None of this blocks KR. The app-not-live clean-break window applies to S7a/S7c/S7d schema shapes — cheaper now than after launch.