veratown-prototype

Truth or Dare

Truth or Dare is a room-scoped, opt-in, chat-only auction over shared economy credits. The runtime validates configuration and approved prompt records, pins their revisions in each session, reserves each participant’s current bid, and settles only persisted outcomes.

Release gate and defaults

The feature is wired into the static plug-in host. TRUTH_DARE_GAMEPLAY_ENABLED is true following the operator’s 2026-10-15 report that live bidding, settlement, decline, timeout, and restart-recovery qualification succeeded and their explicit activation approval. This is an operator-reported qualification, not a live run performed by the provisioning tool. Room configuration remains disabled by default, and an auction cannot open without at least two approved prompts in the configured locale.

Startup inserts the configuration below and ten approved English starter prompts only when absent. Existing rules, enabled state, timestamps, and prompt revisions are preserved. The starter catalog contains five playful fictional truths and five non-explicit, chat-only dares; none requests physical actions or private information.

The implementation adopts these bounded decisions:

Credits have no real-money value. Prompts must be approved, bounded text that is safe to complete in room chat. They must not ask for identifying or intimate information, physical or external actions, images or voice, or disclosure about non-consenting people. Moderation status is an operator assertion, not an automated semantic safety classifier.

Commands

All feedback is privately whispered; other players’ bids and wallet balances are never announced.

Command Behavior
/bot dare rules Privately show configured rules and bounds.
/bot dare join Show the rules and the explicit opt-in command.
/bot dare join accept Join the pre-auction roster while present in the observed room.
/bot dare leave Withdraw before an auction is opened.
/bot dare open Room-admin only; freeze the roster and open bidding.
/bot dare bid <credits> Place, raise, or lower a bid using one base-10 integer.
/bot dare cancel Cancel a bid before the configured bid-change cutoff.
/bot dare complete Explicitly attest the current winner or lowest-bid challenge.
/bot dare decline Decline the current challenge without claiming completion.

Global and scoped help, including /bot dare rules and admin-only commands, is shown only while both the code gate and room configuration enable the game. The commands remain registered while disabled.

Bid reductions and cancellation close bidChangeCloseMsBeforeDeadline before the bidding deadline; raises remain available until the deadline. Accepted bids must be within the configured bounds and follow bidIncrementCredits. Each participant has one active bid; adjustments atomically reserve or release only the difference. If a reservation fails, the prior bid remains unchanged.

At close, fewer than two valid bidders voids the round and refunds all reservations. Non-winner reservations are released before the winner challenge. A non-bidder is never selected as the low bidder or charged.

Configuration and approved prompts

Configuration is stored in the selected runtime database’s prototypePartyGameConfigs collection. There is one document per (roomKey, gameId) with _id: "<roomKey>:truth-dare", gameId: "truth-dare", a positive revision, timestamps, and a config matching this shape:

{
    "enabled": false,
    "locale": "en",
    "minPlayers": 2,
    "maxPlayers": 8,
    "bidWindowMs": 45000,
    "challengeWindowMs": 60000,
    "bidChangeCloseMsBeforeDeadline": 5000,
    "minBidCredits": 1,
    "maxBidCredits": 100,
    "bidIncrementCredits": 1,
    "rewardCredits": 5,
    "lowBidPolicy": "challenge_only",
    "declinePolicy": "release",
    "maxRoundsPerWindow": 2
}

The implementation caps the roster at 20, bid amounts at 1,000 credits, rewards at 1,000 credits, each timed window at five minutes, and rounds at 10 per rolling 24-hour period. Invalid or absent settings fail closed. This config is owned by this feature and is not managed through prototypeGameConfig or the world-admin commands.

prototypeTruthDarePrompts stores immutable revisions. _id is "<promptId>:r<revision>"; each record includes kind (truth or dare), category, locale, text (10–500 characters), challenge level (1–5), moderationStatus (pending, approved, or rejected), revision, and timestamps. Operators should create a new record for each content revision rather than editing an in-use revision. The runtime selects two distinct prompt IDs in the configured locale and rechecks approval when recovering a challenge.

Use the party-game provisioning workflow to inspect, populate, and explicitly enable an approved room. New database defaults stay disabled; production activation is separate from test qualification.

Persistence, privacy, and recovery

prototypeTruthDareSessions stores the frozen member-number roster, pinned configuration and prompt revisions, bounded bids, acceptance sequence, phase, deadlines, attestation/decline state, and stable settlement identifiers. It stores no challenge answer, chat transcript, or response text. Active sessions hold a unique room key and have no TTL; terminal sessions expire after 30 days. Concurrent bids and phase changes use revision-checked updates. A persisted pending bid is replayed with the same economy operation ID after restart.

prototypeEconomyAccounts and prototypeEconomyLedger remain the only balance authority. Truth/Dare uses the shared port’s atomic reservation adjustment for bid changes; terminal operations use stable IDs for refunds, consumption, and the one-time completion reward. The economic ledger remains separate from game session state and BC action journaling. If an economic operation cannot be reconciled, the session remains active for recovery/operator review; it is not reported as settled.

The plug-in cancels its deadline timers and drains pending work on stop, leaving unresolved sessions and reservations recoverable. Deadline callbacks do not claim challenge completion. BC chat and whisper actions retain their existing dispatch-unconfirmed semantics.

Runtime diagrams

flowchart LR
    Root["start.ts"] --> Plugin["TruthDarePlugin"]
    Plugin --> Commands["TruthDareCommandSuite"]
    Plugin --> Service["TruthDareService"]
    Commands --> Service
    Service --> Repo["MongoTruthDareRepository"]
    Service --> Economy["EconomyPort"]
    Service --> Communication["Injected private-whisper capability"]
    Repo --> Config[("prototypePartyGameConfigs")]
    Repo --> Prompts[("prototypeTruthDarePrompts")]
    Repo --> Sessions[("prototypeTruthDareSessions")]
stateDiagram-v2
    [*] --> Bidding: gameplay gate + enabled room config
    Bidding --> Bidding: reserve or adjust bid
    Bidding --> Voided: fewer than two bidders; refund reservations
    Bidding --> WinnerChallenge: close and persist high/low bidders
    WinnerChallenge --> Settling: attested / declined / left / timed out
    Settling --> LowChallenge: winner reservation resolved
    LowChallenge --> Settling: low challenge attested / declined / left / timed out
    Settling --> Settling: recovery retries stable operation IDs
    Settling --> Complete: reservations and rewards reconciled
    Voided --> Complete
    Complete --> [*]

The winning stake and completion reward are applied only after explicit self-attestation. Recovery uses the persisted outcome and reservation IDs; it does not rerun the auction or choose new recipients.

Implementation and references