veratown-prototype

The Hot Seat — Feature Proposal

Status: Implemented as an independently wired, disabled-by-default feature. See the Hot Seat guide for runtime behavior, commands, operator prompt moderation, persistence, and current release status.

Archived: 2026-10-16 (UTC). The feature guide is the current runtime reference.

Purpose and scope

An independent, room-scoped social bluffing game. The bot chooses one opted-in player, presents an approved database prompt, and lets that player answer truthfully, bluff, or refuse. Other opted-in players vote whether the answer is true or a lie. The game evaluates only the player’s declared mode against the room vote; it cannot determine whether a personal statement is factually true.

The optional double/forfeit rules use the proposed shared economy from the economy proposal, not a separate Hot Seat wallet or game-to-game imports.

Product rules

The chat vote is a social judgment, not a truth detector. The bot must never state that it verified the response, diagnose a player, or store a behavioral profile.

Room configuration

Use one typed document per (roomKey, gameId) in a room-scoped prototypePartyGameConfigs collection. The proposed ID for this game is <roomKey>:hot-seat. The document has a revision, enabled (false when missing), typed settings, and update timestamp. It is loaded from MongoDB; absence or invalid data disables the game. Admin updates use strict bounds and revision checks. A settings revision is pinned to each session; changes apply to the next round, not an active vote.

Setting Proposed validation / intent
minPlayers, maxPlayers Safe bounded integers; respondent plus voters fit the configured cap.
answerWindowMs, voteWindowMs Bounded positive durations; persisted absolute deadlines drive recovery.
minVotes At least one and no greater than eligible voters. Below quorum, void.
bluffMultiplier Initially 2; positive bounded multiplier, still subject to economy ceiling.
refusalPolicy, timeoutPolicy Typed enums, default to no economic penalty.
enabled Explicit room-level opt-in; absent means disabled.

The operator-managed prototypeHotSeatPrompts collection contains validated prompt IDs, category, locale, bounded prompt text, moderation status, content revision, and timestamps. Only approved/active records can be selected. Do not put example adult prompts in code or this proposal. Prompt content must be curated for an adults-only room and must not ask for identifying, non-consensual, or otherwise unsafe personal disclosures.

Persistence and lifecycle

prototypeHotSeatSessions stores a bounded roster, respondent member number, prompt ID/revision, config revision, phase/revision, respondent’s truth / bluff intent, bounded vote choices, deadlines, and outcome/settlement IDs. It does not store the respondent’s free-form answer or a transcript. Terminal sessions may expire after the approved retention period. Active sessions have no TTL and are recovered or explicitly voided.

Only one active Hot Seat session per room is allowed. Use a unique active key and revision-checked updates so concurrent starts, duplicate votes, and stale timer callbacks cannot advance the same round twice. Deadlines are persisted; timers are in-memory wakeups, unref’ed, canceled on stop, and rebuilt from durable state after restart. Economic rewards/forfeitures use stable session-derived operation IDs.

If the respondent leaves before submitting/refusing, void the round and make no score change. A departed voter remains in the frozen roster but cannot add or change a vote; new arrivals are eligible next round. If the bot shuts down before a vote is closed, resume from the persisted phase and deadline. If the outcome was saved but economy settlement was interrupted, retry the same idempotent settlement.

UML

Component diagram

flowchart LR
    Root["start.ts"] --> Plugin["HotSeatPlugin"]
    Plugin --> Commands["HotSeatCommandSuite"]
    Plugin --> Service["HotSeatService"]
    Commands --> Parser["BotCommandParser"]
    Commands --> Service
    Service --> Rules["HotSeatRules"]
    Service --> Repo["HotSeatRepository"]
    Service --> Economy["Injected EconomyPort"]
    Service --> Comms["Injected CharacterCommunicationApi"]
    Repo --> Config[("Room-scoped game config")]
    Repo --> Prompts[("prototypeHotSeatPrompts")]
    Repo --> Sessions[("prototypeHotSeatSessions")]
    Comms --> BC["Existing BcClient actions"]

Class diagram

classDiagram
    class HotSeatPlugin {
        +start()
        +stop()
    }
    class HotSeatCommandSuite {
        +register()
        -vote()
        -declareMode()
        -refuse()
    }
    class HotSeatService {
        +startRound(roomKey)
        +declareMode(sessionId, member, mode)
        +castVote(sessionId, member, vote)
        +refuse(sessionId, member)
        +recover(roomKey)
        +stop()
    }
    class HotSeatRules {
        +resolve(mode, votes, minVotes)
        +validateConfig(config)
    }
    class HotSeatRepository {
        <<port>>
        +createActiveSession()
        +transition(expectedRevision, nextState)
        +listRecoverable(roomKey)
    }
    class EconomyPort {
        <<injected shared contract>>
        +forfeitAvailable(operationId, member)
        +grantOnce(operationId, member, amount, reason)
    }
    HotSeatPlugin --> HotSeatCommandSuite
    HotSeatPlugin --> HotSeatService
    HotSeatCommandSuite --> HotSeatService
    HotSeatService --> HotSeatRules
    HotSeatService --> HotSeatRepository
    HotSeatService --> EconomyPort

Use-case diagram

flowchart LR
    Admin((Room admin)) --> Enable([Enable and configure Hot Seat])
    Player((Opted-in player)) --> Join([Join a round])
    Player --> Respond([Answer, declare intent, or refuse])
    Voter((Opted-in voter)) --> Vote([Vote true or lie])
    Timer((Deadline worker)) --> Resolve([Resolve quorum and outcome])
    Enable --> Config[(Room config)]
    Respond --> Session[(Active round)]
    Vote --> Session
    Resolve --> Economy([Apply one bounded settlement])

Activity diagram: one round

flowchart TD
    Start([Start requested]) --> Enabled{Room config valid and enabled?}
    Enabled -->|No| Deny[Reply disabled; no session]
    Enabled -->|Yes| Join{Minimum opted-in players present?}
    Join -->|No| Deny
    Join -->|Yes| Create[Select respondent and approved prompt]
    Create --> Ask[Post prompt; persist deadline]
    Ask --> Reply{Respondent answers, refuses, leaves, or times out}
    Reply -->|Refuse / leave| Void[Persist terminal no-penalty outcome]
    Reply -->|Timeout| Policy[Apply configured timeout policy]
    Reply -->|Answer + private intent| Vote[Open vote; accept one vote per eligible member]
    Vote --> Deadline{Deadline reached?}
    Deadline -->|No| Vote
    Deadline -->|Yes| Quorum{Vote quorum met?}
    Quorum -->|No| Void
    Quorum -->|Yes| Resolve[Resolve plurality and persist outcome]
    Resolve --> Economy[Idempotent credit double / available-credit forfeit / no change]
    Policy --> End([Close round])
    Economy --> End
    Void --> End

State machine

stateDiagram-v2
    [*] --> Lobby
    Lobby --> Question: enabled + quorum of joined players
    Question --> Response: prompt published
    Response --> Voting: answer posted + private truth/bluff intent
    Response --> Refused: respondent declines
    Response --> Voided: respondent leaves / timeout policy voids
    Voting --> Voting: valid vote before deadline
    Voting --> Voided: deadline with insufficient votes
    Voting --> Settling: quorum; persist outcome
    Settling --> Complete: idempotent economy result recorded
    Settling --> Settling: restart; retry same operation ID
    Refused --> Complete
    Voided --> Complete
    Complete --> [*]

Sequence / communication diagram

sequenceDiagram
    participant Player
    participant Cmd as HotSeatCommandSuite
    participant Service as HotSeatService
    participant DB as HotSeatRepository
    participant BC as CharacterCommunicationApi
    participant Economy as EconomyPort
    Player->>Cmd: start / join
    Cmd->>Service: startRound(roomKey)
    Service->>DB: create session + persist prompt/config revision
    Service->>BC: publish prompt and respondent
    Player->>Cmd: private truth/bluff intent
    Cmd->>Service: declareMode(sessionId, member, mode)
    Service->>DB: CAS response -> voting
    Service->>BC: announce vote window (unconfirmed dispatch)
    loop Before persisted deadline
        Player->>Cmd: vote true|lie
        Cmd->>Service: castVote(member, enum)
        Service->>DB: CAS insert/replace allowed vote
    end
    Service->>DB: persist resolved outcome
    Service->>Economy: idempotent settle/forfeit if applicable
    Service->>BC: announce result (dispatch status remains honest)

Adopted implementation defaults and acceptance criteria

Room administrators must explicitly confirm the adults-only room policy before enabling. Trusted operators manually moderate prompt records and set them approved; no prompt text is seeded. The implementation uses the strict lie majority rule (ties fool a declared bluff), defaults to a multiplier of two, sets a hard available-credit ceiling of 100,000, defaults refusal and timeout to no penalty, and accepts mode/vote commands only through native /bot or private Whisper paths. Settings remain bounded and room-configurable; active rounds pin their settings revision.

Acceptance requires independent disablement; strict private command parsing; vote/round CAS tests under duplicate and concurrent commands; timeout, departure, restart, and settlement-retry tests; zero raw response text in Mongo/logs; and no economic mutation when the round is disabled, refused, or below quorum. Live room qualification remains separate from implementation; missing configuration is disabled and no prompt content is included by default.

References