Dilemma is an independent, room-scoped prediction game. “Blackmail” is fictional framing only: prompts must not ask for real secrets, identifying information, or non-consensual conduct. Players opt in separately for each round, and the decision maker may pass without penalty. The feature does not infer age, build player profiles, or retain prediction history beyond the round record.
The feature is disabled unless the selected runtime database contains a valid, enabled room configuration. Startup inserts the disabled baseline below and ten active English fictional A/B prompts only when absent. There are no in-game configuration commands. Operators provision and review configuration and prompt records in the isolated runtime database; never edit the source database or enable the game outside an approved adults-only room policy. The runtime records the operator’s room-policy confirmation but does not independently verify player age.
The starter catalog uses non-explicit, playful fictional choices. Startup preserves existing config values, enabled state, prompt content, and moderation decisions. Use the party-game provisioning workflow to populate and explicitly enable an approved room.
All command responses are private whispers. Native /bot and ! command forms
are supported by the shared parser.
| Command | Behavior |
|---|---|
/bot dilemma join |
Opt in for the next round only. |
/bot dilemma withdraw |
Withdraw from the next round’s opt-in roster. |
/bot dilemma start |
Room-admin only; starts a round from present, opted-in members. |
/bot dilemma predict A or B |
Submit or replace one private prediction before predictions close. |
/bot dilemma choose A or B |
Decision maker privately locks the choice early or during the choice window. |
/bot dilemma pass |
Decision maker voids the round without penalty or rewards. |
The room receives the fictional scenario and its two options, but not individual predictions or the decision maker’s identity. The selected decision maker is notified privately, but BC does not confirm whisper delivery. Once a choice is durably locked, the room receives only the aggregate result and total credits awarded. A timeout, pass, departure before choice lock, or missed prediction quorum voids the round. Incorrect predictions never lose credits.
Opt-ins are held in process memory until a round starts, a member withdraws, leaves, or the connection becomes ready again. They are not durable preferences; a restart does not carry consent into another round.
The repository owns prototypePartyGameConfigs, keyed by
(roomKey, gameId) with _id: "<roomKey>:dilemma", plus
prototypeDilemmaPrompts and prototypeDilemmaSessions. Configuration and
prompt documents are strict schemas; malformed records fail closed. Operators
must increment revision when changing a configuration and contentRevision
when revising a prompt.
Required configuration fields:
| Field | Bounds and meaning |
|---|---|
enabled |
Explicit opt-in; missing or false disables the game. |
adultsOnlyRoomConfirmed |
Must be true to enable; an operator policy attestation, not age verification. |
locale |
Exact locale used to select reviewed prompts. |
minPlayers, maxPlayers |
2–50 opted-in members, including the decision maker. |
predictionWindowMs, choiceWindowMs |
Each 10 seconds–1 hour; persisted deadlines govern recovery. |
minPredictions |
At least one, no more than the predictor roster. |
correctPredictionRewardCredits |
1–100 credits when enabled. |
maxRewardsPerRound |
At most 49; the maximum roster must fit the recipient cap. |
timeoutPolicy |
Currently only void; no timeout reward path exists. |
The product of the per-prediction reward and recipient cap cannot exceed 1,000 credits per round. An enabled configuration that cannot meet its minimum roster, quorum, or recipient limits is rejected.
Prompt records require _id, locale, category, scenario, optionA,
optionB, moderationStatus, contentRevision, createdAt, and updatedAt.
Only records marked moderationStatus: "active" are selectable; drafts and
archived prompts are not. Scenario text is capped at 1,000 characters and each
option at 300. Human review remains necessary: the runtime does not attempt to
infer whether content requests secrets or is otherwise appropriate.
The active catalog is bounded to 1,000 prompts per locale.
Example disabled configuration shape (timestamps are BSON dates):
{
"_id": "main:dilemma",
"schemaVersion": 1,
"gameId": "dilemma",
"roomKey": "main",
"revision": 1,
"enabled": false,
"adultsOnlyRoomConfirmed": false,
"locale": "en",
"minPlayers": 3,
"maxPlayers": 10,
"predictionWindowMs": 60000,
"choiceWindowMs": 60000,
"minPredictions": 2,
"correctPredictionRewardCredits": 5,
"maxRewardsPerRound": 9,
"timeoutPolicy": "void",
"createdAt": "<BSON Date>",
"updatedAt": "<BSON Date>"
}
Only one active session per room is allowed. MongoDB enforces the active-room unique key; predictions, phase transitions, outcomes, and reward progress use revision compare-and-set updates. Session snapshots pin the exact config and reviewed prompt revisions and bounded text used for that round, so later catalog edits do not alter an active game.
The roster is frozen when a round starts. Commands from absent members are rejected; a decision maker who leaves before locking a choice voids the round. New arrivals can only join a later round. Timers are wakeups, not state authorities: persisted deadlines and conditional writes determine transitions. Timers are unref’ed, canceled on plug-in stop, and active sessions are recovered on startup. The plug-in detaches room listeners before draining event work. Its service stop may emit a final resolution, so it drains again after releasing the resolution subscription; cleanup failures cannot skip either drain or service shutdown.
The final choice and complete reward plan are persisted before economy writes.
Each correct predictor is credited through the shared EconomyPort with a
stable dilemma:<sessionId>:<memberNumber> operation ID. If recovery repeats a
grant, grantOnce returns the prior result instead of crediting twice. Voided
rounds never award credits. Aggregate room announcements use the existing
communication action and are dispatched without a BC delivery acknowledgement.
prototypePartyGameConfigs and prototypeDilemmaPrompts are operator-managed
configuration/content collections and have no TTL. Active session records have
no expiry. Terminal sessions receive an explicit 60-day expiresAt and a TTL
index; TTL cleanup is asynchronous. The session stores only bounded member
numbers, A/B choices, prompt/config snapshots, deadlines, and reward state—no
raw player text or inferred traits.
flowchart LR
Root["start.ts"] --> Plugin["DilemmaPlugin"]
Plugin --> Commands["DilemmaCommandSuite"]
Plugin --> Service["DilemmaService"]
Commands --> Service
Service --> Repo["MongoDilemmaRepository"]
Service --> Economy["EconomyPort"]
Service --> Comms["DilemmaCommunicationPort"]
Repo --> Config[("prototypePartyGameConfigs")]
Repo --> Prompts[("prototypeDilemmaPrompts")]
Repo --> Sessions[("prototypeDilemmaSessions")]
stateDiagram-v2
[*] --> Predicting: enabled config + frozen opt-in roster
Predicting --> Predicting: accept or replace private prediction
Predicting --> ChoicePending: prediction deadline / early choice
ChoicePending --> Voided: pass / departure / timeout
ChoicePending --> Voided: prediction quorum not met
ChoicePending --> Awarding: choice locked + quorum met
Awarding --> Awarding: retry pending grant with same operation ID
Awarding --> Complete: all grants resolved
Voided --> Complete
Complete --> [*]
The repository compare-and-set transition is authoritative; deadlines only wake the resolver. Each correct prediction is granted once after the durable outcome and reward plan are saved. A void round never calls the economy.