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)