veratown-prototype

Communication And Social Features

This guide covers outgoing communication actions and the incoming friend/MOTD features. CommunicationPlugin owns the friend and character-entry subscriptions; CommunicationTestCommandSuite owns the admin diagnostic command. All sends use CharacterCommunicationApi and its journaled actions over the shared BcClient queue.

UML Map

classDiagram
    direction LR

    class CommunicationPlugin {
        +start() void
        +stop() void
        -friendReciprocation: FriendReciprocationAction
        -motd: MotdService
    }
    class CommunicationTestCommandSuite {
        +register() Dispose
        -execute(sender, args) Promise~void~
    }
    class CharacterCommunicationApi {
        +chat(text) ActionOutcome
        +chatRedacted(text) ActionOutcome
        +emote(text) ActionOutcome
        +action(text, dictionary) ActionOutcome
        +activity(text, dictionary) ActionOutcome
        +status(text, dictionary) ActionOutcome
        +hidden(member, text, dictionary) ActionOutcome
        +whisper(member, text) ActionOutcome
        +motd(member, text) ActionOutcome
        +beep(member, text) ActionOutcome
        +testAllChannels(member, text) ChannelResult[]
    }
    class SendRoomMessageAction {
        +execute(type, content, target, dictionary) ActionOutcome
    }
    class SendWhisperAction {
        +execute(member, text) ActionOutcome
    }
    class SendBeepAction {
        +execute(member, text) ActionOutcome
    }
    class FriendReciprocationAction {
        +handleMessage(message) Promise~ActionOutcome?~
        +resetConnectionState() void
    }
    class MotdService {
        +onCharacterEntered(member) Promise~ActionOutcome?~
        +resetConnectionState() void
    }
    class BcClient
    class ActionCoordinator
    class ActionJournal

    CommunicationPlugin --> BcClient : message/presence
    CommunicationPlugin --> FriendReciprocationAction
    CommunicationPlugin --> MotdService
    CommunicationPlugin --> CommunicationTestCommandSuite
    CommunicationTestCommandSuite --> CharacterCommunicationApi
    CharacterCommunicationApi --> SendRoomMessageAction
    CharacterCommunicationApi --> SendWhisperAction
    CharacterCommunicationApi --> SendBeepAction
    SendRoomMessageAction --> BcClient : ChatRoomChat
    SendWhisperAction --> BcClient : targeted Whisper
    SendBeepAction --> BcClient : AccountBeep
    FriendReciprocationAction --> BcClient : AccountUpdate / Hidden / OnlineFriends
    MotdService --> CharacterCommunicationApi
    SendRoomMessageAction --> ActionCoordinator
    SendWhisperAction --> ActionCoordinator
    SendBeepAction --> ActionCoordinator
    ActionCoordinator --> ActionJournal

Message Action Flow

stateDiagram-v2
    [*] --> Requested
    Requested --> Journaled: ActionCoordinator starts record
    Journaled --> Queued: action validates and enqueues packet
    Queued --> Failed: validation, disconnected transport, or emit error
    Queued --> DispatchedUnconfirmed: local Socket.IO emit succeeds
    DispatchedUnconfirmed --> DeliveryUnknown: no sender-side receipt exists
    DeliveryUnknown --> [*]
    Failed --> [*]

Room messages share BC’s rate-limited ChatRoomChat transport. Chat, Emote, Action, Activity, and Hidden are available through SendRoomMessageAction; Whisper has a dedicated action because it needs a target and BC’s map-room wrapping; Beep uses AccountBeep and requires the target to be a bot friend. These sends generally end at dispatched_unconfirmed; a successful emit does not prove display or delivery.

CharacterCommunicationApi.chatRedacted() uses the same journaled room-chat action but replaces any dispatch exception with a fixed error before it reaches the action journal. Use it for sensitive one-shot text whose content must not appear in an error string; action metadata still records only its length and dispatch status.

Action and Activity content is not arbitrary prose in BC’s UI. Action content must be a localized key available in BC asset/interface text, with matching substitutions in the dictionary. Activity needs an ActivityDictionary.csv key and activity metadata. The prototype’s channel test uses existing localized examples and substitutes the supplied text into the nickname-change Action. Status is declared by the wire type but is not displayed by the current BC chat renderer, so the diagnostic probe reports it skipped. Hidden is delivered as a targeted packet but intentionally does not appear in the room transcript.

Friend Reciprocity Flow

stateDiagram-v2
    [*] --> IncomingHidden
    IncomingHidden --> Ignored: wrong type/content/target/sender
    IncomingHidden --> AlreadyFriend: sender already in bot friend state
    IncomingHidden --> Pending: valid ChatRoomFriendRequestAdd
    Pending --> Deduplicated: same sender request already pending
    Pending --> AccountUpdate: add sender to bot FriendList
    AccountUpdate --> NotifyPeer: emit reciprocal Hidden request
    NotifyPeer --> ReadBack: query OnlineFriends
    ReadBack --> PersistedObserved: online result says Type=Friend
    ReadBack --> PersistenceNotObserved: absent/offline/query failure
    AlreadyFriend --> RecordedSatisfied
    Ignored --> [*]
    Deduplicated --> [*]
    PersistedObserved --> [*]
    PersistenceNotObserved --> [*]
    RecordedSatisfied --> [*]

The read-back is an online-friends subset. If the sender is offline or missing from the response, the feature records persistence as unobserved rather than claiming rejection or success.

MOTD Flow

stateDiagram-v2
    [*] --> CharacterEntered
    CharacterEntered --> Skipped: invalid member / empty message / already greeted
    CharacterEntered --> ResolveMessage: first valid entry this connection
    ResolveMessage --> WhisperRequested: matching scheduled preset or MOTD_TEXT fallback
    WhisperRequested --> DispatchedUnconfirmed: local emit succeeds
    WhisperRequested --> Failed: send action fails
    DispatchedUnconfirmed --> [*]
    Failed --> RetryEligible: remove greeting marker
    Skipped --> [*]
    RetryEligible --> [*]

The greeting set is reset on each BC ready event. A sent Whisper has no delivery receipt, so it remains dispatched_unconfirmed.

The message is selected when the character enters. The bot reads the current motd.schedule value from the live prototypeGameConfig snapshot; writes from the admin web are picked up by the existing game-config watcher. The schedule contains named message presets and ordered rules, using an explicit IANA time zone (UTC by default). A rule can match weekdays, a time range, or both; empty weekdays mean every day, and an overnight range belongs to its starting weekday. The first matching rule selects its preset. If the config is absent or invalid, or no rule matches, the bot uses MOTD_TEXT. Delete the stored schedule to return to that environment fallback. Messages are limited to 500 characters.

Administrators can create, edit, reorder, and delete presets and schedule rules from the MOTD tab in the admin web. Those edits use the global-config revision check and audit path; they do not change live BC state.

Communication Test Command

Room admins can run !testcomms <memberId> <text> or /bot testcomms <memberId> <text>. Chat/Emote/Action/Activity are room-wide; Hidden/Whisper/Beep target the supplied member. The command privately reports each action status and records only safe summaries such as target, channel, and text length. Use non-sensitive test text because several variants are visible in the room.