This document describes the prototype’s typed interface between Veratown intent, BC wire packets, and available acknowledgement/read-back signals. The exhaustive request/response classification is in BC-SOCKET-API-ACKNOWLEDGEMENTS.md.
For the end-to-end composition, plug-in order, action flow, and data-authority boundaries, see BOT-ARCHITECTURE.md.
Protocol baseline: Bondage-College 51df6acc01f8fa185079d8deebf591fe3642a148 (R133). The complete BC event and payload declarations are vendored in src/adapters/bc/bc-messages.d.ts; src/adapters/bc/bc-messages-patch.d.ts supplies browser-only external aliases and precise map-configuration, tether, map-doodad-reference, asset-category, and color types. These declarations are type-only; no browser runtime or bc-bot implementation is included.
The 46-commit update since the previous pin adds four assets and changes
metadata on 87 existing assets, including lock/color and eligibility metadata.
The catalog contains 85 groups and 2,662 assets; unsupported callbacks and
dynamic getters remain unexecuted and mutation stays guarded. The map catalog
remains at 170 tiles and 372 objects and now retains BC’s CanBeTetherPoint
flag. Wire declarations add tether state/configuration and a MapViewTether
chat-message variant; this is not a Socket.IO event, so the 26/30 event maps and
acknowledgement classifications remain unchanged. The prototype does not
implement tether actions. See the
2026-11-03 compatibility review,
the 2026-10-29 review, and
the shared map contract.
The Phase 1 appearance foundation adds
BcAssetCatalog, BcAppearanceCodec, BcAppearancePrerequisiteEvaluator,
BcLockCapabilityCatalog,
feature-neutral appearance API contracts, and the shared BC adapter
BcAppearanceAction. It uses guarded BcClient methods, not nested
BcApiPlane.dispatch() calls, so each mutation has one journal owner.
AppearanceItemSetService loads versioned database definitions on each call and
uses the shared per-character sequence queue. Set key, revision and piece index
are journaled with each mutation; partial and unconfirmed results remain explicit.
The adapter evaluator implements pinned BC asset and selected-option
prerequisites against observed character state; unavailable evidence remains
unknown and fails closed. The composition root supplies its check to the
feature-owned itemset service. This does not add a transport-independent
prerequisite contract to core/.
The shared mutation contract includes a JSON-safe craft snapshot for restoration
workflows to preserve exact observed BC Craft metadata. The shower uses this
only for supported catalogued clothing; itemsets and chat commands cannot provide
the snapshot, and its contents are not journaled.
Its optional per-piece callback runs inside that queue after each journaled
outcome. An optional caller signal cancels pending observation waits and skips
not-yet-dispatched pieces without bypassing the BC send queue. Already-emitted
writes remain unconfirmed unless a matching BC observation arrives; cancelled
queued pieces have failed journal outcomes, not synthetic observations.
Location Entry Behavior consumes the core location-transition event
contract and reads its strict policy from room-scoped prototypeGameConfig; it applies
standard appearance itemsets without changing their lock configuration. Any
configured timer lock and removeItemOnExpiry value are passed through to BC’s
native appearance behavior. The feature does not persist application progress or
manage expiry.
BcAppearanceAction also takes the Mongo-backed
prototypeCharacterActionLeases per-room/member lease in production
composition. The local queue remains, while the lease serializes the same
target’s appearance mutations across supervisor, helper, and ABCL processes.
Lease loss aborts the guarded write; lease records are coordination state, not
action-journal outcomes or evidence that BC state changed.
BcApiPlane and the exhaustive BC_OUTGOING_CONTRACTS catalog are implemented in src/adapters/bc/api-plane.ts. The production composition root at src/app/start.ts constructs one plane using the shared BcClient and ActionCoordinator, then injects it into CommunicationPlugin. Friend reciprocity uses it for the OnlineFriends read-back. Other feature actions continue to use dedicated actions and guarded BcClient methods; the plane is not a replacement for those domain APIs. src/index.ts only launches the composition root. Unit tests cover the plane contract, and an integration test covers the communication plug-in through the real client adapter and a fake socket.
The optional ABCL feature uses a narrowly typed BC-mod extension dictionary
adapter for ABCL:sync, ABCLMsg, and ECHO_INFO2. These mod-owned fields are
not part of BC’s pinned standard ChatMessageDictionary union; they are
validated at the adapter boundary and sent only through BcClient’s existing
rate-limited ChatRoomChat queue. AbclModAction owns the journal record and
stores only the fixed operation name and target, never the serialized mod
payload. The extension has no direct BC acknowledgement; dispatch remains
dispatched_unconfirmed, and a later ABCL mod response is bounded input for a
new decision, not proof that an earlier command succeeded.
Administrator ABCL tests request a new sync through the same action. A
change-specific test uses a bounded, session-local response window and real
thresholds; a matching later response remains loosely correlated, not a
protocol acknowledgement.
The plane supports typed packet dispatch, raw BC event subscriptions, and requests waiting for one selected response event. Contracts distinguish listed progress and terminal-failure events; callers can classify success/failure variants within a shared response event, and may provide a current-state getter for already-satisfied checks. The caveats below describe the remaining limits.
BcClient also subscribes to ChatRoomSyncArousal and retains only a validated
0–100 progress projection on the current room character. Pose and relationship
facts use the existing pose and full-character syncs; the snapshot exposes only
owner/lover booleans, not relationship details. Validated title and reputation
are projected from full character syncs. Appearance facts remain item-scoped:
expression, lock presence, and removal timer are derived from the exact observed
item property, without exposing lock-holder identity. Veratown credits
conditions read the economy service’s available balance because BC excludes
private Money from room syncs.
classDiagram
direction LR
class ClientToServerEvents {
<<BC wire interface>>
+AccountLogin
+AccountCreate
+PasswordReset
+PasswordResetProcess
+AccountUpdate
+AccountUpdateEmail
+AccountQuery
+AccountBeep
+AccountOwnership
+AccountLovership
+AccountDifficulty
+AccountDisconnect
+ChatRoomSearch
+ChatRoomCreate
+ChatRoomJoin
+ChatRoomLeave
+ChatRoomChat
+ChatRoomCharacterUpdate
+ChatRoomCharacterExpressionUpdate
+ChatRoomCharacterPoseUpdate
+ChatRoomCharacterArousalUpdate
+ChatRoomCharacterItemUpdate
+ChatRoomCharacterMapDataUpdate
+ChatRoomAdmin
+ChatRoomAllowItem
+ChatRoomGame
}
class ServerToClientEvents {
<<BC wire interface>>
+LoginResponse
+CreationResponse
+PasswordResetResponse
+AccountQueryResult
+AccountOwnership
+AccountLovership
+AccountBeep
+ChatRoomSearchResult
+ChatRoomCreateResponse
+ChatRoomSearchResponse
+ChatRoomMessage
+ChatRoomGameResponse
+ChatRoomSync
+ChatRoomSyncCharacter
+ChatRoomSyncMemberJoin
+ChatRoomSyncMemberLeave
+ChatRoomSyncRoomProperties
+ChatRoomSyncReorderPlayers
+ChatRoomSyncSingle
+ChatRoomSyncExpression
+ChatRoomSyncPose
+ChatRoomSyncArousal
+ChatRoomSyncItem
+ChatRoomSyncMapData
+ChatRoomUpdateResponse
+ChatRoomAllowItem
+ForceDisconnect
+LoginQueue
+ServerInfo
+ServerMessage
}
class BcClient {
<<transport adapter>>
+start() Promise~void~
+close() void
+currentPlayer: BcCharacterSnapshot?
+currentRoom: BcRoomSnapshot?
+getMapObservation() currentConnectionMap?
+setRoomCreationDefinition(definition?) void
+isFriend(memberNumber) boolean
+dispatchPacket~Event~(event, payload) Promise~void~
+onServerEvent~Event~(event, listener) Unsubscribe
+queryOnlineFriends(timeoutMs) Promise~BcOnlineFriend[]~
+updateMapObject(position, style) Promise~void~
+updateRoomSettings(settings) Promise~void~
+updateOwnMapPosition(position) Promise~void~
+getAppearanceObservation(memberNumber) BcAppearanceObservation?
+getCharacterSnapshot(memberNumber) BcCharacterSnapshot?
+queryItemAccess(memberNumber, timeoutMs, signal) Promise~BcItemAccess~
+updateCharacterAppearance(expected, appearance, signal) Promise~void~
+events: ready / characterEntered / room / roomObserved / roomUpdateResponse / mapPosition / message / transportState / failure
}
class BcClientConfig {
<<interface>>
+serverUrl: string
+origin: string
+username: string
+password: string
+room: string
+roomDefinition?: BcRoomDefinition
+readyTimeoutMs?: number
}
class BcSocketLike {
<<transport port>>
+on(event, listener)
+off(event, listener)
+emit(event, payload)
+connect()
+disconnect()
+connected: boolean
}
class BcRoomDefinition {
<<BC-derived type>>
+Name
+Description
+Background
+Space
+Admin
+Ban
+Access
+Visibility
+Limit
+BlockCategory
+Game
+Language
+MapData
}
class BcRoomSnapshot {
<<runtime projection>>
+Name
+Character: BcCharacterSnapshot[]
+Admin
+Whitelist
+Ban
+Description
+Background
+Limit
+Game
+Visibility
+Access
+BlockCategory
+Language
+Space
+Custom
+MapData
}
class BcCharacterSnapshot {
<<runtime projection>>
+MemberNumber
+Name
+Nickname
+Title
+Reputation
+FriendList
+FriendNames
+MapData
+Appearance
+ActivePose
+ArousalProgress
+HasOwner
+HasLover
}
class BcMapPosition {
<<interface>>
+X: number
+Y: number
}
class BcChatRoomMessage {
<<BC-derived type>>
+Sender
+Target
+Type
+Content
+Dictionary
}
class CharacterCommunicationApi {
<<feature API>>
+whisper(memberNumber, text) ActionOutcome
+motd(memberNumber, text) ActionOutcome
+chat(text) ActionOutcome
+chatRedacted(text) ActionOutcome
+emote(text) ActionOutcome
+beep(memberNumber, text) ActionOutcome
}
class HelperTaskNotifier {
<<core wake-up port>>
+notifyTaskAvailable(agentId) dispatched_or_unavailable
}
class HelperTaskWakeups {
<<core subscription port>>
+subscribeTaskAvailable(listener) Dispose
}
class SupervisorPositionObservationSession
class HelperPositionObservationSession
class HelperBotsPlugin
class TrackedTasks {
<<transport-neutral shutdown drain>>
+track(promise)
+drain()
}
SupervisorPositionObservationSession ..|> HelperTaskNotifier
HelperPositionObservationSession ..|> HelperTaskWakeups
HelperBotsPlugin --> HelperTaskNotifier : after durable enqueue
HelperBotsPlugin --> HelperTaskWakeups : wake existing Mongo poller
HelperBotsPlugin --> TrackedTasks : wait for accepted work before Mongo closes
class FriendReciprocationAction {
<<incoming-message action>>
+handleMessage(message) Promise~ActionOutcome?~
+resetConnectionState() void
}
class MotdService {
<<incoming-presence action>>
+onCharacterEntered(memberNumber) Promise~ActionOutcome?~
+resetConnectionState() void
}
class SendRoomMessageAction {
<<dispatch-only action>>
+execute(type, content) ActionOutcome
+executeRedacted(type, content) ActionOutcome
}
class SendBeepAction {
<<dispatch-only action>>
+execute(memberNumber, message) ActionOutcome
}
class BcApiPlane {
<<public typed API>>
-client: BcClient
-coordinator: ActionCoordinator
-pendingResponseEvents: Map
+contract~Event~(event) BcEventContract
+subscribe~Event~(event, listener) Unsubscribe
+dispatch~Event~(event, payload, metadata) Promise~ActionOutcome~
+request~Request, Response~(spec) Promise~BcRequestResult~
}
class BcEventContract {
<<interface>>
+model: AckModel
+responseEvents: BcIncomingEventName[]
+progressResponseEvents?: BcIncomingEventName[]
+failureResponseEvents?: BcIncomingEventName[]
+directResponseEvents?: BcIncomingEventName[]
+correlation: string
+limitation: string
}
class BcAcknowledgementModel {
<<union>>
direct_response
conditional_response
state_observation
dispatch_only
}
class BcIncomingEventName {
<<keyof>>
names in ServerToClientEvents
}
class BcOutgoingEventName {
<<keyof>>
names in ClientToServerEvents
}
class BcIncomingPayload {
<<mapped type>>
payload selected by inbound event name
}
class BcOutgoingPayload {
<<mapped type>>
payload selected by outbound event name
}
class BcActionMetadata {
<<interface>>
+action?: string
+target?: Record~string, number|string~
+desired?: Record~string, unknown~
}
class BcRequestSpec {
<<interface>>
+requestEvent: BcOutgoingEventName
+payload: BcOutgoingPayload~Request~
+responseEvent: BcIncomingEventName
+matches(response) boolean
+current?() Response
+classifyResponse?(response) Classification
+timeoutMs: number
}
class BcRequestResult {
<<interface>>
+outcome: ActionOutcome
+response?: BcIncomingPayload~Response~
}
class ObservationChannel {
<<interface>>
+current() Observation
+subscribe(listener) Unsubscribe
}
class ObservedActionRequest {
<<interface>>
+action: string
+target: Record
+desired: Record
+timeoutMs: number
+signal?: AbortSignal
+channel: ObservationChannel
+matches(observation) boolean
+evidenceForCurrent?(observation) Record
+evidenceForObservation?(observation) Record
+dispatch() Promise~void~
}
class DispatchOnlyActionRequest {
<<interface>>
+action: string
+target: Record
+desired: Record
+dispatch() Promise~void~
+verify?() Promise~Readback~
}
class ActionCoordinator {
<<lifecycle>>
+executeDispatchOnly(request) ActionOutcome
+executeObserved(request) ActionOutcome
+recordAlreadySatisfied(...) ActionOutcome
}
class CharacterAppearanceApi {
<<shared contract>>
+execute(memberNumber, mutation) Promise~ActionOutcome~
}
class AppearanceMutation {
<<shared contract>>
+craftSnapshot?: JSON-safe observed Craft metadata
}
CharacterAppearanceApi ..> AppearanceMutation
class CharacterAppearanceSequenceApi {
<<ordered shared contract>>
+executeSequence(memberNumber, mutations, source, onPieceOutcome?, signal?) Promise~ActionOutcome[]~
}
class CharacterMutationLeasePort {
<<cross-process core contract>>
+acquire(memberNumber, signal?) Promise~LeaseHandle?~
}
class CharacterMutationLeaseHandle {
+signal: AbortSignal
+assertOwned()
+release()
}
class ShowerAdminPort {
<<transport-neutral core command contract>>
+showSettings()
+updateSettings(settings)
+listSongs()
+addSong(song)
+editSong(id, song)
+setSongEnabled(id, enabled)
}
class ShowerAdminService
class WorldAdminCommandSuite
class BcAppearanceAction {
<<per-character journaled action>>
+execute(memberNumber, mutation) Promise~ActionOutcome~
+executeGuarded(memberNumber, mutation, beforeDispatch) Promise~ActionOutcome~
+executeLock(memberNumber, request, secrets?) Promise~ActionOutcome~
+executeSequence(memberNumber, mutations, source, onPieceOutcome?, signal?) Promise~ActionOutcome[]~
+stop() void
}
class CharacterAppearanceLockApi {
<<ephemeral credential port>>
+executeLock(memberNumber, request, secrets?) Promise~ActionOutcome~
}
class AppearanceItemSetService {
+validateDefinition(definition) AppearanceItemSet
+applyDefinition(memberNumber, definition, options) Promise~Result~
}
class BcAppearancePrerequisiteEvaluator {
<<pinned BC semantics>>
+evaluateItem(character, group, name, configuration?, targetIsPlayer?) BcPrerequisiteEvaluation
+evaluateList(value, character, target, targetIsPlayer?) BcPrerequisiteEvaluation
}
class BcPrerequisiteEvaluation {
<<adapter result>>
+status: satisfied | unsatisfied | unknown | unsupported
+prerequisite?: string
}
class BcAppearanceCodec {
<<headless adapter>>
+plan(character, mutation, actor) AppearanceBundle
+checkPrerequisites(character, group, name, configuration?, targetIsPlayer?) BcPrerequisiteEvaluation
+effectiveProperties(item) Record
+matches(observed, desired) boolean
}
class BcAssetCatalog {
<<pinned metadata adapter>>
+get(group, name) AssetDefinition?
+list() AssetDefinition[]
+classify(group, name) Classification
}
class BcLockCapabilityCatalog {
<<all source locks; reviewed mutation policy>>
+list() LockCapability[]
+get(type) LockCapability?
}
class MongoCharacterMutationLease
BcAppearanceAction ..|> CharacterAppearanceApi
BcAppearanceAction ..|> CharacterAppearanceLockApi
BcAppearanceAction ..|> CharacterAppearanceSequenceApi
AppearanceItemSetService --> CharacterAppearanceSequenceApi
AppearanceItemSetService ..> BcAppearanceCodec : prerequisite callback injected by composition root
WorldAdminCommandSuite --> ShowerAdminPort
ShowerAdminService ..|> ShowerAdminPort
BcAppearanceAction --> ActionCoordinator
BcAppearanceAction --> CharacterMutationLeasePort
CharacterMutationLeasePort ..> CharacterMutationLeaseHandle
MongoCharacterMutationLease ..|> CharacterMutationLeasePort
BcAppearanceAction --> BcClient
BcAppearanceAction --> BcAppearanceCodec
BcAppearanceCodec --> BcAppearancePrerequisiteEvaluator
BcAppearancePrerequisiteEvaluator ..> BcPrerequisiteEvaluation
BcAppearanceCodec --> BcAssetCatalog
BcAppearanceCodec --> BcLockCapabilityCatalog
class ActionOutcome {
<<interface>>
+operationId: string
+action: string
+status: ActionStatus
+requestedAt: number
+completedAt: number
+target: Record
+desired: Record
+evidence?: Record
+error?: string
}
class ActionStatus {
<<union>>
requested
dispatched
acknowledged
observed
persisted_observed
timed_out_unobserved
persistence_not_observed
dispatched_unconfirmed
dispatch_unrecorded
already_satisfied
failed
}
class ActionTransition {
<<interface>>
+status: ActionStatus
+at: number
+evidence?: Record
+error?: string
}
class ActionJournalDocument {
<<interface>>
+_id: string
+action: string
+target: Record
+desired: Record
+status: ActionStatus
+requestedAt: number
+updatedAt: number
+createdAt: Date
+expiresAt: Date
+history: ActionTransition[]
}
class AttemptResult~T~ {
<<discriminated union>>
+ok: boolean
+value?: T
+error?: Error
}
class ActionJournal {
<<port interface>>
+start(record) Promise~void~
+transition(operationId, transition) Promise~void~
}
class MongoActionJournal {
<<Mongo adapter>>
+initialize() Promise~void~
}
class SetMapObjectAction {
<<dedicated action>>
+execute(position, style) Promise~ActionOutcome~
}
class ApplyRoomSettingsAction {
<<dedicated action>>
+execute(roomName, revision, settings, botMemberNumber) Promise~ActionOutcome~
}
class SendWhisperAction {
<<dedicated action>>
+execute(memberNumber, text) Promise~ActionOutcome~
}
ClientToServerEvents --> BcApiPlane : typed outbound payloads
ServerToClientEvents --> BcApiPlane : typed inbound payloads
BcApiPlane --> BcOutgoingPayload : dispatch payload typing
BcApiPlane --> BcIncomingPayload : response/listener typing
BcEventContract --> BcAcknowledgementModel
BcIncomingEventName --> BcIncomingPayload
BcOutgoingEventName --> BcOutgoingPayload
BcApiPlane --> BcClient : dispatch / subscribe
BcClient --> BcSocketLike : owns transport
BcClient --> BcClientConfig : configured by
BcClient --> BcRoomSnapshot : maintains observed state
BcRoomSnapshot --> BcCharacterSnapshot
BcRoomDefinition --> ClientToServerEvents : room create payload
BcChatRoomMessage --> ServerToClientEvents : message payload
BcMapPosition --> BcCharacterSnapshot : map state
CharacterCommunicationApi --> SendWhisperAction
CharacterCommunicationApi --> SendRoomMessageAction
CharacterCommunicationApi --> SendBeepAction
SendWhisperAction --> BcClient : ChatRoomChat Hidden
SendRoomMessageAction --> BcClient : ChatRoomChat
SendBeepAction --> BcClient : AccountBeep
MotdService --> CharacterCommunicationApi : characterEntered whisper
FriendReciprocationAction --> BcClient : ChatRoomMessage incoming
FriendReciprocationAction --> BcClient : AccountUpdate and hidden request
FriendReciprocationAction --> BcApiPlane : OnlineFriends read-back
SetMapObjectAction --> BcClient : ChatRoomAdmin map observation
ApplyRoomSettingsAction --> BcClient : guarded ChatRoomAdmin room update
ApplyRoomSettingsAction --> ActionCoordinator : journal room-settings reconciliation
BcApiPlane --> ActionCoordinator : journal operation lifecycle
CharacterCommunicationApi --> ActionCoordinator : journal operation lifecycle
MotdService --> ActionCoordinator : journal operation lifecycle
FriendReciprocationAction --> ActionCoordinator : journal operation lifecycle
ActionCoordinator --> ActionJournal : persist transitions
ActionCoordinator --> DispatchOnlyActionRequest
ActionCoordinator --> ObservedActionRequest
ObservedActionRequest --> ObservationChannel
ActionCoordinator --> AttemptResult
ActionCoordinator --> ActionTransition
ActionOutcome --> ActionStatus
MongoActionJournal ..|> ActionJournal
ActionJournalDocument --> ActionTransition
SetMapObjectAction --> ActionCoordinator
ApplyRoomSettingsAction --> ActionCoordinator
SendWhisperAction --> ActionCoordinator
getMapObservation() clones only the map of the configured room when the current
connection is synchronized. World map saves reject disconnected/stale state and
never substitute Mongo data. setRoomCreationDefinition() changes only the
template used after CannotFindRoom; clearing it disables creation after a
failed database reload. Neither API emits a BC mutation or changes observed
snapshots, so saving a map has a Mongo persistence result, not a BC acknowledgement.
Built-in features are statically assembled by src/app/start.ts; src/index.ts is only the process entrypoint. Plug-ins are not dynamically loaded from files or packages. The host gives each feature a lifecycle, while feature code continues to use the existing action/adapter/contract APIs. The composition root injects BcApiPlane into CommunicationPlugin for the OnlineFriends read-back; other plug-ins continue using the existing domain actions and client events.
classDiagram
direction LR
class BotPlugin {
<<lifecycle contract>>
+id: string
+start()
+stop()
}
class BotPluginHost {
-plugins: BotPlugin[]
-startedPlugins: BotPlugin[]
+start() Promise~void~
+stop() Promise~void~
}
class BotCommandParser {
<<command registry>>
+register(name, description, handler, helpDescriptors?) Dispose
+handleMessage(input: BotCommandInput) Promise~boolean~
}
class BcCommandParser {
<<BC protocol adapter>>
+handleMessage(message: BcChatRoomMessage) Promise~boolean~
}
class KeyedDeadlines {
<<transport-neutral scheduler>>
+schedule(key, delayMs, callback)
+cancel(key)
+has(key) boolean
+clear()
}
class BotCommandContext {
+command
+args
+alias?
}
class CommandAliasResolver {
<<single-pass fallback>>
+resolve(parts, commandPaths, descriptors)
+availableAliases(commandPaths, descriptors)
}
class GameConfigRuntime {
+getForRoom(roomKey, key)
+current()
}
class CommandHelpDescriptor {
<<reviewed capability metadata>>
+id
+commandPath
+featureId
+scopePath
+summary
+access policy
+feature availability policy
+capability availability policy
}
class CommandHelpCatalog {
<<disposable live catalog>>
+register(descriptors) Dispose
+snapshot() CommandHelpSnapshot
+validate()
}
class ScopedHelpService {
<<read-only private help>>
+request(sender, scope, channel)
}
class HelpContextPort {
<<observed requester context>>
+current(sender, channel)
+isCurrent(context)
}
class CommandSuite {
<<command registration contract>>
+register() Dispose
}
class CommandRouterPlugin {
<<transport bridge>>
+start() void
+stop() void
}
class AppearancePlugin {
<<qualification feature plugin>>
+start() void
+stop() void
}
class AppearanceCommandSuite {
<<qualification commands>>
+register() Dispose
}
class DoorFeaturePlugin {
<<feature plugin>>
-runtime: VeratownMapRuntime
-admin: DoorAdminService
+start() Promise~void~
+stop() Promise~void~
}
class LocationEntryBehaviorPlugin {
<<database-driven feature>>
+start() Promise~void~
+stop() Promise~void~
}
class LocationEntryBehaviorService {
+apply(memberNumber) Promise~LocationEntryBehaviorResult~
}
class LocationEntryBehaviorRepository {
+loadConfig() Promise~LocationEntryBehaviorConfig~
+loadItemSet(key) Promise~AppearanceItemSet~
}
class DoorCommandSuite {
<<runtime commands>>
+register() Dispose
}
class DoorAdminCommandSuite {
<<admin commands>>
+register() Dispose
}
class CommunicationPlugin {
<<feature plugin>>
-friendReciprocation: FriendReciprocationAction
-motd: MotdService
-apiPlane: BcApiPlane
+start() void
+stop() void
}
class CommunicationTestCommandSuite {
<<diagnostic command>>
+register() Dispose
}
class PrototypeStatusPlugin {
<<command plugin>>
+start() void
+stop() void
}
class PrototypeCommandSuite {
<<status command>>
+register() Dispose
}
class HotSeatPlugin {
<<room-scoped game plugin>>
+start() void
+stop() void
}
class HotSeatCommandSuite {
<<private-only game commands>>
+register() Dispose
}
class BcClient
class BcApiPlane
class CharacterCommunicationApi
class ActionCoordinator
class MongoActionJournal
class VeratownMapRuntime
class MapLocationEventBus {
<<core event contract>>
+locationEntered(memberNumber, location, firstObservation, cause?)
}
class LocationEntryFeatureHandler {
<<generic core lifecycle and de-duplication>>
+start()
+stop()
}
class TileGeometry {
<<transport-independent core contract>>
+point
+inclusive rectangle
+canonical tile row spans
+containsTile(geometry, position) boolean
}
class LocationAdministration {
<<core admin port>>
+find(key) location with optional region
+create(draft with optional region)
+update(key, edit with region/null)
}
class HelperTaskExecutor
class PositionObservations {
<<read-only core contract>>
+getLatestObservation(memberNumber)
+subscribe(listener) Dispose
}
class MapPositionObservationBus {
<<bounded five-second process cache>>
+publish(observation)
+forget(memberNumber)
+clear()
}
class PositionObservationSession {
<<optional transport lifecycle>>
+start() Promise~void~
+stop() Promise~void~
}
class MapTeleportAction {
+teleport(roomKey, locationKey) Promise~ActionOutcome~
+stop() Promise~void~
}
class AppearanceItemSetService
class BcAppearanceAction
class DoorAdminService
class SetMapObjectAction
class FriendReciprocationAction
class MotdService
class MapRuntimeRepository {
<<Mongo module>>
+loadMapRuntimeConfiguration(db, roomKey) Promise~Configuration~
+watchMapRuntimeConfiguration(db, reload, onError) Dispose
}
class EconomyPort {
<<core shared capability>>
+balance(memberNumber)
+grantOnce(operationId, memberNumber, amount, reason, maximumAvailableCredits?)
+reserve(operationId, memberNumber, amount, sessionId)
+settle(operationId, reservationId, result)
+refund(operationId, reservationId)
+forfeitAvailable(operationId, memberNumber)
}
class EconomyService
class EconomyRepository {
<<feature persistence port>>
+initialize()
+applyTransaction()
}
class MongoEconomyRepository
class CasinoFeaturePlugin
class AnonymousWhispererPlugin
class WhispererCommandSuite
class WhispererSubmissionHandler
class WhispererService
class HotSeatPlugin
class HotSeatCommandSuite
class HotSeatService
EconomyService ..|> EconomyPort
EconomyService --> EconomyRepository
MongoEconomyRepository ..|> EconomyRepository
CasinoFeaturePlugin --> EconomyPort : injected by start.ts
AnonymousWhispererPlugin ..|> BotPlugin
AnonymousWhispererPlugin --> WhispererCommandSuite : owns
AnonymousWhispererPlugin --> WhispererSubmissionHandler : owns redacted input
AnonymousWhispererPlugin --> WhispererService
AnonymousWhispererPlugin --> BcClient : message and presence lifecycle
WhispererService --> EconomyPort : injected by start.ts
WhispererService --> CharacterCommunicationApi : injected communication port
HotSeatPlugin ..|> BotPlugin
HotSeatPlugin --> EconomyPort : injected by start.ts
HotSeatPlugin --> HotSeatService
HotSeatPlugin --> HotSeatCommandSuite
HotSeatCommandSuite --> BotCommandParser
BotPluginHost o-- BotPlugin : owns lifecycle
DoorFeaturePlugin ..|> BotPlugin
VeratownMapRuntime --> KeyedDeadlines : safe BC map deadlines
LocationEntryBehaviorPlugin ..|> BotPlugin
CommunicationPlugin ..|> BotPlugin
PrototypeStatusPlugin ..|> BotPlugin
AppearancePlugin ..|> BotPlugin
HotSeatPlugin ..|> BotPlugin
CommandRouterPlugin ..|> BotPlugin
DoorCommandSuite ..|> CommandSuite
DoorAdminCommandSuite ..|> CommandSuite
CommunicationTestCommandSuite ..|> CommandSuite
PrototypeCommandSuite ..|> CommandSuite
AppearanceCommandSuite ..|> CommandSuite
WhispererCommandSuite ..|> CommandSuite
WhispererCommandSuite --> WhispererService : game actions
DoorFeaturePlugin --> DoorCommandSuite : owns
DoorFeaturePlugin --> DoorAdminCommandSuite : owns
CommunicationPlugin --> CommunicationTestCommandSuite : owns
PrototypeStatusPlugin --> PrototypeCommandSuite : owns
AppearancePlugin --> AppearanceCommandSuite : registers qualification commands
WhispererCommandSuite --> BotCommandParser : registers game commands
WhispererSubmissionHandler --> CharacterCommunicationApi : private replies
WhispererSubmissionHandler --> WhispererService : consented ephemeral input
DoorCommandSuite --> BotCommandParser : registers door/code
DoorAdminCommandSuite --> BotCommandParser : registers admin subcommands
CommunicationTestCommandSuite --> BotCommandParser : registers testcomms
PrototypeCommandSuite --> BotCommandParser : registers prototype
AppearanceCommandSuite --> BotCommandParser : registers qualification commands
CommandRouterPlugin --> BcCommandParser : passes BC messages
BcCommandParser --|> BotCommandParser : dispatches normalized inputs
CommandRouterPlugin --> BcClient : subscribes message
BotCommandParser --> CommandHelpCatalog : registers/disposes descriptors
BotCommandParser --> CommandAliasResolver : no-match fallback
BotCommandParser --> ScopedHelpService : global/scoped help
CommandAliasResolver --> GameConfigRuntime : reads room aliases by revision
CommandAliasResolver --> CommandHelpCatalog : validates live targets
ScopedHelpService --> CommandHelpCatalog : consistent snapshot
ScopedHelpService --> HelpContextPort : membership/permission freshness
ScopedHelpService --> CharacterCommunicationApi : private replies
DoorFeaturePlugin --> BcClient : map/room events
DoorFeaturePlugin --> VeratownMapRuntime
VeratownMapRuntime --> TileGeometry : exact location and door membership
HelperTaskExecutor --> TileGeometry : same location membership
LocationAdministration --> TileGeometry : region contract
DoorFeaturePlugin --> MapLocationEventBus : publishes transitions
LocationEntryFeatureHandler --> MapLocationEventBus : subscribes to entries
DoorFeaturePlugin --> MapPositionObservationBus : publishes BC coordinates/invalidation
MapPositionObservationBus ..|> PositionObservations
MapTeleportAction --> PositionObservations : optional fresh shared evidence
MapTeleportAction --> ActionCoordinator
MapTeleportAction --> BcClient : guarded position update
PositionObservationSession --> MapPositionObservationBus : scoped live stream
LocationEntryBehaviorPlugin --> MapLocationEventBus : subscribes; ignores snapshots
LocationEntryBehaviorPlugin --> LocationEntryBehaviorService
LocationEntryBehaviorPlugin --> LocationEntryBehaviorRepository
LocationEntryBehaviorService --> LocationEntryBehaviorRepository : loads current revisions
LocationEntryBehaviorService --> AppearanceItemSetService : applies selected standard set
AppearanceItemSetService --> BcAppearanceAction : observed ordered mutations
AppearancePlugin --> AppearanceItemSetService : applies itemsets
AppearancePlugin --> BcAppearanceAction : single appearance mutations
DoorAdminCommandSuite --> DoorAdminService
DoorFeaturePlugin --> SetMapObjectAction
DoorFeaturePlugin --> MapRuntimeRepository : reloads configuration
CommunicationPlugin --> BcClient : message/presence events
CommunicationPlugin --> BcApiPlane : OnlineFriends query
CommunicationPlugin --> FriendReciprocationAction
CommunicationPlugin --> MotdService
CommunicationTestCommandSuite --> CharacterCommunicationApi
PrototypeCommandSuite --> CharacterCommunicationApi
SetMapObjectAction --> ActionCoordinator
FriendReciprocationAction --> ActionCoordinator
MotdService --> CharacterCommunicationApi
ActionCoordinator --> MongoActionJournal
| Interface/type | Responsibility |
|---|---|
ClientToServerEvents |
Pinned BC declarations for all 26 client-originated packet names and payloads. |
ServerToClientEvents |
Pinned BC declarations for all 30 server-originated event names and payloads. |
BcIncomingPayload<Event> / BcOutgoingPayload<Event> |
Type-level lookup of the exact payload associated with a BC event name. |
BcIncomingEventName / BcOutgoingEventName |
Keys of the pinned server-to-client and client-to-server event maps. |
BcClient |
Sole production Socket.IO owner; manages connection/readiness, the global send queue, typed packet dispatch, and BC observations. |
BcApiPlane |
Typed adapter for outbound contracts, raw inbound subscriptions, single-response requests, payload-summary redaction, and lifecycle recording. The app injects it into CommunicationPlugin for OnlineFriends read-back; dedicated feature actions remain in use for other operations. |
BC_OUTGOING_CONTRACTS |
Exhaustive 26-event contract catalog describing acknowledgement model, candidate responses, direct responses, correlation, and limitations. |
BotPlugin / BotPluginHost |
Lifecycle contract and static host; starts features in order, rolls back partial startup, and stops started features in reverse order. |
CommandSuite |
Feature command-registration contract; register() returns a disposer owned by its plug-in lifecycle. |
BotCommandParser |
Transport-neutral longest-prefix registry for validated BotCommandInput; registered paths win before single-pass alias resolution and scoped help. |
BcCommandParser |
BC-specific Hidden /bot and ! Chat/Emote/targeted-Whisper validation and normalization; raw BC content does not enter the core registry. |
CommandAliasResolver |
Core-only guarded parser for room-scoped aliases; reads command.aliases by game-config revision, rejects malformed/sensitive aliases, and ignores targets no longer present in the live help catalog. |
CommandHelpDescriptor / CommandHelpCatalog |
Reviewed feature-owned capability metadata with explicit public/admin access, feature-wide enabled-state, and per-capability availability rules; registrations share command disposer lifetimes and reject duplicate or reserved paths. |
ScopedHelpService / HelpContextPort |
Recomputes global/scoped help from server-observed requester context, hides all descriptors for disabled features, denies stale/unknown scopes, bounds and rate-limits private output, and rechecks context/catalog revisions before each chunk. |
CommandRouterPlugin |
Connects BC message events to the command registry and owns that event subscription’s disposal. |
DoorFeaturePlugin |
Owns keypad/map subscriptions, door runtime, admin service, configuration watcher, and door command-suite lifecycles. |
DoorCommandSuite |
Registers runtime door/door open/door close and code commands. |
DoorAdminCommandSuite |
Registers door CRUD, group, and access command paths and returns admin-service results privately. |
TileGeometry / MapRegionDefinition |
Core-only exact tile membership for points, legacy inclusive rectangles and canonical tile row spans; map schemas validate persisted forms at the feature boundary. |
LocationAdministration |
Core command port for room-admin location CRUD, including optional region definitions and explicit region clearing. |
CommunicationPlugin |
Owns friend-reciprocation and MOTD event subscriptions plus the communication-test command-suite lifecycle. |
EconomyPort / EconomyService / MongoEconomyRepository |
Shared credits capability: core contract, application policy, and economy-owned Mongo account/ledger persistence. The composition root constructs one service and injects it into economy consumers; it is not a plug-in or BC API-plane dependency. |
CommunicationTestCommandSuite |
Validates admin test requests, invokes journaled channel actions, and returns per-channel outcomes privately. |
PrototypeStatusPlugin / PrototypeCommandSuite |
Plug-in wrapper and suite for the prototype status command. |
BcRoomMapData / BcRoomSettings |
BC-derived room map and full room-settings types. |
BcMapPositionUpdate / BcRoomAdminRequest / BcRoomPropertiesUpdate / BcRoomUpdateResponse |
BC-derived payloads for map, room-admin updates, room-property sync, and update result signals. |
BcAccountUpdate / BcAccountBeepRequest |
BC-derived account-update and beep request payloads. |
BcOnlineFriend / BcOnlineFriendsQueryResponse |
Typed account-query result entries used for server read-back; this query includes online friends only. |
BcCharacterMapData / BcAppearanceItem |
Validated feature-facing character position and appearance projections. |
BcMsgIdDictionaryEntry |
BC message localization dictionary entry used by chat packets. |
BcClientConfig |
Connection origin/server, dedicated account credentials, room name, and optional room-creation definition. Credentials are never journaled. |
BcSocketLike |
Typed Socket.IO transport port owned by BcClient; it keeps features away from raw socket construction and the global rate-limit queue. |
BcRoomDefinition |
Validated BC room settings used by join-or-create. |
BcRoomSnapshot |
Latest BC-observed room settings/map and room roster; it is not proof that this client caused those values. |
BcCharacterSnapshot |
Feature-facing character view including member number, appearance, optional map state, and friend-list snapshot. |
BcMapPosition |
BC room-map coordinate used by location and map-object actions. |
BcChatRoomMessage |
BC chat/hidden/emote/etc. packet payload. Generic action journaling stores its summary, not full message contents. |
BcEventContract |
Per-outbound-event model, candidate response events, optional progress/failure event lists, direct-response subset, correlation hint, and protocol limitation. The catalog is exhaustive over keyof ClientToServerEvents; progress events cannot complete requests, and declared failure events produce failed outcomes. |
BcAcknowledgementModel |
The four completion classes: direct response, conditional response, state observation, and dispatch only. |
BcActionMetadata |
Safe operation name, target identity, and caller-provided desired-state summary. Do not place passwords, full chat text, appearance payloads, or private account data in metadata. |
BcRequestSpec<Request, Response> |
Typed outbound packet, one selected inbound event, matcher, timeout, optional current-state getter, optional success/failure classifier, and safe metadata. Progress events are rejected as completion events; a classifier handles result variants sharing one event. |
BcRequestResult<Response> |
Action lifecycle outcome plus the matched response payload when observed before timeout. |
ObservationChannel<T> |
Supplies the latest known state and a disposable subscription to future observations for ActionCoordinator. BcApiPlane.request() adapts its optional current() provider to this channel; without one, the channel has no cached value. |
ObservedActionRequest<T> |
Desired-state action specification; completes on a matching observation or timeout. Optional AbortSignal cancels waits; before dispatch it prevents emission, and after dispatch it remains timed_out_unobserved unless matching evidence already arrived. Optional evidence projectors add safe capture/provenance details without changing outcome semantics. |
DispatchOnlyActionRequest |
One-way action specification; may optionally run a separate read-back verifier. |
ActionOutcome |
Stable operation ID, lifecycle status, timing, target, desired-state summary, evidence, and optional failure detail. |
ActionTransition |
One timestamped lifecycle transition persisted to the journal. |
ActionJournal |
Persistence port for action start and status transitions. |
ActionJournalDocument |
Full action record with desired state, target, lifecycle history, and 30-day expiry. |
AttemptResult<T> |
Discriminated success/error result used at adapter boundaries instead of scattered exception handling. |
ActionCoordinator |
Enforces lifecycle ordering, separates dispatch from acknowledgement/observation, and persists each transition through ActionJournal. |
MongoActionJournal |
Mongo adapter writing prototypeActions; records use a TTL index for 30-day retention. |
CharacterCommunicationApi |
Feature API for room chat, redacted one-shot chat, emotes, action/activity/status messages, targeted hidden messages, whispers/MOTD, and beeps; each delegates to a journaled action. |
FriendReciprocationAction |
Consumes incoming hidden friend-request messages, dispatches reciprocal account/chat packets, and optionally verifies through OnlineFriends read-back. |
MotdService |
Deduplicates MOTD whispers per entrant and connection state. |
SendRoomMessageAction |
Journaled Chat, Emote, Action, Activity, Hidden, and Status ChatRoomChat variants, with optional target and localization dictionary. The sensitive-text variant replaces transport exceptions before journaling. |
SendWhisperAction / SendBeepAction |
Journal dispatch attempts and return dispatched_unconfirmed; BC exposes no sender-side delivery receipt for these packets. |
SetMapObjectAction |
Serializes map writes; resolves style plus optional BC Type to the exact object ID and accepts ChatRoomUpdateResponse: "Updated" as weak acknowledgement or a matching room snapshot as observed state. Neither provides request-ID-level causality. |
ApplyRoomSettingsAction |
Serializes room-setting updates with map-admin updates and accepts only a matching inbound room sync as completion; a successful ChatRoomUpdateResponse is not sufficient. |
BcClient remains the only owner of Socket.IO and the global 14-packet/1.2-second send queue. Its generic dispatchPacket and onServerEvent methods are the narrow typed transport ports. The API plane adds policy, serialization, metadata redaction, and lifecycle semantics; it does not open a second connection or bypass the queue.
Feature plug-ins use the shared client, typed action APIs, repositories, and lifecycle journal provided by the composition root. They must dispose every event subscription and command registration in stop(). New plug-ins are statically imported and listed in src/app/start.ts; dynamic code loading is intentionally not supported. The composition root constructs BcApiPlane once and injects it into CommunicationPlugin for the OnlineFriends read-back. Other plug-ins continue to use domain-specific actions and client events.
Do not create a second socket or duplicate the client’s connection lifecycle: BcClient.start() owns login, room join-or-create, and the corresponding state synchronization. The plane’s generic API complements this lifecycle; it does not replace it.
Existing feature actions use ActionCoordinator and client methods that apply domain-specific checks or transformations. BcApiPlane.dispatch() also journals through that coordinator. Calling dispatch() from inside an already-journaled action would create a second lifecycle record for one operation. Keep dedicated actions as the lifecycle owner, or redesign the transport boundary so exactly one layer journals each operation. Do not replace methods such as sendWhisper, sendBeep, or updateMapObject with generic packet dispatch without preserving their validation, formatting, and friend/map-state safeguards.
SetMapObjectAction uses the guarded BcClient.updateMapObject() method, which
requires the synchronized configured room and current room-admin membership.
ApplyRoomSettingsAction uses the guarded BcClient.updateRoomSettings() method
and the same serialization queue as map-admin writes. It preserves the inbound
map snapshot while sending settings through the normal BC queue. The action
treats BC’s update response as a rejection signal only; it completes as
observed only after a matching inbound room-settings sync. The supervisor runs
this reconciliation after the initial room sync, using the latest
prototypeRoomConfig revision. Admin-Map does not open a BC connection or
mutate the live room.
Friend reciprocity intentionally creates two related records: the feature action records the friend-add/notification workflow, while the plane records the distinct OnlineFriends read-back request. The client still owns FriendList construction and updates its server-observed friend cache when that response arrives. Avoid issuing the same AccountQuery concurrently through another request helper; requests sharing AccountQueryResult are serialized by the plane.
request() accepts one selected response event and serializes concurrent requests waiting for that same event. It rejects events listed as progress, such as LoginQueue, as completion events. Events listed as terminal failures, such as ForceDisconnect for login, produce a failed action outcome. For success/failure variants on one event, provide classifyResponse; its matcher must accept the relevant variants so the classifier can distinguish them. A caller may provide current() for that response type: if the current value matches and is not classified as a failure, the plane records already_satisfied and does not dispatch. Without a provider, no cached current-state check is performed. Multi-step login and room readiness remain owned by BcClient, not by a single request() call.
Use this when BC declares no reliable result, such as AccountUpdate, ChatRoomChat, AccountBeep, or AccountDifficulty.
const outcome = await api.dispatch(
"AccountUpdate",
{ FriendList: [42] },
{
action: "bc.account.friend.add",
target: { memberNumber: 42 },
desired: { friend: true },
},
);
A successful dispatch() resolves to dispatched_unconfirmed: the local Socket.IO queue emitted the packet. It does not mean BC stored the change. The API plane records only a redacted payload summary; credentials, chat text, and account values are not copied into action metadata.
Use request() when the protocol defines a terminal response event. Its matcher must distinguish the intended response variant. Requests waiting on the same response event are serialized because BC packets do not include a generic request ID. The plane rejects catalogued progress events as completion events and records catalogued or caller-classified failures as failed.
const result = await api.request({
action: "bc.account.online-friends.query",
requestEvent: "AccountQuery",
payload: { Query: "OnlineFriends" },
responseEvent: "AccountQueryResult",
matches: (response) => response.Query === "OnlineFriends",
timeoutMs: 8_000,
});
A matched event listed in directResponseEvents returns outcome.status === "acknowledged", unless it is classified as a failure, which returns failed. A matching non-direct candidate event returns observed unless classified as a failure. Neither status proves causation because BC supplies no generic request ID. The typed response is included when received; a matching current value supplied by current() is returned with already_satisfied; timeout returns timed_out_unobserved.
Subscribe to any declared raw BC server event with the exact payload type. This is distinct from the higher-level BcClient events such as ready, mapPosition, and message. The returned disposer must be called when the feature is disabled or the connection owner shuts down.
const unsubscribe = api.subscribe("ChatRoomSyncPose", (pose) => {
console.info(pose.MemberNumber, pose.Pose);
});
Use a dedicated action and an observed-state predicate for effects whose result appears later as a room/character sync. Matching inbound BC state is sufficient operational evidence even though its correlation is loose: no causal proof, independent helper, or second confirmation is required. Preserve target/desired-state matching and connection/freshness guards; dispatch alone remains unconfirmed and room observation does not imply account persistence. SetMapObjectAction serializes room map updates and listens for both the uncorrelated ChatRoomUpdateResponse: "Updated" result and room syncs. The former returns acknowledged; the latter returns observed with temporal_match_only evidence. Door expiry begins after either result; timeout begins no timer and requires reconciliation before retrying.
| Model | API surface | Completion meaning | Examples |
|---|---|---|---|
direct_response |
request() |
Matching BC response event observed. | Login, room join/create, search, AccountQuery, item permission. |
conditional_response |
request() with a matcher/classifier, or subscribe() |
A matching terminal response arrived; caller classifies result variants sharing the event. Contract-listed progress events cannot be selected as completion. | Ownership/lovership, room admin, game messages. |
state_observation |
A dedicated observed action, or request() with a sync matcher and optional current-state getter |
Desired state appeared in a BC sync, or was already present in the supplied current value; causation may remain uncertain. | Character, pose, expression, item, map changes. |
dispatch_only |
dispatch() |
Local queue emitted the packet; server receipt, delivery, or persistence is unknown. | AccountUpdate, chat, beep, leave, difficulty. |
Implemented restrictions include no synthetic success for missing events, no generic retry of non-idempotent actions, no request() for contracts classified dispatch_only, rejection of listed progress events as completion, terminal failure classification, one pending operation per selected response event, optional current-state short-circuiting, and no secrets in journal summaries. The plane is integrated for the communication feature’s OnlineFriends read-back; other generic requests should be added only with an explicit response classifier where needed and a clear single-owner journaling design. Full packet pairing and caveats are listed in BC-SOCKET-API-ACKNOWLEDGEMENTS.md.