# 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's `a.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. - **The Odds API `mma_mixed_martial_arts` (ALREADY PAID — `ODDS_API_KEY`):** wire `oddsService.SPORT_KEYS.mma = 'mma_mixed_martial_arts'` + `MMA_MARKETS = ['h2h','totals']` (moneyline + round totals). Reuse `oddsNormalizer`. **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.js` `SPORT_CONFIG` + legacy `SPORTS` (flip `mma.active`), `web/src/config/sports.ts` mirror. - `oddsService.SPORT_KEYS`/`SPORT_MARKETS` + `oddsNormalizer` MMA 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_SPORTS` in 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 shared `ArchetypeBadge` · 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 `#D4AF37` combat token already exists in `shareCards/tokens.js`; add a frontend MMA sport token + `SportBadge` support. - **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 (reuse `EmptyState`) off-season. - Sportsbook links via the existing `bookLinks.js`/affiliate layer unchanged. ## TESTS - `archetypeService`/`archetypes.js` combat 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. - `oddsNormalizer` MMA 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.