BaaS Module Technical Specifications
Glicko-2 matchmaking with MMR + Rating Deviation, double-entry virtual currency ledger, sandboxed JS / TS cloud functions via a Node vm sandbox, rule-based anti-cheat (5 rule types), battle passes, tournaments, guild wars, push notifications across APNs / FCM / Web Push, WebRTC voice via SFU adapters with spatial 3D audio, in-game inbox with transactional reward delivery, achievements, challenges, leaderboards, LiveOps, marketplace, GDPR / COPPA / CCPA player-data export and deletion.
System Metrics
Data model
70 collections across every domain, all tenant-isolated and queryable via the standard Aeion API
Rating algorithm
Glicko-2 (MMR + RD)
Currency model
Double-entry ledger (atomic debit + credit)
Function runtime
Node `vm` sandbox (JS/TS transpiled); QuickJS WASM is roadmapped
Function memory limit
Configurable (default 128MB)
Matchmaking
MMR + RD widening (4 steps, configurable)
Push platforms
APNs (iOS), FCM (Android), Web Push
Loot roll
Seeded (deterministic) or probabilistic
Architecture Overview
BaaS spans capabilities across every domain of a game backend:
Capability inventory
├── Push notifications — APNs/FCM/WebPush, campaigns, device tokens
├── Inventory — Item holdings, bundles, trades
├── Inbox — In-game messages with attached rewards
├── Battle pass — Tiered progression, XP grants, reward tiers
├── Tournaments — Bracket/round-robin, prizes, team/solo
├── Anti-cheat — Rule engine, severity, auto-ban
├── Matchmaking — MMR+RD queue, widening, parties
├── Function executor — Sandboxed JS/TS VM execution
├── Leaderboards — Score submission, anti-cheat validation, resets
├── Guild wars — Guild vs guild competition
├── Match lifecycle — Match creation, events, disputes
├── Remote config — Feature flags, A/B test config
├── Challenges — Objectives, progress tracking, rewards
├── Currency ledger — Double-entry ledger, atomic transactions
├── Match chat — Per-match chat, mutes, moderation
├── Live ops — Events, seasons, participation tracking
├── Marketplace — Player-to-player item trading
├── Function deployment — Code deployment, versioning, analysis
├── Alerting — Real-time metric alerting
├── Guilds — Guild lifecycle, ranks, applications
├── Match rooms — Room management, player slots
├── Chat channels — Text channel subscriptions
├── Presence — Online/offline, last seen
├── Retention — Cohort tracking, churn signals
├── Loot — Drop rate resolution, seed-based rolls
├── Rating engine — Glicko-2 implementation
├── Trading — Atomic two-party trades
├── Voice — Voice room management
├── Bans — Account suspension, ban appeals
├── Match room broadcast — Real-time room event broadcast
├── Matchmaking tickets — Match seat reservations
├── Reward retries — Retry queue for failed rewards
├── Usage metering — Billing usage metering
├── Webhook delivery — Outbound webhook retry logic
├── Function triggers — Live log streaming, event triggers
├── Realtime broadcast — WebSocket broadcast hub
├── Usage rollups — Hourly → daily → monthly rollup
├── Achievements — Achievement definitions + progress
└── Async job queue — Background job processing for async tasks
Data model (50+ tables): The full schema spans every domain above — projects, players, currencies and balances, inventory and trades, matches and disputes, guilds and guild wars, live events, inbox messages, remote config, chat and presence, anti-cheat rules and flags, bans, matchmaking tickets, player ratings, functions and executions, and usage aggregates — all tenant-isolated and queryable via the standard Aeion API.
Rating Engine — Glicko-2 Implementation
Glicko-2 is a significant upgrade over ELO. It tracks:
- Rating (r): True skill estimate (starts at 1500 for new players)
- Rating Deviation (RD): Confidence in the rating (starts at 350, decays for inactive players)
- Volatility (σ): How erratic the player's performance is
Each player's rating record:
json
{
"rating": 1500,
"rd": 350,
"volatility": 0.06,
"gamesPlayed": 0,
"isRanked": false,
"lastGameAt": "2026-06-01T00:00:00Z"
}
RD decay: If a player is inactive, RD increases each rating period (typically weekly) using the published Glicko-2 decay formula, tuned by a configurable decay constant (default: 5-10). This means a player who hasn't played in 4 months has a much higher RD — their rating is less certain, and they should be matched more loosely.
After a match: Both players' rating, RD, and volatility update based on the outcome (win, loss, or draw) relative to the expected result.
Matchmaking integration: The matchmaking engine reads each player's RD alongside their rating. Two players with similar ratings but very different RDs are matched with different tolerances — a player with RD=350 (new) can be matched against RD=50 (veteran) as long as the MMR gap is within the widen threshold.
Matchmaking — MMR + RD Queue Model
Queue configuration:
json
{
"name": "string",
"region": "string",
"gameMode": "string",
"minPlayers": 2,
"maxPlayers": 10,
"teamSize": 1,
"skillDeltaSteps": [50, 100, 200, 400],
"widenTimesSeconds": [0, 30, 60, 120],
"antiSmurfGames": 10,
"defaultStartingMmr": 1500,
"defaultStartingRd": 350,
"ticketHeartbeatSeconds": 5,
"ticketMaxLifetimeSeconds": 300,
"priorityBias": "fifo | skill | fast"
}
Each queue ticket tracks the player, an optional party ID (for squad matchmaking), current MMR and RD, ticket state (waiting / matched / cancelled / expired), and heartbeat/match timestamps.
Match draft (the core matching algorithm): when the matchmaker forms a match, it groups tickets into teams and records the average MMR and MMR spread (max − min) across the resulting match, alongside the queue and region.
Widen strategy: skillDeltaSteps (4 values) and widenTimesSeconds (3 values) are deliberately asymmetric — the queue starts at the first skill-delta step immediately, then widens to each subsequent step as its corresponding time threshold passes. After the last configured time threshold, the spread caps at the last configured step.
Anti-smurf enforcement: players who haven't completed their configured placement-game count are treated as unranked and matched in a separate provisional pool until placement completes.
Leaderboards — Score Submission & Validation
Leaderboard types: global, weekly, tournament, speed-run, or custom. Scoring direction: ascending or descending (descending — higher is better — is standard). Reset period: none, daily, weekly, or monthly.
Submitting a score:
json
{
"leaderboardId": "string",
"userId": "string",
"score": 0,
"displayName": "string",
"avatarUrl": "string",
"metadata": {},
"submittedAt": "2026-06-01T00:00:00Z"
}
Response:
json
{
"entry": { "...": "ranked entry" },
"isNewRecord": true,
"previousScore": null,
"rankChange": 0
}
Anti-cheat integration: before ranking, every submitted score is validated against the anti-cheat engine — checked against the player's personal best and the leaderboard's global mean. If the check fails, the entry is flagged with a reason and marked unvalidated rather than silently rejected.
Score submission rules:
onlyBetterScores: Iftrue, a submitted score only replaces the previous score if it's better in the scoring directionallowUpdates: Iffalse, scores are immutable once submitted (no resubmission)maxEntries: Maximum number of entries stored per player per leaderboard
Reset handling: resetPeriod determines when entries are cleared. lastResetAt tracks the last reset timestamp. Weekly resets clear all entries and start fresh — useful for competitive seasons.
Currency — Double-Entry Ledger
Every currency operation writes two linked transaction rows — one debit, one credit — that always net to zero:
json
{
"userId": "string",
"currency": "soft | hard | premium",
"delta": 0,
"reason": "match_reward | achievement_unlock | battle_pass_tier | challenge_complete | purchase | refund | trade | admin_grant | admin_revoke | expiration | system",
"sourceId": "matchId, achievementId, etc.",
"balanceAfter": 0,
"pairedTransactionId": "string",
"createdAt": "2026-06-01T00:00:00Z"
}
delta is signed (negative for the debit side, positive for the credit side); pairedTransactionId links the two rows of the same transfer back to each other.
Atomic operations: debiting an account fails outright if the balance is insufficient; crediting an account always succeeds; transferring between two accounts debits one and credits the other in a single all-or-nothing operation. Every operation runs inside a database transaction, so a balance can never go negative from a partial write.
Balance tracking: the current balance per account and currency is tracked directly, but transactions are append-only — balance is always re-derivable as SUM(credits) - SUM(debits) from the transaction log, not just a mutable field.
Loot — Drop Rate Resolution
A loot table entry:
json
{
"itemId": "string",
"dropRate": 0.0,
"minQuantity": 1,
"maxQuantity": 1,
"weight": 1,
"conditions": [
{
"field": "player.level",
"operator": "gte | lte | equals | in",
"value": 0
}
]
}
Rolls can be seeded (deterministic — same seed always produces the same result, useful for fairness audits) or purely probabilistic.
Drop rate resolution: load the table's entries, filter by any player-state conditions (level, battle-pass tier, etc.), roll each eligible entry against its drop rate, and distribute the resulting items or currency to the player — with every roll logged for audit.
Use cases: Loot chests, achievement rewards, battle pass tier rewards, challenge completion bonuses, random daily login rewards, gacha pulls.
Anti-Cheat — Rule Engine
Rule types: rate-limit, impossibility, account-age, honeypot, custom. Applies to: all, score, achievement, purchase, currency, or loot. Severity: low, medium, high, critical.
Validation result: every check returns whether the caller should proceed, the action taken (allowed / flagged / blocked), a reason and severity if triggered, and which rules matched.
A rule definition carries its type, what it applies to, the action to take (flag or block), severity, rule-specific config, priority, an active flag, and whether critical hits should auto-ban.
Rule configurations by type:
```typescript // rate_limit { windowSeconds: 60, maxActions: 5, scope: "userId" | "ip" | "leaderboardId" }
// impossibility { theoreticalMax: 100000, stdDeviations: 5, checkAgainstGlobalMean: true }
// account_age { minimumDays: 30 }
// custom — JS expression evaluated in sandbox { expression: "input.value < player.lifetimeValue * 0.1" } ```
Severity → action mapping:
typescript
low/medium/high: action: "flag" → status: "pending" → reviewed manually
→ resolved as dismissed / acknowledged / escalated
critical: action: "block" → autoBanOnCritical: true → 24-hour auto-ban
Every flag starts pending regardless of severity — there's no automatic timer or hit-count that escalates it on its own. A human reviewer resolves each one to dismissed, acknowledged, or escalated.
Evidence collection: Every validation logs: player history, account age, recent sessions, peer performance comparison. Full context for human review.
Function Runtime — Sandboxed Execution
Trigger types: adhoc, http, cron, event. Execution status: running, success, failure, timeout, memory-exceeded, rejected.
Every execution record captures its status, duration, peak memory, output or captured error, the log lines produced, how many Aeion API calls it made, and a reject reason if it was blocked before running.
Execution flow:
- Load active version of the function (or specific
versionIdfor ad-hoc test) - Transpile TypeScript if
runtime: "ts" - Run static analysis — check for blocking issues
- Spin up a Node
vmsandbox with: - Execute the compiled function inside the sandbox
- Capture output, duration, memory peak
- Store execution record with logs
`aeion` API available in sandbox:
typescript
aeion.db.find / findById(); // Database access — read-only
aeion.currencies.add / deduct / balance(); // Currency operations
aeion.inventory.addItem / consumeItem(); // Item management
aeion.log.info / warn / error(message, meta); // Structured logging
aeion.events.emit(name, payload); // Emit game event
`console.log` → live clients: The function trigger router captures sandbox logs and broadcasts them via WebSocket. Clients subscribe to live function output — streaming AI responses, real-time state updates, debugging.
Push Notifications — Multi-Platform Campaigns
Platforms: iOS, Android, web, desktop. Delivery status: queued, sent, delivered, opened, dismissed, failed, suppressed. Campaign status: draft, scheduled, processing, completed, cancelled, failed.
Each device token record tracks the player, device, platform, the underlying APNs/FCM token, whether it's active, last-active time, consecutive-failure count, and per-player notification preferences (marketing opt-in, system opt-in, quiet hours).
Campaign configuration:
json
{
"title": "string",
"body": "string",
"imageUrl": "string | null",
"deepLink": "string | null",
"variables": { "playerName": "resolved per recipient" },
"targetSource": "all devices | segment | specific users",
"mirrorToInbox": true,
"attachedRewards": [{ "type": "currency | item | xp" }],
"priority": "low | normal | high | critical",
"respectQuietHours": true,
"expiresAfterHours": null
}
Attached rewards: when a user opens a push notification, the attached rewards (currency, items, XP) are delivered transactionally. If reward delivery fails, the message is marked failed and stays retryable.
Mirror to inbox: mirrorToInbox: true creates a corresponding in-game inbox message alongside every push delivery. Useful for players who have push disabled — they see the message in their in-game inbox instead.
Battle Pass — Tiered Progression System
Each tier defines the cumulative XP required to reach it, plus its free-track and premium-track rewards (currency, item, or bonus XP).
Player progress tracks total XP, current tier, whether the premium track is unlocked, which free/premium tiers have been claimed, and a full XP history (amount, source, and originating event for every grant).
XP sources are configured per battle pass: XP per challenge completed, XP per achievement unlocked, XP per leaderboard score submitted.
Tier advancement: granting XP appends to the player's history, recalculates their current tier against the cumulative thresholds, auto-claims rewards where configured, triggers the underlying currency/item grants, and emits a tier-progress event.
Tournaments — Bracket & Prize Management
Tournament types:
- Bracket — Single elimination, double elimination, triple elimination
- Round Robin — Every team plays every other team
- Swiss — Used in chess/esports for large fields without full round robin
Match generation: loads the registered participants and bracket structure, generates every match for each round or heat, and orders them appropriately for the format (bracket position for elimination brackets, round-robin table for round robin).
Prize distribution: loads the tournament's configured prize structure, determines final rankings, and for each prize tier identifies qualifying participants, grants the associated currency/items, and records the prize in each participant's history.
LiveOps events:
- Daily login bonuses
- Weekly challenges
- Seasonal content rotations
- Time-limited store offerings
- Participation leaderboards for events
Key Architecture Decisions
Rating system
Glicko-2 (MMR + RD) — Tracks skill uncertainty, not just wins/losses
Matchmaking
MMR + RD widening — Ensures fair games even for inactive players
Currency
Double-entry ledger — Mathematically impossible to dupe — always balanced
Inventory
Source tracking — Know where every item came from (purchase/granted/trade/found)
Anti-cheat
Rule engine — Configurable per project, not a static blacklist
Function sandbox
Node `vm` — Isolated execution, memory/time limits enforced (QuickJS WASM roadmapped)
Function logs
WebSocket broadcast — Live function output to game clients
Loot
Seeded + probabilistic — Seeded for reproducible results (gacha fairness)
Push
APNs + FCM + Web — All three platforms from one API
Inbox rewards
Transactional — Reward failure → retry, never lost
Battle pass XP
Source-tracked history — Full audit trail of every XP grant