Multi-Account Detection¶
Status: 📐 Design-only — Entirely unbuilt — doc self-declares 📐 Design-only; no service, model, job, consumer wiring, or admin UI exists anywhere in the codebase. (impl audit 2026-06-16)
📐 Design-only. Per ADR-0056. Shared infrastructure that surfaces clusters of accounts likely operated by the same human, then downgrades or blocks their participation in surfaces where alt-rings cause measurable harm: governance voting, station-takeover volume metrics, message-beacon visibility, and faction-rep farming.
Principle: payment is the legitimacy gate¶
The multi-account exploit gets its leverage from being cheap. One human running 10 free-tier alts is a meaningfully different actor from one human paying 10× $5/mo for 10 Galactic Citizen subscriptions. The first is gaming the system; the second is a paying customer with multiple personas. The detection layer is subscription-tier aware:
- All clustered accounts have active paid subscriptions (Galactic Citizen or Region Owner): no block, no discount. Soft signals still surface for monitoring but do not penalize.
- At least one free-tier account in the cluster: hard signals block, soft signals discount the free account's participation weight.
This means a household of two paying players sharing a payment method gets full participation; a single human running five free alts on the same fingerprint does not.
Cluster signals¶
| Signal | Class | Meaning |
|---|---|---|
| Same payment method on file | hard | Same credit card, PayPal billing agreement, or other PSP-side identifier across multiple accounts. PSP returns a stable hash; we never store raw card numbers. |
| Same active session token across different player IDs | hard | A single browser session authenticated as two players within the same hour. Should never happen legitimately — session-share or account-sale signal. |
| Same IP address within 24h | soft | Common for shared households, coffee shops, dorm rooms. Cluster signal but not a confidence signal alone. |
| Same device fingerprint hash | soft | Canvas + WebGL + timezone + audio-ctx + UA-CH composite hash. Stable across sessions on the same device. |
| Perfectly-correlated trade timings | soft | Two accounts trading reciprocally within a tight time window, repeated. Behavioural — runs as a periodic batch. |
| Perfectly-correlated voting patterns | soft | Two accounts voting identically across multiple unrelated proposals. Behavioural — runs at election close. |
Hard signals trigger immediately on the gated action (team formation, payment update). Soft signals run as a periodic batch every hour; the runtime worker is a Batch-6 service with no need for real-time evaluation — alt-rings don't form in seconds.
Discount math¶
For every gated participation surface, the per-account multiplier is:
def participation_weight(player_id, surface):
flag = MultiAccountFlag.most_severe_for(player_id)
if flag is None:
return 1.0 # no cluster, full weight
if all_paid_in_cluster(flag.cluster_id):
return 1.0 # paid-tier exemption
if flag.severity == "hard":
return 0.0
if flag.severity == "soft":
return 0.5
Surfaces that consume the weight:
- Regional governance vote:
RegionalVote.weight = membership.voting_power × participation_weight(player_id, 'governance'). Per../FEATURES/gameplay/regional-governance.md. - Station-takeover volume metric: free-tier accounts in a flagged cluster contribute to volume at the discounted weight. Hard-flagged accounts contribute 0 — the syndicate-volume exploit collapses.
- Message-beacon visibility: free-tier accounts in a flagged cluster have beacon weight 0× (their beacons don't count toward the per-sector cap and aren't surfaced). Per
../FEATURES/gameplay/message-beacons.md. - Faction-rep gain: free-tier accounts in a flagged cluster have faction-rep deltas multiplied by the participation weight. Hard flag → no rep gain at all on cluster-coordinated actions.
Service contract¶
# services/gameserver/src/services/multi_account_detection_service.py (target path)
def upsert_cluster(player_ids: set[UUID], signal: ClusterSignal) -> MultiAccountCluster
def flag_account(player_id: UUID, cluster_id: UUID, severity: str, signal: str) -> MultiAccountFlag
def participation_weight(player_id: UUID, surface: str) -> float
def review_queue() -> list[MultiAccountCluster] # admin-UI fetch
def admin_override(cluster_id: UUID, decision: str, reason: str) -> None
Schema¶
MultiAccountCluster and MultiAccountFlag live in ../DATA_MODELS/gameplay.md.
Admin review surface¶
The admin UI (per ./admin-ui.md) gets a Multi-Account Review page that lists every active cluster with:
- Cluster signals (which heuristics fired) and their severity classes.
- Each account's subscription tier, age, recent activity summary.
- A decision panel:
Confirm(cluster is real, apply discounts),Override(legitimate household / shared connection — clear flags),Escalate(deeper review). - Audit log of admin decisions for compliance.
Decisions update MultiAccountCluster.admin_decision and propagate to all member MultiAccountFlag rows. An admin override clears flags for that cluster permanently — re-detection requires a new signal.
Pre-existing alt rings¶
The detection job runs against historical session data on first deploy, generating an initial backlog of clusters. Admin reviews and confirms / overrides each one before any discounts apply. After the backlog is cleared, new clusters surface continuously.
Source map¶
| Concern | Target file |
|---|---|
MultiAccountDetectionService |
services/gameserver/src/services/multi_account_detection_service.py (target) |
| Cluster + flag models | services/gameserver/src/models/multi_account.py (target) |
| Periodic detection job | services/gameserver/src/jobs/multi_account_sweep.py (target) per ADR-0053 |
| Admin review UI | services/admin-ui/src/pages/MultiAccountReview/ (target) |
Related¶
- ADR-0056 — origin ADR for this service.
./monetization.md— subscription-tier definitions../admin-ui.md— admin app structure.../FEATURES/gameplay/regional-governance.md— voting eligibility consumer.../FEATURES/gameplay/message-beacons.md— beacon visibility consumer.../FEATURES/gameplay/factions-and-teams.md— faction-rep consumer.