Status: Implemented as a disabled-by-default runtime feature. See the Dilemma feature guide for commands, room configuration, persistence, privacy, and recovery behavior.
Archived: 2026-10-16 (UTC). The feature guide is the current runtime reference.
Propose an independent, room-scoped prediction game. The bot selects one opted-in decision maker and posts an approved two-option scenario from MongoDB. Other opted-in players privately predict option A or B before the decision maker privately locks a choice. Correct predictions earn shared economy credits.
“Blackmail” is fictional game framing only. The feature must not use real secrets, threats, leverage, or pressure to extract a personal disclosure. Prompts describe fictional choices; the decision maker may pass without penalty. The game records a choice for this round, not a psychological profile.
prototypeDilemmaPrompts, containing
scenario text and exactly two bounded options. Post all three to the room./bot dilemma predict A or B privately before
the prediction deadline. One prediction per participant is accepted; the
player may replace it until predictions close.The game measures predictions about one fictionalized choice, not a player’s personality, intent, or actual private behavior. Do not accumulate prediction histories into a profile or expose individual predictions before the outcome.
Store one strict, typed config document per (roomKey, gameId) in
prototypePartyGameConfigs, using <roomKey>:dilemma as this game’s ID.
Missing or invalid config means disabled. Every setting is loaded from the DB;
there is no hidden runtime default that silently enables the feature.
| Setting | Proposed validation / intent |
|---|---|
enabled |
Explicit room-level opt-in; false when absent. |
minPlayers, maxPlayers |
Bounded roster including one decision maker. |
predictionWindowMs, choiceWindowMs |
Positive bounded durations, persisted as deadlines on a session. |
minPredictions |
Bounded number of valid predictions required to settle. |
correctPredictionRewardCredits |
Bounded non-negative integer; checked against economy ceilings. |
maxRewardsPerRound |
Hard cap on recipient count / total award exposure. |
timeoutPolicy |
Typed choice; proposed default is void and no reward. |
Each prompt record has an ID, locale, category, scenario, option A, option B, moderation status, content revision, and timestamps. Only reviewed active records are selectable. Keep adult content in the DB, not source code. Prompt text must not request identifying information, non-consensual conduct, or disclosure of real secrets. Config and prompt revisions are pinned to the session so a live round does not change when an operator edits the catalog.
prototypeDilemmaSessions holds roomKey, bounded joined member numbers,
decision maker, prompt ID/revision, config revision, phase/revision, one
bounded prediction per eligible member, locked choice, deadlines, outcome, and
settlement IDs. Do not store free-form player messages or inferred traits.
Active records have no TTL; terminal records receive an explicit expiresAt
under the approved retention policy.
Permit only one active Dilemma session per room. Enforce this with a unique active key and conditional revision updates. A timer is only a wakeup: persisted deadlines and a compare-and-set phase transition determine the winner. Timers are unref’ed, tracked, canceled on plugin stop, and recovered from the repository at startup. A duplicate prediction, timeout callback, or process instance must not cause duplicate rewards.
Persist the final choice and outcome before settlement. Credit each correct prediction through the shared economy using a session- and member-specific idempotency key. A crash between outcome persistence and wallet settlement leaves the round pending; recovery retries the same keys and cannot pay twice. If a player leaves after joining, keep the frozen roster but reject commands from a member no longer present. A decision maker who leaves before locking a choice voids the round. New arrivals join only a later round.
start.ts.flowchart LR
Root["start.ts"] --> Plugin["DilemmaPlugin"]
Plugin --> Commands["DilemmaCommandSuite"]
Plugin --> Service["DilemmaService"]
Commands --> Parser["BotCommandParser"]
Commands --> Service
Service --> Rules["DilemmaRules"]
Service --> Repo["DilemmaRepository"]
Service --> Economy["Injected EconomyPort"]
Service --> Comms["Injected CharacterCommunicationApi"]
Repo --> Config[("Room-scoped game config")]
Repo --> Prompts[("prototypeDilemmaPrompts")]
Repo --> Sessions[("prototypeDilemmaSessions")]
Comms --> BC["Existing BcClient actions"]
classDiagram
class DilemmaPlugin {
+start()
+stop()
}
class DilemmaCommandSuite {
+register()
-predict(A|B)
-choose(A|B)
-pass()
}
class DilemmaService {
+startRound(roomKey)
+predict(sessionId, member, option)
+choose(sessionId, member, option)
+pass(sessionId, member)
+recover(roomKey)
+stop()
}
class DilemmaRules {
+validatePrompt(prompt)
+resolve(predictions, choice, minPredictions)
}
class DilemmaRepository {
<<port>>
+createActiveSession()
+recordPrediction(expectedRevision)
+lockChoice(expectedRevision)
+persistOutcome(expectedRevision)
+listRecoverable(roomKey)
}
class EconomyPort {
<<injected shared contract>>
+grantOnce(operationId, member, amount, reason)
}
DilemmaPlugin --> DilemmaCommandSuite
DilemmaPlugin --> DilemmaService
DilemmaCommandSuite --> DilemmaService
DilemmaService --> DilemmaRules
DilemmaService --> DilemmaRepository
DilemmaService --> EconomyPort
flowchart LR
Admin((Room admin)) --> Configure([Enable and tune this room])
Player((Opted-in player)) --> Join([Join next round])
DecisionMaker((Selected player)) --> Decide([Choose A, B, or pass])
Predictor((Eligible predictor)) --> Predict([Predict A or B])
Timer((Deadline worker)) --> Resolve([Lock and resolve predictions])
Configure --> Config[(Room config / reviewed catalog)]
Decide --> Session[(Round state)]
Predict --> Session
Resolve --> Reward([Credit correct predictions once])
flowchart TD
Start([Start requested]) --> Gate{Enabled config and enough joined?}
Gate -->|No| Deny[Private disabled / insufficient-player reply]
Gate -->|Yes| Select[Select decision maker and reviewed prompt]
Select --> Create[Persist session and prediction deadline]
Create --> Post[Post scenario and options]
Post --> Predict[Accept/revise private A/B predictions]
Predict --> ClosePred{Prediction deadline or choice submitted?}
ClosePred -->|Predictions closed| Lock[Hide further predictions]
ClosePred -->|Choice arrives| Lock
Lock --> Choice{Decision maker chooses or passes?}
Choice -->|Pass / leave / timeout| Void[Persist void outcome]
Choice -->|A or B| Save[Persist locked choice and outcome]
Save --> Quorum{Minimum predictions met?}
Quorum -->|No| Void
Quorum -->|Yes| Award[Grant each correct predictor idempotently]
Award --> Finish([Announce aggregate result and close])
Void --> Finish
stateDiagram-v2
[*] --> Lobby
Lobby --> Predicting: enabled + joined roster + prompt
Predicting --> Predicting: valid prediction / allowed replacement
Predicting --> ChoicePending: prediction deadline closes
Predicting --> ChoicePending: decision maker chooses early
ChoicePending --> Resolving: valid choice locked
ChoicePending --> Voided: pass / departure / timeout
Resolving --> Awarding: outcome persisted and quorum met
Resolving --> Voided: insufficient predictions
Awarding --> Complete: every idempotent reward resolved
Awarding --> Awarding: recovery retries same operation IDs
Voided --> Complete
Complete --> [*]
sequenceDiagram
participant Player
participant Cmd as DilemmaCommandSuite
participant Service as DilemmaService
participant DB as DilemmaRepository
participant Economy as EconomyPort
participant BC as CharacterCommunicationApi
Service->>DB: load enabled room config and active prompt
Service->>DB: create session with revision/deadline
Service->>BC: publish fictional scenario and options
loop Before prediction lock
Player->>Cmd: private predict A|B
Cmd->>Service: predict(member, enum)
Service->>DB: CAS bounded prediction
end
Player->>Cmd: private choose A|B
Cmd->>Service: choose(decisionMaker, enum)
Service->>DB: CAS lock choice and persist outcome
loop Correct predictors
Service->>Economy: grantOnce(session/member operation ID)
Economy-->>Service: credited / prior result / explicit failure
end
Service->>BC: announce aggregate result (dispatch is unconfirmed)
active are
selectable. No prompt authoring or moderation UI is included.minPredictions is the room-configured quorum. Predictions can be replaced
until the prediction window closes; an early locked choice closes it
immediately.The feature is not a mechanism for storing or learning psychological profiles. Tests cover strict options/configuration, duplicate and replaced predictions, ineligible members, early choice, quorum, pass, deadlines, departure, concurrent close, restart recovery, and idempotent awards. Disabled or void sessions cannot credit an account.