The economy is a shared application capability for casino and approved party games. Its canonical indivisible unit is credits. Casino commands may temporarily display the compatibility label chips; both labels refer to the same balance. Credits have no real-money value.
src/core/economy/economy-port.ts defines the transport- and database-neutral
EconomyPort. EconomyService enforces approved source/reason combinations;
MongoEconomyRepository owns the shared account and ledger collections. The
composition root constructs one service and injects it into casino and
independently gated party-game consumers. Features do not import one another or
write account records.
An account is keyed by BC memberNumber and is global across rooms using the
same selected runtime database. Production and test remain separate, and the
source database is never an economy target.
| Operation | Behavior |
|---|---|
| Balance | Returns available and reserved credits; creates only a zero account when absent. |
| Grant once | Applies an approved bounded positive award once per stable operation ID. Casino entry grants also enforce an atomic rolling cooldown. |
| Reserve | Atomically moves available credits to reserved credits; insufficient balances do not write a reservation. |
| Adjust reservation | Truth/Dare-only atomic bid adjustment reserves or releases only the difference; lowering to zero refunds and closes the reservation. |
| Settle | Consumes the reservation and applies the specified total return exactly once. |
| Refund | Returns the full reservation exactly once. |
| Forfeit available | Hot Seat-only operation that clears available credits without changing any reservation. |
The shop uses its own shop source: purchases reserve and settle/refund credits
with stable order IDs, while completed favors receive a stable favor_payout.
Stopping or voiding a favor forfeits only its unpaid reward; it never invokes
the Hot Seat-only available-balance forfeiture.
Amounts and balances are safe integers. Source games and reason codes are
allowlisted in EconomyService; there are no player-to-player transfers,
operator grants, or real-money operations. The shop is the only purchase
consumer and uses recipient consent plus reserved-credit settlement; see the
shop guide. Operation IDs are bounded stable identifiers
derived from persisted session/event IDs, never player text.
Account and ledger mutations use Mongo transactions. A reservation and its
adjustment history have no expiry until they settle or refund; finalized records
expire after 60 days. MongoDB TTL cleanup is asynchronous.
The economy provides approved operations to consumers; it does not by itself enable gameplay. Casino slots and roulette were separately operator-qualified and approved for activation on 2026-10-15. The operator also reported Truth or Dare live qualification and activation approval on that date; its source gate is true, while each room configuration remains disabled by default. Anonymous Whisperer remains disabled until its adults-only room configuration and separate qualification are approved. Dilemma requires enabled room configuration, adults- only room policy, reviewed prompts, and per-round consent; Hot Seat requires its room-scoped configuration to be enabled. Hot Seat bluff rewards may include a code-validated available-balance ceiling, applied atomically with the idempotent grant. Each feature’s consent and release gates remain independent. See the Anonymous Whisperer guide, Truth or Dare guide, Dilemma guide, and Hot Seat guide.
| Collection | Owner | Retention |
|---|---|---|
prototypeEconomyAccounts |
Economy repository | No TTL; one account per member in the selected runtime database. |
prototypeEconomyLedger |
Economy repository | 60 days after finalization; unsettled reservations, adjustment history, and the cutover marker remain durable. |
prototypeCasinoWallets |
Legacy casino compatibility / rollback | No TTL; retained through the rollback window. |
prototypeCasinoLedger |
Legacy casino compatibility / rollback | 60 days after grant or settlement; unsettled reservations remain durable. |
flowchart LR
Root["start.ts composition root"] --> Service["EconomyService"]
Service -. "implements" .-> Port["EconomyPort"]
Casino["Casino"] --> Port
PartyGames["Party-game services"] --> Port
Service --> Repository["MongoEconomyRepository"]
Repository --> Accounts[("prototypeEconomyAccounts")]
Repository --> Ledger[("prototypeEconomyLedger")]
sequenceDiagram
participant Game as Game service
participant Economy as EconomyService
participant Repo as MongoEconomyRepository
participant Mongo as MongoDB transaction
Game->>Economy: reserve / settle (stable operation ID)
Economy->>Repo: validate and apply operation
Repo->>Mongo: check idempotency key and update account + ledger
alt New valid operation
Mongo-->>Repo: commit
Repo-->>Economy: durable result
else Same operation already recorded
Mongo-->>Repo: prior result
Repo-->>Economy: replay without a second mutation
else Conflict or insufficient balance
Mongo-->>Repo: no mutation
Repo-->>Economy: typed rejection
end
Economy-->>Game: result
The economy repository refuses startup until the migration marker exists. This prevents a zero-balance economy account or an entry grant from being created before legacy balances are copied.
Run the migration against the intended RUNTIME_TARGET; it reads and writes
only that target’s fixed database. It never opens the legacy source database or
changes the configuration-copy allowlist. Stop the runtime during cutover.
npm run migrate:casino-economy
npm run migrate:casino-economy -- --apply
The default is a dry run. It validates the complete legacy wallet and ledger
shapes, checks destination conflicts, and prints only counts and aggregate
available/reserved credit totals. Apply uses stable per-wallet and ledger IDs,
does not delete the legacy collections, and is safe to resume after an
interrupted run. Legacy ledger expiry dates are extended to preserve the
approved 60-day retention. The final durable cutover marker is written only
after wallet, ledger, and aggregate economic source data remain unchanged and
all imports verify. The script changes only legacy ledger expiresAt values to
preserve rollback retention.
For an approved rollback, stop the runtime and inspect the inverse dry run:
npm run migrate:casino-economy -- --rollback
npm run migrate:casino-economy -- --rollback --apply
Rollback restores shared account balances and entry-grant timestamps to the legacy casino wallet collection. It is limited to 60 days after cutover and fails closed if any post-cutover economy activity occurred outside casino entry grants; those operations cannot be safely represented by the legacy casino wallet. A rollback-compatible deployment must keep wagering and new games disabled. Since slots and roulette are now activated, stop gameplay before considering rollback; any activity beyond entry grants makes this legacy rollback unavailable. Resume the prior runtime only after verifying the aggregate totals.