Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.1 KiB
VYNDR — COMBAT INTELLIGENCE LAYER (spec)
Wave 6 of the wiring/data train. Net-new sport (MMA/UFC). Governs the combat build. Honest free v1; the full matchup-GRADE engine is a DEFERRED sub-wave. Build toward specs/design-reference/vyndr-system.html (FIGHTER A / VERDICT / FIGHTER B, GRAPPLER%/STRIKER% blend, tale-of-the-tape, MONEYLINE/DECISION/SUBMISSION/KO grades). Keep the live wordmark.
DATA SEMANTICS (unchanged, restated for combat)
VYNDR never generates odds — ML/round-total values are REAL book numbers at a timestamp. Fighter records/physicals/style stats are REAL sourced facts. Model output (style edge, any grade) is always labeled MODEL. Number(null)===0 is the trap — absent stat ⇒ absent, never 0. Never ingest a fighter's photo/likeness (same rule as headshots) — initials monogram only. Scraping fragility ⇒ degrade to absent, never fabricate.
SCOPE — v1 (this wave) vs DEFERRED
v1 ships: fight-card discovery + tale-of-the-tape + style-blend archetypes + ML & round-total odds + a style-edge VERDICT (a MODEL style read, explicitly NOT a settled grade).
DEFERRED (own sub-wave, do NOT build now): the matchup-GRADE engine that produces settled ML/method/round grades; method-of-victory & round & fighter props (data-limited on the free feed); combat outcome SETTLEMENT (no free box-score settle path yet — grades would stay pending); ufcstats.com scraping (needs a parser dep — CONFIRM before adding).
DATA SOURCES (zero-out-of-pocket)
- ESPN MMA (FREE, JSON, no auth — same family VYNDR already uses):
- Fight cards / schedule:
site.api.espn.com/apis/site/v2/sports/mma/ufc/scoreboard(date-pinned like the other sports). - Event / fight detail + results + tale-of-the-tape:
.../mma/ufc/summary?event={id}and the athlete endpoints (athlete.id, record, stance, reach, weight class). ESPN athlete id also gives a headshot via the entity layer'sa.espncdn.com/i/headshots/mma/...pattern — but per the likeness rule, v1 uses initials monograms; wire the id but default to monogram. - Depth caveat (honest): ESPN MMA striking/grappling granularity is THINNER than ufcstats. Style-blend is best-effort from what ESPN exposes (finish history, method-of-victory counts, takedown/strike splits where present); when a stat is absent, the blend says less — never invents.
- Fight cards / schedule:
- The Odds API
mma_mixed_martial_arts(ALREADY PAID —ODDS_API_KEY): wireoddsService.SPORT_KEYS.mma = 'mma_mixed_martial_arts'+MMA_MARKETS = ['h2h','totals'](moneyline + round totals). ReuseoddsNormalizer. PropLine has NO combat → odds-api-only; combat does NOT enter the abundant-props path.
COMBAT ARCHETYPE REGISTRY (PINNED — both src/services/archetypeService.js AND web/src/lib/archetypes.js use these EXACT names/colors/glyphs; a test asserts they match, same as other sports). Colors unique WITHIN combat; may reuse hues used in other sports (cross-sport reuse is fine).
Six styles. classify('mma', fighterStats) returns { primary, secondary|null, blend:[{archetype,weight}] } (same contract as other sports) scored from finish-rate / method splits / takedown & strike tendencies.
| Name | Glyph | Color | One-liner (shown where it leads) |
|---|---|---|---|
| STRIKER | ✦ | #E8703A |
Wins on the feet — volume + power at range. |
| GRAPPLER | ⊗ | #2FA4E7 |
Fight hits the mat on his terms — control + subs. |
| PRESSURE | ➤ | #E4574C |
Forward, relentless, breaks the pace. |
| COUNTER | ◊ | #8E7BE0 |
Patient — punishes what you show him. |
| FINISHER | ▲ | #12B886 |
Ends nights — high KO/SUB rate. |
| GRINDER | ▦ | #B0883B |
Goes the distance, wins the rounds. |
| STRIKER↔GRAPPLER is the primary range axis the mockup renders as the two blend bars; PRESSURE/COUNTER is tempo; FINISHER/GRINDER is the outcome tendency. A fighter is a BLEND (e.g. GRAPPLER 80% / STRIKER 30%). Discipline pedigree tags (Combat Sambo, Dagestan Wrestling, BJJ, Wrestling Base, Kickboxing, Muay Thai, Boxing) are verifiable credentials, rendered separately from the archetype blend — VERIFIABLE ONLY, mark unknowns absent, never guess a fighter's base. |
STYLE-MATCHUP VERDICT (v1 — a MODEL read, not a grade)
styleMatchup(fighterA, fighterB) → a style-edge verdict (e.g. "GRAPPLER EDGE · Islam") from comparing the two blends + finish/defense tendencies. This is a descriptive MODEL read, labeled as such — NOT a settled grade, NOT an edge %, NO fabricated confidence. The mockup's CENTER VERDICT. Keep it honest: if the data is too thin to call, say "STYLES EVEN / INSUFFICIENT READ".
CONFIG WIRING (the recurring 4-layer-desync trap — do all in sync)
src/config/sports.jsSPORT_CONFIG+ legacySPORTS(flipmma.active),web/src/config/sports.tsmirror.oddsService.SPORT_KEYS/SPORT_MARKETS+oddsNormalizerMMA market keys.src/config/statFilters.js+web/src/config/statFilters.ts— combat stat categories (if any props surface).- Combat is NOT added to
snapshotService.ACTIVE_SPORTSin v1 (no settle path) — fight cards + odds + style card render on-read; grades stay OUT of the locked-snapshot loop until the deferred engine.
FRONTEND (toward the mockup)
- Tale-of-the-tape / head-to-head style card —
web/src/components/vyndr/FightCard.tsx: FIGHTER A (initials monogram, name, record e.g. 26–1, stance, reach) · GRAPPLER%/STRIKER% blend bars · discipline pedigree tags · archetype chip(s) via the sharedArchetypeBadge· CENTER VERDICT (style edge) · FIGHTER B mirror · a grades/odds row (MONEYLINE + round total from the odds feed; method/KO/SUB shown as "— data-limited" honest placeholders, NOT fabricated). Two fighters side-by-side (NOT the player-strip row grammar). Mono data, no glitch on data. - MMA badge/color — the
#D4AF37combat token already exists inshareCards/tokens.js; add a frontend MMA sport token +SportBadgesupport. - Route —
web/src/app/fight/[id]/page.tsx(server wrapper + client card) and/or a combat surface on the schedule; add Next proxies for the ESPN-MMA read (S25 rule). Self-hide / honest empty (reuseEmptyState) off-season. - Sportsbook links via the existing
bookLinks.js/affiliate layer unchanged.
TESTS
archetypeService/archetypes.jscombat colors MATCH (extend the existing cross-file color test).classify('mma', …)blends correctly from fixtures; thin data → fewer/absent style claims (never fabricated).styleMatchup→ honest "insufficient read" on thin data; a clear edge on divergent styles.- combat adapter: defensive parse (ESPN shape → normalized fight card; unrecognized → empty, never throw); injectable, NO network.
oddsNormalizerMMA market keys map (h2h/totals), else combat odds silently zero.- FightCard self-hides / honest-empty; renders tale-of-the-tape mono; method/round shown as data-limited, not fabricated.
OUT OF SCOPE REMINDER
No settled grades, no method/round/props board, no fighter photos, no scraping dep — all DEFERRED and flagged in-UI as data-limited rather than promised. The cheapest honest v1 is the goal: styles + tape + ML/round-totals, beautifully rendered.