Files
vyndr/specs/combat-intelligence.md
T
2026-07-13 16:07:33 -04:00

7.1 KiB
Raw Blame History

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 cardweb/src/components/vyndr/FightCard.tsx: FIGHTER A (initials monogram, name, record e.g. 261, 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.
  • Routeweb/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.