Skip to content

Player Profile Schema

Dominique Devereaux edited this page Aug 23, 2026 · 1 revision

Player Profile Schema & Matchmaking

Overview

A player profile is two components merged into one queryable object:

  1. Stats — sourced from actor.rpg.stats on AT Proto (Phase 2, planned)
  2. Deck — sourced from Firestore playerProfiles collection (to be built alongside Sealed Deck mechanic)

Matchmaking for the 4-player engine reads both to balance skill and faction composition across the table.


Design & Architecture

  • AT-Protocol — the-rift bot, Bluesky posting, Phase 2 stats write-back
  • Player-Profile-Schema — Sealed Deck + stat matchmaking, Firestore schema, build order

Profile Model

PlayerProfile {
  // Identity
  did: string                    // AT Proto DID — primary key across both systems

  // From actor.rpg.stats (AT Proto — Phase 2)
  acpl: number                   // Average centipawn loss — skill proxy
  winRate: number                // Overall win rate
  veilRate: number               // How often their pieces get veiled — aggression proxy
  rebirthAdvanceCount: number    // Promotion cap hits — pressure-style proxy

  // From Firestore playerProfiles (Sealed Deck mechanic — to be built)
  primarySuit: "spades" | "hearts" | "diamonds" | "clubs"
  deck: CardSelection[]          // Which face cards / pip cards they run
  playstyle: derived             // Conquest / Restoration / Intelligence / Fortification
}

Firestore Schema

Collection: playerProfiles Document key: player's AT Proto DID

{
  did: "did:plc:...",
  primarySuit: "diamonds",
  deck: [
    { card: "courier", suit: "diamonds", ability: "courier_intercept" },
    { card: "spy",     suit: "diamonds", ability: "spys_glance" }
  ],
  playstyle: "intelligence",
  updatedAt: Timestamp
}

Stats are not duplicated in Firestore — they're fetched from actor.rpg.stats at matchmaking time and merged in memory. Firestore owns deck data only.


Matchmaking Logic

For a 4-player table, balance across two axes:

Skill Axis

  • Compute MMR from acpl + winRate
  • Find players within a tolerance window (e.g. ±150 MMR)
  • Prefer tighter windows; widen if lobby pool is small

Faction Axis

The four suits have distinct mechanical roles — a table missing a role is mechanically weaker:

Suit Role Why It Matters
Diamonds Intelligence / Information Counters Fog Mode hidden info (courier_intercept, spys_glance)
Hearts Restoration / Sustain HP recovery; game longevity (phoenix_rite, menders_grace)
Spades Conquest / Aggression Pressure and tempo
Clubs Fortification / Defense Board control and anchoring

Composition targets:

  • At least 1 Diamonds player at any Fog Mode table
  • At least 1 Hearts player (sustain)
  • No more than 2 players sharing the same suit (prevents mono-doctrine games)

Algorithm:

  1. Find all players within skill tolerance
  2. Score each possible 4-player grouping by faction coverage (all 4 suits covered = highest score)
  3. Prefer highest faction score within skill window
  4. Fall back to any skill-balanced table if faction balance isn't achievable from the current lobby pool

Deck Selection Write Trigger

When a player confirms their deck selection in the UI, write to Firestore immediately:

await db.collection('playerProfiles').doc(playerDid).set({
  did: playerDid,
  primarySuit: selectedSuit,
  deck: selectedCards,
  playstyle: derivePlaystyle(selectedSuit),
  updatedAt: Firestore.Timestamp.now()
}, { merge: true });

Use merge: true so a deck update doesn't wipe stats data if they ever co-locate.


Build Order

This system builds in sequence with the broader roadmap:

  1. Fog Mode playtest in Duet (in progress)
  2. Sealed Deck mechanic in Duet → deck selection UI + Firestore playerProfiles write built here
  3. AT Proto Phase 2actor.rpg.stats OAuth write-back; stats now live on AT Proto
  4. Matchmaking reads Firestore (deck) + AT Proto (stats) → full profile available
  5. 4-player engine matchmaking uses the complete profile

Design Reference

Paragon (Epic Games) used a card-based system for hero build customization — proof that card-driven MOBA builds work at scale. Key distinction: in Paragon, cards were separate from hero identity. In Veiled Dominion, the deck is the identity — Sufi Tariqah vs. Systems Quartet are worldviews with mechanical consequences, not just stat modifiers. The matchmaking system needs to respect that distinction: faction balance is not cosmetic, it's structural.


Related Pages

Clone this wiki locally