veratown-prototype

Truth or Dare — Feature Proposal

Status: Implemented and activation-approved following operator-reported live qualification on 2026-10-15. TRUTH_DARE_GAMEPLAY_ENABLED is true; new room configurations remain disabled by default. The runtime and operator-owned data contracts are documented in docs/features/truth-dare/README.md.

Archived: 2026-10-16 (UTC). See the live feature guide for current runtime behavior and release status.

Purpose and scope

Propose an independent, room-scoped, chat-only challenge auction. The bot selects an approved Truth/Dare record from MongoDB and opens a timed bidding window. Opted-in players reserve shared economy credits with `/bot dare bid

`. The highest eligible bidder receives the challenge and a configured reward after attesting completion. The lowest bidder receives a separate, approved in-game challenge. The runtime cannot force a person to perform a task. "Must execute" can only mean an explicit opt-in to the bounded in-game rules, with a refusal path and transparent virtual-credit consequences. No physical, sexual, external, image/voice, or other real-world action is required or inferred. ## Product rules - Require an enabled per-room config and a frozen roster of players who opted into the auction and its published rules. Never enroll a nonparticipant. - The selected content must be text-only and completable in room chat. Prompts must not demand identifying information, intimate images, physical actions, contact with another person, or disclosures about non-consenting people. - Each bidder has at most one active bid. The command accepts one positive base-10 integer in the configured range; reject signs, decimals, separators, exponent notation, unsafe integers, extra arguments, and unknown users. - A bid is reserved atomically against the shared economy. Raising it reserves only the difference; lowering/canceling follows the configured window and refunds the difference once. A failed reservation leaves the prior bid unchanged. - At close, require at least two valid bidders. Highest bid wins; ties use server-assigned acceptance sequence then member number for deterministic replay. Lowest bid is selected from the remaining valid final bids, using ascending amount and the same tie-breakers. A joined player who did not bid is not the lowest bidder and is not penalized. - Release non-winning reservations. Consume the winning bid only under the approved outcome policy; grant the configured reward once only after the winner attests completion. The lowest bidder receives a separately selected optional challenge with no credit penalty or reward. Do not debit or reward players merely because a timer fired. - If the winner refuses, leaves, or times out, use an explicitly configured refusal policy. The recommended default is no reward and release the winning reservation; any forfeiture must be shown before players opt in and bounded by the session's approved rules. - If any challenge is declined, mark it declined; do not claim it was executed. The bot never infers completion from a missing message or an unverified public statement. ## Room configuration and prompt catalog One config document per `(roomKey, gameId)` in `prototypePartyGameConfigs`; this game uses `:truth-dare`. Missing/invalid config is disabled. The session pins the config revision and prompt revision. | Setting | Proposed validation / intent | | ------------------------------------------------------- | --------------------------------------------------------------------------------- | | `enabled` | Explicit room-level enable; false when absent. | | `locale` | Selects approved prompt revisions in the configured locale. | | `minPlayers`, `maxPlayers` | Bounded roster and at least two valid bidders to resolve high/low. | | `bidWindowMs`, `challengeWindowMs` | Positive bounded durations stored as absolute deadlines. | | `bidChangeCloseMsBeforeDeadline` | Bounded cutoff before bidding closes; reductions and cancellation stop here. | | `minBidCredits`, `maxBidCredits`, `bidIncrementCredits` | Positive safe integers; `maxBidCredits` cannot exceed the implementation ceiling. | | `rewardCredits` | Bounded reward for attested completion; total possible award is capped. | | `lowBidPolicy`, `declinePolicy` | Currently `challenge_only` and `release`; no hidden point penalty or forfeiture. | | `maxRoundsPerWindow` | Bounded number of sessions per room per rolling 24 hours. | The `prototypeTruthDarePrompts` catalog stores prompt ID, `kind` (`truth` or `dare`), category, locale, bounded text, challenge level, moderation status, content revision, and timestamps. Runtime code owns the mechanics and validation; MongoDB owns adjustable settings and approved content, not executable rules. ## Persistence, bidding, and recovery `prototypeTruthDareSessions` stores a bounded participant roster, prompt IDs and revisions, bid entries with member number/amount/acceptance sequence, reservation IDs, current phase/revision, deadlines, winner/lowest bidder, attestation/decline state, outcome, and settlement operation IDs. It stores no challenge response or transcript. Active sessions are never TTL-deleted; terminal sessions receive an approved expiry. Only one active auction per room is allowed. A unique active key blocks overlapping sessions; conditional revision transitions serialize concurrent bids, raises, timer callbacks, and close operations. Reservations and settlements use idempotent economy operation IDs. Startup recovery loads persisted deadlines and reservations; it never reruns the auction or changes the winner. Stop cancels timers, drains operations, and leaves unresolved reservations recoverable. If recovery cannot determine an economic result, fail closed and retain the session for operator review. Player departure does not silently free a reservation. A departing winner is handled using the published timeout/decline policy; other bidders' reservations are released once the auction is resolved. New arrivals do not join an active auction. All player-facing acceptance, reward, and penalty notices use the existing communication API and preserve unconfirmed-dispatch semantics. ## Consent, privacy, and authorization - Each room config is disabled by default. The source gameplay gate is enabled following operator-reported qualification; require an explicit per-session join before a player's balance can be reserved. - Privately disclose the high/low rules, bid bounds, refusal path, and reward before accepting opt-in. `/bot dare leave` withdraws from the pre-auction roster; once bidding is open, accepted reservations resolve through the declared policy. - Keep tasks bounded to consensual chat participation. No real-world "punishment," coercion, threats, or forced disclosure. - Parse bids strictly and resolve targets only from the current observed room roster. Replies to the bidder are private; never echo wallet balances or other players' bids. - Use only the shared economy port, shared command parser, and injected BC communication. No feature-to-feature imports, direct socket access, or second BC connection. - Never log/store full chat submissions or responses. Persist only the approved prompt ID and bounded game metadata. ## UML ### Component diagram ```mermaid flowchart LR Root["start.ts"] --> Plugin["TruthDarePlugin"] Plugin --> Commands["TruthDareCommandSuite"] Plugin --> Service["TruthDareService"] Commands --> Parser["BotCommandParser"] Commands --> Service Service --> Rules["TruthDareRules"] Service --> Repo["TruthDareRepository"] Service --> Economy["Injected EconomyPort"] Service --> Comms["Injected CharacterCommunicationApi"] Repo --> Config[("Room-scoped game config")] Repo --> Prompts[("prototypeTruthDarePrompts")] Repo --> Sessions[("prototypeTruthDareSessions")] Comms --> BC["Existing BcClient actions"] ``` ### Class diagram ```mermaid classDiagram class TruthDarePlugin { +start() +stop() } class TruthDareCommandSuite { +register() -join() -bid(amount) -complete() -decline() } class TruthDareService { +openAuction(roomKey) +placeBid(sessionId, member, amount) +closeAuction(sessionId) +attest(sessionId, member) +decline(sessionId, member) +recover(roomKey) +stop() } class TruthDareRules { +validateBid(text, config) +rankBids(bids) +validatePrompt(prompt) } class TruthDareRepository { <> +createActiveSession() +recordBid(expectedRevision) +closeBidding(expectedRevision) +persistOutcome(expectedRevision) +listRecoverable(roomKey) } class EconomyPort { <> +reserve(operationId, member, amount, session) +settle(operationId, reservationId, result) +refund(operationId, reservationId) +grantOnce(operationId, member, amount, reason) } TruthDarePlugin --> TruthDareCommandSuite TruthDarePlugin --> TruthDareService TruthDareCommandSuite --> TruthDareService TruthDareService --> TruthDareRules TruthDareService --> TruthDareRepository TruthDareService --> EconomyPort ``` ### Use-case diagram ```mermaid flowchart LR Admin((Room admin)) --> Configure([Configure bounds and policies]) Player((Opted-in player)) --> Join([Join and accept published rules]) Player --> Bid([Place or raise a bounded bid]) Winner((Highest bidder)) --> Respond([Attest completion or decline]) Lowest((Lowest bidder)) --> Secondary([Accept or decline secondary challenge]) Timer((Deadline worker)) --> Close([Close and rank auction]) Configure --> Config[(Room config and approved prompts)] Bid --> Session[(Durable auction)] Close --> Settle([Release / consume reservations idempotently]) Settle --> Wallet[(Shared economy)] ``` ### Activity diagram: auction ```mermaid flowchart TD Start([Open requested]) --> Gate{Enabled and enough opted-in players?} Gate -->|No| Deny[No session; explain privately] Gate -->|Yes| Load[Load approved challenge and pin revisions] Load --> Create[Persist active auction and deadline] Create --> Publish[Publish prompt, bounds, reward, refusal policy] Publish --> Bid[Validate bid and atomically reserve credits] Bid --> Window{Bid window closed?} Window -->|No| Bid Window -->|Yes| Count{At least two valid bidders?} Count -->|No| Refund[Void and refund reservations] Count -->|Yes| Rank[Persist deterministic high/low winners] Rank --> Challenge[Release non-winner reservations; prompt winner] Challenge --> Attested{Winner attests before deadline?} Attested -->|Yes| Reward[Consume accepted stake and grant bounded reward once] Attested -->|No| Decline[Apply published decline policy; no success claim] Challenge --> Lowest[Offer secondary prompt to lowest bidder] Lowest --> Finish([Persist terminal state; notify with honest outcome]) Reward --> Lowest Decline --> Lowest Refund --> Finish ``` ### State machine ```mermaid stateDiagram-v2 [*] --> Consent Consent --> Bidding: roster joined and rules accepted Bidding --> Bidding: valid bid / raise / configured cancel Bidding --> Refunding: fewer than two valid bidders at deadline Bidding --> ChallengePending: close; persist deterministic high/low ChallengePending --> WinnerComplete: winner attests before deadline ChallengePending --> WinnerDeclined: decline / leave / timeout policy WinnerComplete --> Settling: persist outcome WinnerDeclined --> Settling: persist published decline outcome Settling --> LowChallenge: reservations reconciled; offer low challenge LowChallenge --> Complete: accepted, declined, or timed out Refunding --> Complete: every reservation refunded once Settling --> Settling: restart; repeat same idempotency keys Complete --> [*] ``` ### Sequence / communication diagram ```mermaid sequenceDiagram participant Player participant Cmd as TruthDareCommandSuite participant Service as TruthDareService participant DB as TruthDareRepository participant Economy as EconomyPort participant BC as CharacterCommunicationApi Service->>DB: load config/prompt and create auction Service->>BC: publish rules, bounds, prompt, deadline Player->>Cmd: bid Cmd->>Service: placeBid(member, validated integer) Service->>Economy: reserve or adjust stable bid reservation Economy-->>Service: reserved / insufficient / conflict Service->>DB: CAS bid and reservation metadata Service->>DB: close and persist deterministic outcome Service->>Economy: refund nonwinners; consume/settle winner and low bid Player->>Cmd: complete or decline Cmd->>Service: attest / decline (authorized winner only) Service->>DB: CAS challenge outcome Service->>Economy: grantOnce(reward) only after attestation Service->>BC: private results; room notice without balances ``` ## Decisions and acceptance criteria The implementation adopts the following decisions: - Consume the winning reservation and issue the one-time configured reward only after the winner explicitly attests completion. This is self-attestation, not independent verification. - On decline, departure, or timeout, release the winning reservation and issue no reward. No forfeiture is implemented. - Offer the lowest bidder a separate optional chat-only challenge with no credit reward or penalty. The winner is excluded from lowest-bid selection so the high and low roles remain distinct even when bids tie. - Break ties by server-assigned accepted-bid sequence, then member number. - Require `/bot dare join accept` after private disclosure; a room admin opens bidding and freezes the observed, opted-in roster. Late arrivals cannot join. - Allow bid reductions/cancellation until the configured cutoff before the bid deadline. Reservation changes atomically adjust the shared economy balance. Challenges are text-only, chat-completable, and opt-in. The room config is stored in `prototypePartyGameConfigs`, approved immutable prompt revisions in `prototypeTruthDarePrompts`, and recoverable session state in `prototypeTruthDareSessions`. The source-defined gameplay gate is true; each room's seeded config remains disabled until explicitly enabled. Offline coverage includes strict bid parsing, deterministic ties, one-bidder refund, bid adjustment and insufficient-funds behavior, explicit attestation, disabled room-config behavior, and replay of a persisted pending bid. The operator reported live bidding, settlement, decline, timeout, and restart recovery qualification and approved activation on 2026-10-15. That report is not independently observed here; prompt moderation and per-room enablement remain operator responsibilities. ## References - [Shared economy proposal](/proposals/archive/SHARED_ECONOMY_PROPOSAL.html) - [Bot runtime architecture](/architecture/BOT-ARCHITECTURE.html) - [Commands and plug-ins](/architecture/FEATURE_COMMANDS_AND_PLUGINS.html) - [Casino games and rewards implementation plan](/features/casino/IMPLEMENTATION_PLAN.html)