This document describes the implemented runtime as it exists in
src/app/start.ts, not a proposed target design. Feature-level behavior and BC
packet acknowledgement details remain in their respective documents.
Feature configuration ownership, startup defaults, and bot command sequences
are catalogued in FEATURE-CONFIGURATION.md.
The optional web administration service is a separate operator-script composition root, not another bot plugin. It has no BC connection. It selects the same effective stored map as missing-room creation, validates configuration through feature-owned contracts, and exposes explicitly enabled, administrator-only configuration writes with concurrency checks and durable sanitized audits. Existing feature reload paths remain responsible for runtime adoption. A persisted web write is neither a BC acknowledgement nor an observation; live-map capture and live actions remain owned by the bot.
Administrator recovery reuses these validated mutations inside snapshot-read
MongoDB transactions, with optimistic versions and paired audits. Configuration
snapshots exclude secrets and player runtime data; selected-record restores never
delete unrelated records. Heartbeat reports carry an optional logical roomKey
for the web projection; legacy unscoped reports are omitted. Neither a recent
heartbeat nor a successful restore proves feature adoption or live BC state.
The composition root owns a bounded ConfigurationAdoption tracker and includes
its optional configurationAdoption projection in every heartbeat write.
scripts/admin-web/recovery.ts validates this field and supplies explicit unknown
entries for legacy/malformed/missing reports. Reports remain room-scoped by the
existing heartbeat roomKey; they never compare persisted documents to guess
adoption. Each instance is displayed separately with its process freshness,
including stale, stopped, disconnected, and clock-skewed reports.
| Consumer identifier | Actual reporting boundary | Revision meaning |
|---|---|---|
shared-game-config |
GameConfigRuntime.apply() installs the in-memory snapshot |
Shared document revision, not downstream game/session adoption |
doors |
DoorFeaturePlugin.start() completes, or its shared admin/change-stream reload replaces running door/location configuration |
Unknown (null): the aggregate has no authoritative revision |
room-creation-template |
The serialized reload in start.ts loads the latest prototypeRoomConfig settings (legacy veratownRooms fallback) and effective map before calling BcClient.setRoomCreationDefinition() |
Unknown (null); creation-template loading is separate from the supervisor’s journaled live-room settings reconciliation |
command-aliases |
CommandAliasResolver validates/replaces its lazy cache during alias/help lookup |
Shared document revision used by that cache |
casino |
CasinoGameService creates a new slots or roulette session with the validated rules |
Shared revision adopted by that new session; older rounds keep their own saved rules |
shop |
ShopService command access checks or ShopFeaturePlugin location interactions configure access policy |
Shared revision used for that interaction, not all quotes/favors |
location-entry-behavior |
Location messaging/disabled gating in LocationEntryBehaviorPlugin, or validated itemset selection in LocationEntryBehaviorService |
Shared policy revision, not an aggregate of referenced itemsets |
shower |
ShowerService evaluates a real entry using settings and selects that session’s timing |
Shower-settings revision, not songs or existing sessions |
truth-dare, hot-seat, dilemma, anonymous-whisperer |
A new active game session is successfully persisted with its dedicated config snapshot | Per-session config revision; not the shared game-config revision, default provenance, or every active session |
staged-table |
A new multiplayer lobby or solo session is successfully persisted with its dedicated config snapshot | Per-session config revision; configuration provenance remains unknown |
trick-or-treat |
A new route starts from validated settings and a ready multiplayer roster (or a player-started solo round); completed eligible rounds settle opt-in consequence progression | Per-session config revision; map entries require observed position_update; appearance consequences require observed BC state |
appearance |
AppearanceItemSetService validates an enabled itemset for a real application attempt |
No aggregate revision: itemset revisions belong to individual sets; BC outcome is separate |
helper-bots |
A helper worker passes identity preparation, loads polling settings, and becomes ready | No settings revision or default provenance is currently available; supervisor task handling remains uninstrumented |
Shared snapshot changes mark the supervisor’s alias, casino, shop and location
entry behavior consumers
pending_next_interaction; persistence alone does not mark them loaded. Casino
adoption waits for a newly created game session (not startup validation, help,
entry grants, policy reads, or an existing round). Shop waits for a command or
location interaction applying its access policy; disabled policies are applications
too. Location entry behavior waits for policy-driven messaging, disabled gating,
or itemset selection. Successful configuration application is distinct from a subsequent
BC action’s success. If an older in-flight interaction completes while a newer
revision is pending, its receipt does not consume that newer pending revision.
Shower has no settings watcher, so it reports no speculative pending revision:
real entries apply settings, while enabled-only mid-session checks never claim
adoption of new timing or songs. Removed
alias settings are applied as a disabled cache on lookup. Invalid settings
fail closed and report configuration_invalid. Helper-only processes do not
claim adoption by supervisor-only consumers. Party-game session receipts are
created only after a new session uses its configuration; they do not infer that
revision one means a seeded default, nor claim coverage of all active sessions.
Appearance reports itemset selection for an application attempt, not an
observed BC result. The admin runtime view compares only revisions sharing the
same process-local GameConfigRuntime revision space; it cannot establish that
the cache matches the persisted document. Independent game, itemset, and helper
settings remain explicitly non-comparable or unknown.
Receipts contain last-loaded revision/time, optional pending revision, and only fixed reload-error identifiers/timestamps. They never contain configuration, error messages, access codes, or raw document identifiers. Reload failures retain the last-successful receipt, not a claim that it is still active: shared configuration and failed door reads retain their prior runtime configuration; invalid aliases disable the cache and failed template reloads disable room creation as before. A successful subsequent load clears the reported error. Repeated lookups/unchanged shared revisions do not advance the last-loaded time.
Helpers, appearance/itemsets, and uninstrumented party-game consumers remain
explicitly unknown. Staged Table reports the revision used by each new lobby
or solo session, without inferring whether it came from startup defaults.
Casino receipts describe the most recent newly created session, not
every concurrent session. Shop quotes/favors, location entry itemsets, shower songs and
pre-existing shower timing are not covered by their feature’s receipt. A failed
configuration read/validation preserves last-loaded details, but does not fall
back to those details to authorize a new interaction. No receipt proves BC
dispatch, acknowledgement, observation, or account persistence.
flowchart TD
Entry[src/index.ts] --> Main[src/app/start.ts<br/>composition root]
Main --> Config[src/app/config.ts<br/>Zod bootstrap + effective config]
Main --> PluginHost[BotPluginHost]
Main --> RuntimeRole[Validated runtime role]
Main --> BotProfiles[MongoBotInstanceProfileRepository]
BotProfiles --> BotInstances[(prototypeBotInstances)]
BotProfiles --> BotInstanceRevisions[(prototypeBotInstanceRevisions)]
Main --> BcClient[BcClient<br/>single Socket.IO connection + send queue]
Main --> ApiPlane[BcApiPlane<br/>typed packet contracts/read-backs]
Main --> Coordinator[ActionCoordinator]
Main --> HelpContext[HelpContextPort<br/>room membership + revision snapshot]
Main --> AliasResolver[CommandAliasResolver<br/>revision-cached room config]
Coordinator --> Journal[MongoActionJournal]
Main --> Mongo[(MongoDB<br/>target-specific database)]
BotProfiles --> Mongo
Main --> MutationLease[MongoCharacterMutationLease]
PluginHost --> Doors[DoorFeaturePlugin]
PluginHost --> LocationMessages[LocationEntryMessagePlugin]
PluginHost --> LocationAppearance[LocationAppearanceTriggerPlugin]
PluginHost --> EmergencyRelease[EmergencyReleasePlugin]
PluginHost --> LocationEntryBehavior[LocationEntryBehaviorPlugin]
PluginHost --> SafetyStop[SafetyStopPlugin]
PluginHost --> WorldAdmin[WorldAdminPlugin]
PluginHost --> Communication[CommunicationPlugin]
PluginHost --> Status[PrototypeStatusPlugin]
PluginHost --> Appearance[AppearancePlugin]
PluginHost --> Whisperer[AnonymousWhispererPlugin]
PluginHost --> RoulettePresentation[RoulettePresentationRouter]
PluginHost --> Casino[CasinoFeaturePlugin]
PluginHost --> Shop[ShopFeaturePlugin]
PluginHost --> TruthDare[TruthDarePlugin]
PluginHost --> HotSeat[HotSeatPlugin<br/>disabled by room config]
PluginHost --> StagedTable[StagedTablePlugin<br/>disabled by room config]
PluginHost --> Dilemma[DilemmaPlugin]
PluginHost --> Helpers[HelperBotsPlugin<br/>supervisor or worker mode]
PluginHost --> Abcl[AbclFeaturePlugin<br/>supervisor only]
PluginHost --> Shower[ShowerFeaturePlugin<br/>opt-in]
Main --> MapTeleport[MapTeleportAction]
EmergencyRelease --> MapTeleport
Main --> PositionBus[MapPositionObservationBus<br/>five-second bounded cache]
Main --> AppearanceBus[SharedAppearanceObservationBus<br/>per-observer appearance cache]
Main --> ShowerRepository[MongoShowerRepository]
Doors --> PositionBus
Helpers --> ObservationSession[Optional supervisor/helper HTTP session]
Abcl --> AbclHelperTasks[Typed ABCL helper tasks]
Abcl --> AbclConsent[MongoAbclParticipantRepository]
AbclHelperTasks --> Helpers
Helpers --> AbclState[Short-lived ABCL decisions and sanitized signals]
AbclState --> Abcl
MutationLease --> Mongo
Helpers --> TaskNotifier[HelperTaskNotifier<br/>hint after durable enqueue]
TaskNotifier --> ObservationSession
ObservationSession --> TaskWakeups[HelperTaskWakeups<br/>coalesced queue check]
TaskWakeups --> Helpers
ObservationSession <--> PrivateStream[Railway private WireGuard stream]
PrivateStream <--> HelperPositionBus[Helper-local MapPositionObservationBus]
AppearanceBus <--> PrivateStream
MapTeleport --> HelperPositionBus
PluginHost --> Router[CommandRouterPlugin]
Main --> Economy[EconomyService<br/>shared application capability]
Economy --> EconomyPort[EconomyPort]
Economy --> EconomyRepository[MongoEconomyRepository]
EconomyRepository --> Mongo
Casino --> EconomyPort
Main --> WhispererRepository[MongoWhispererRepository]
WhispererRepository --> Mongo
Main --> TruthDareService[TruthDareService]
Main --> TruthDareRepository[MongoTruthDareRepository]
Main --> StagedTableRepository[MongoStagedTableRepository]
Main --> StagedTableService[StagedTableService]
StagedTable --> StagedTableService
StagedTable --> SafetyStop
StagedTableService --> StagedTableRepository
StagedTableRepository --> Mongo
TruthDare --> TruthDareService
TruthDareService --> EconomyPort
TruthDareService --> TruthDareRepository
TruthDareRepository --> PartyConfigs[("prototypePartyGameConfigs")]
TruthDareRepository --> TruthPrompts[("prototypeTruthDarePrompts")]
TruthDareRepository --> TruthSessions[("prototypeTruthDareSessions")]
HotSeat --> EconomyPort
HotSeat --> HotSeatService[HotSeatService]
HotSeatService --> HotSeatRepository[MongoHotSeatRepository]
HotSeatRepository --> Mongo
Main --> DilemmaRepository[MongoDilemmaRepository]
DilemmaRepository --> Mongo
Dilemma --> DilemmaRepository
Dilemma --> EconomyPort
Dilemma --> CommunicationApi
Shop --> LocationBus : observed shop-region membership
Shop --> Parser
ShopService --> CommunicationApi
Main --> ShopService[ShopService]
Main --> ShopRepository[MongoShopRepository]
Shop --> ShopService
ShopService --> EconomyPort
ShopService --> AppearanceApi
ShopService --> ShopRepository
ShopRepository --> Mongo
Main --> HelperTaskRepository[MongoHelperTaskRepository]
Helpers --> HelperTaskRepository
RoulettePresentation --> HelperTaskRepository
ShopService --> HelperTaskRepository : optional reviewed shop templates
HelperTaskRepository --> HelperTasks[(prototypeHelperTasks)]
HelperTaskRepository --> Mongo
Helpers --> CommunicationApi
Helpers --> MapTeleport : optional configured startup position
Casino --> RoulettePresentation : open, spin, and settled-result stages
Doors --> MapRuntime[VeratownMapRuntime]
Doors --> DoorAdminService[DoorAdminService]
DoorAdminService -->|persistence port| MapRepository[MapRepository]
Main --> MongoMapRepository[MongoMapRepository]
MongoMapRepository --> MapRepository
MongoMapRepository --> Mongo
Doors --> DoorCommands[Door command suites]
LocationMessages --> LocationBus
LocationAppearance --> LocationBus
LocationAppearance --> AppearanceItemSets[AppearanceItemSetService]
EmergencyRelease --> LocationBus
EmergencyRelease --> AppearanceItemSets
EmergencyRelease --> MapTeleport
EmergencyRelease --> Parser
EmergencyRelease --> CommunicationApi
LocationEntryBehavior --> LocationBus[MapLocationEventBus]
WorldAdmin --> WorldAdminCommands[World admin BC command adapter]
WorldAdmin --> GameConfigRuntime[GameConfigRuntime]
WorldAdmin --> MapWatch[Map/room configuration watchers]
MapWatch --> CreationTemplate[Validated highest-revision creation template]
CreationTemplate --> BcClient
WorldAdminCommands --> WorldServices[Location/Map/GameConfig admin services]
WorldAdminCommands --> ShowerAdminService[ShowerAdminService]
ShowerAdminService --> ShowerRepository
Shower --> ShowerService[ShowerService]
ShowerService --> ShowerRepository
ShowerService --> AppearanceApi
ShowerService --> CommunicationApi
ShowerService --> LocationBus
Doors --> LocationBus
Communication --> CommunicationApi[CharacterCommunicationApi]
Appearance --> AppearanceApi[BcAppearanceAction + itemsets]
Whisperer --> WhispererService[WhispererService]
WhispererService --> CommunicationApi
WhispererService --> EconomyPort
WhispererService --> WhispererRepository
DoorCommands --> Coordinator
Communication --> Coordinator
Appearance --> Coordinator
MapTeleport --> Coordinator
MapTeleport --> MapRepository
MapTeleport --> BcClient
Coordinator --> BcClient
ApiPlane --> BcClient
Router --> BcParser[BcCommandParser<br/>BC packet validation and normalization]
BcParser --> Parser[BotCommandParser<br/>transport-neutral registry]
Parser --> HelpCatalog[CommandHelpCatalog<br/>disposable descriptors]
Parser --> AliasResolver
Parser --> ScopedHelp[ScopedHelpService]
AliasResolver --> GameConfigRuntime
ScopedHelp --> HelpCatalog
ScopedHelp --> HelpContext
ScopedHelp --> CommunicationApi[CharacterCommunicationApi<br/>private reply]
HelpContext --> Snapshots
DoorCommands --> Parser
Communication --> Parser
Status --> Parser
Appearance --> Parser
Whisperer --> Parser
Dilemma --> Parser
BcClient <--> BC[Bondage Club Socket.IO server]
BcClient --> Snapshots[BC-synced room/player observations]
Snapshots --> Doors
Snapshots --> Communication
Snapshots --> Appearance
Snapshots --> Shower
Mongo --> FeatureConfig[Room/map, doors, groups,<br/>locations, itemsets, location-entry policy, game config,<br/>shower config/songs, command aliases, helper bots,<br/>Whisperer, Truth/Dare, Dilemma, Hot Seat, Staged Table,<br/>Trick-or-Treat and shop data]
FeatureConfig --> ShowerRepository
FeatureConfig --> Doors
FeatureConfig --> Appearance
FeatureConfig --> LocationEntryBehavior
FeatureConfig --> Casino
FeatureConfig --> Shop
FeatureConfig --> Whisperer
FeatureConfig --> HotSeat
FeatureConfig --> StagedTable
FeatureConfig --> HotSeatPrompts[prototypeHotSeatPrompts]
FeatureConfig --> Dilemma
FeatureConfig --> TruthDare
FeatureConfig --> Abcl
AbclConsent --> Mongo
AbclState --> Mongo
Coordinator --> Journal
Main --> RuntimeState[prototypeRuntime heartbeat]
Main --> DebugLog[prototypeDoorDebug, optional]
Main --> Mongo
BOT_INSTANCE_ID optionally selects a typed, versioned profile from the
database pinned by RUNTIME_TARGET. After the bootstrap connection is
established, the profile is validated and its role and non-secret runtime
settings override their environment counterparts. The role then selects the
existing static composition: supervisor is the default and includes the
game-config watcher and ABCL coordination, while helper starts the
game-config watcher and only the helper worker. There is no
dedicated ABCL runtime role. Without a profile, a legacy deployment with
HELPER_AGENT_ID and no explicit role still selects helper mode. Credentials,
Mongo connection/database selection, and observation bearer tokens remain
deployment settings. Profile updates are not hot-applied; heartbeats report the
actually loaded revision and role beside the latest desired revision and role.
ABCL remains inactive unless rooms.main["abcl.rules"], its enabled region,
and the registered abcl-capable helper are valid; missing, disabled, or
invalid ABCL configuration does not prevent the supervisor from starting.
Existing abcl profiles remain readable for migration but cannot start a
process.
src/core/logging/log-event.ts defines the process-independent logging port;
src/adapters/logging/json-line-logger.ts emits one schema-versioned JSON object
per line with timestamp, severity, deployment, service, replica, correlation,
and event fields. Startup phases use this logger. Fatal process records retain
the same envelope while preserving nested causes and redacting configured
secrets/URIs. Configuration validation reports field names and issue codes
without input values. Plugin startup/rollback errors retain plugin IDs and
original causes. See the
Railway diagnostic guide.
src/index.ts invokes main() through the process boundary in
src/app/process.ts. Startup failures, unhandled rejections, and uncaught
exceptions produce a synchronous, single-line JSON stderr record before
exiting with status 1. Fatal process events do not attempt asynchronous
cleanup or resume an unsafe runtime; normal signal shutdown is unchanged.main() parses the bootstrap values RUNTIME_TARGET, MONGODB_URI,
optional fixed-mapping MONGODB_DATABASE, and optional BOT_INSTANCE_ID,
then connects to the database fixed for that target:
veratown_prototype for production or veratown_prototype_test for testing.
When an instance ID is present, it loads and validates the matching profile
before parsing the effective runtime configuration; a missing, disabled,
deleted, malformed, or wrong-target profile fails startup.BcClient, BcApiPlane, ActionCoordinator, shared communication API,
appearance services, map-location event bus, shared economy service, and
feature repositories, including the Whisperer, Truth/Dare, Dilemma, Hot
Seat, Staged Table, Trick-or-Treat, shop, and ABCL participant/decision
repositories. The helper role uses its configured HELPER_AGENT_ID.BotPluginHost starts the statically listed plugins in this order:
game-config watcher, room-config watcher, shared safeword, doors/map, location-entry messages, delayed location appearance, emergency release, location-entry behavior, world administration,
communication, prototype status, appearance, Anonymous Whisperer, casino,
shop, Truth/Dare, Dilemma, Hot Seat, Staged Table, Trick-or-Treat, helper bots,
ABCL coordination, shower, then command routing. ABCL stops before helper
bots, so it fences and drains task creation before the helper worker stops.
Shower stops before helper bots and before BC closes, so graceful
compensation can still use local room updates and optional helper observations.
A helper process starts only the game-config watcher, room-config watcher,
and helper worker, without command routing or other feature subscriptions.
ABCL commands are registered with the supervisor’s shared command router;
the helper observes ABCL messages and executes assigned typed tasks.
Command handlers and
event consumers are therefore registered before the BC connection starts
routing incoming messages.(roomKey, revision) indexes for append-only world-map and
room-settings revisions.
Truth/Dare indexes enforce one active session per room, support bounded
recovery/config/prompt lookups, and expire only terminal sessions.
Legacy maps are excluded from revision uniqueness. It then idempotently seeds
the validated casino and shop baselines under
prototypeGameConfig.rooms.main["casino.rules"] and
prototypeGameConfig.rooms.main["shop.rules"] and the bet, slots, and
balance starter aliases under
prototypeGameConfig.rooms.main.command.aliases only when their keys are
absent, preserving existing configuration, and loads the resulting
game-config runtime snapshot before plugin startup.
The Whisperer
repository seeds its disabled room config and ten curated prompts without
overwriting existing documents. After index initialization, the composition
root calls seedDefaults("main", now) on the Truth/Dare, Hot Seat, Staged Table,
Dilemma, and Trick-or-Treat repositories to insert disabled configs; prompt-based
games also receive their reviewed English prompts. Before loading map configuration, the composition root also
inserts missing room-wide room_admin and room_whitelist door groups in
keypadGroupDefinitions, under the __room__ management namespace with
accessScope: "room" and the public default code knock. The existing map
configuration watch refreshes edits. Keypad authorization requires a matching
configured group code and live role or active static membership; no role
bypasses the code prompt. Room scope covers existing and future doors without
duplicating role groups per door. These insert-only seeds preserve enabled state, operator
settings, prompt revisions, and moderation decisions. Casino and location behavior baseline seeding logs
whether each baseline was inserted and its document revision. Shop defaults
are seeded only when its room key is absent; the region and itemset favors
are never invented. The bot persists
starting, calls BcClient.start(), then records ready and periodic
heartbeat states in prototypeRuntime. The client resolves start only after
room join/create and a matching room sync. In the supervisor role, the
composition root then imports a baseline from that synchronized room if no
prototypeRoomConfig revision exists and applies the current revision via
ApplyRoomSettingsAction. Startup proceeds only after matching room settings
are observed (or already satisfied); timeout or BC rejection fails startup.
Invalid required room or character
data keeps the process alive but leaves the client not ready: room-dependent
traffic is fenced and the same socket reconnects with capped exponential
backoff until a valid snapshot arrives. Malformed optional Ownership
metadata is ignored and leaves HasOwner unknown. A helper worker with
startLocationKey then requests MapTeleportAction; task polling starts only
after the destination is already observed locally or through an enabled,
fresh supervisor position stream, or a matching local map-position sync
arrives. The optional helper receiver starts before the helper can reach
ready; its handshake is bounded and loss leaves the worker fenced.stopped, flushes optional door-debug
writes, and closes MongoDB in a finally block. Startup failure rolls back
plugins already entered by the host.src/app/start.ts is the wiring/composition point. Features receive shared
dependencies; they do not create sockets or Mongo clients. BcClient owns login,
room join-or-create, server-event normalization, BC snapshots, reconnection and
the global outbound queue (14 packets per 1,200 ms).
MapTeleportAction resolves an enabled point or region from veratownLocations
(using a deterministic in-region tile for regions), uses the
guarded BcClient.updateOwnMapPosition() method, and journals the request with
ActionCoordinator. The local BcClient snapshot changes only on inbound BC
sync. When optional sharing is enabled, a fresh supervisor stream observation
may supply independent evidence for the helper’s destination; either matching
signal confirms convergence, not causal attribution. The helper startup
destination is optional and operator-managed.
The native-only emergency-release command applies the configured itemset
directly to the requesting member through the guarded appearance path. It does
not move either character or depend on a location observation.
When POSITION_OBSERVATION_ENABLED=true, DoorFeaturePlugin separately publishes
validated room-scoped position observations and redacted appearance observations
to process-local core buses. The supervisor serves sequenced room-scoped
observations over the Railway private HTTP stream, and helpers upload their own
local observations back through an authenticated POST endpoint on that same
service. Fresh positions are retained per observer so independent live reports
are not overwritten by another observer’s conflicting coordinate. Each helper has a unique bearer token; the supervisor checks its
enabled registry record and runtime/room, and the client validates scope, HMAC
proof, sequence, epoch, freshness, and bounded frame size. The feature defaults
off, uses no extra BC connection or database collection, and never treats
historical coordinates as live evidence.
The same optional session implements separate transport-neutral
HelperTaskNotifier (supervisor) and HelperTaskWakeups (helper) ports, injected
by start.ts. The supervisor dispatches a sequenced, payload-free hint only
after task persistence; helpers negotiate support through an HTTP request
header. Hints and stream reconnection wake the existing serialized Mongo
claim/lease worker, never a second executor. Periodic polling remains enabled
for unavailable streams, lost notifications, and mixed-version deployments.
Wake-ups coalesce while work is in flight. Startup positioning gates every
wake-up; connection generations fence stale preparation/claims after disconnect
or stop. The helper plug-in disposes its wake-up subscription before closing
the session during shutdown. Hints are neither BC observations nor task results.
Command suites register bounded, reviewed CommandHelpDescriptor metadata beside
their handlers; each registration disposer removes both handler and help metadata.
The composition root supplies a core CommandAliasResolver with a read function
over the main room’s command.aliases setting. The resolver parses and caches
the value per game-config revision; admin writes are checked against the live
help catalog. Real command paths win, an alias is expanded at most once, and
fixed arguments precede player-supplied arguments.
The parser sends global (/bot help, !help), scoped (/bot <feature> help),
and explicitly marked bare-feature requests to ScopedHelpService. It filters by
the current observed room membership, feature-wide enabled-state, room-admin
status, and capability availability policies, then replies only through private
whispers. A disabled feature hides every help descriptor, including its
administrative setup commands; capability policies can still hide individual
commands such as games unavailable at the requester’s location. Hidden and
unknown scopes return the same neutral text. Help is recomputed per request,
rate-limited, chunked, and suppressed if requester, permission, location/config,
or catalog revisions change before enqueue. Command execution continues to
perform its own authorization checks. Alias shortcuts appear next to a target
only when that descriptor is currently visible; admin-only targets stay
permission-checked by their handlers.
Plugins own lifecycle and subscriptions. A feature may also own a command suite, service/runtime, repository, and dedicated actions:
Shared core/async primitives own common mechanics without importing feature or
transport code: KeyedDeadlines replaces and cancels per-session timers,
SerialQueue/KeyedSerialQueue serialize dependent work without poisoning the
queue on failure, and TrackedTasks drains in-flight work during shutdown.
core/plugins/DisposerStack releases registrations, listeners, and asynchronous
watchers in reverse acquisition order, attempting every cleanup even if one
fails. Features retain their own authorization, error reporting, deadline
policy, and stop order; these primitives are not BC acknowledgements or durable
state authorities.
Synchronous disposers detach listeners before the first asynchronous cleanup
yield. drainTrackedTasks waits for every task group and reports all failures
without abandoning the other group’s drain. ABCL and Helper Bots fence new
work immediately on stop, then await already accepted polls, event work, and
repository calls before Mongo closes; a blocked repository call can delay
shutdown, but cannot execute a newly claimed task after the fence. Shop,
Casino, Dilemma, Trick-or-Treat, and World administration use reverse-order
resource cleanup, including partial-start rollback; a failed disposer does
not skip service shutdown, and the original startup error remains visible.
The map runtime, shop favors, and delayed location appearance use keyed,
replaceable deadlines with injected schedulers for deterministic tests; map
callback failures are reported through the door action-error/debug path rather
than becoming unhandled timer rejections. Communication and party-game plug-ins
drain accepted event work before their services stop and before the shared
Mongo client closes. A deadline expiring is not BC evidence.
adapters/mongo/revisioned-writes.ts shares atomic revision-CAS mechanics
for Hot Seat and Staged Table configuration/session writes; each repository
still owns document parsing, isolation guards, validation, and collection
selection. A competing write returns the newly read revision, never a
success-shaped fallback.
VeratownMapRuntime. Its transport-neutral DoorAdminService depends on the
MapRepository port; MongoMapRepository owns validated Mongo reads, writes,
indexes, and the configuration watcher. The plugin emits location
transitions on the shared core event bus. Location Entry Behavior consumes
those transitions; the bus keeps the map feature from importing the feature.
The location-messages plug-in uses a generic core location-entry handler to
revalidate live room membership and configured geometry before rendering
per-location templates and sending a private, journaled Whisper. Initial
snapshots and configuration-only recalculations do not send messages. The
location-appearance plug-in listens for both entry and leave events, cancels
per-member/location timers on exit, and rechecks current location configuration
and BC-observed position before applying an itemset through the shared
appearance service.
core/tile-geometry supplies pure point, inclusive-rectangle and canonical
row-span containment; map persistence validates legacy rectangles and exact
tile regions at the boundary. The runtime, door occupancy checks and helper
executor use the same containment semantics. Reload recalculates membership
from existing BC observations without creating new position evidence.
Region bounds in admin output are summaries only. Optional point coordinates
remain explicit destinations; region membership never supplies a teleport
destination.MOTD_TEXT as fallback. CharacterCommunicationApi presents
whisper/chat/emote/beep operations through dedicated actions./bot helpers assignments and observes helper
messages; a helper deployment claims only its configured identity and sends
Chat/Emote through CharacterCommunicationApi. A task is successful only
after the supervisor sees the matching message and current BC-observed
location. Helpers do not edit shared map data or execute movement. The
database-configured fleet limit defaults to three, and terminal tasks/action
journals use a five-day maximum retention and 10,000-record terminal FIFO cap.
Optional bounded flavor schedules run in the supervisor, using reviewed
region templates and observed occupancy; helper workers recheck current
settings and occupancy before dispatch. Schedules use the same queue and
budget, never infer occupancy from heartbeats, and do not replay missed ticks.BcAppearanceAction. It does not persist per-member
state or manage expiry.GameConfigRuntime keeps in-memory settings synced to
database updates via change streams. Game config stores bounded, revisioned
JSON in separate global and explicit room scopes; the runtime never inherits
global settings as room-specific values. Supported feature schemas validate
their configuration before admin writes and again before runtime use. World commands use the core
LocationAdministration API injected by the composition root rather than
importing the map feature. MapAdminService uses a repository port and a
guarded BC observation callback for live saves. WorldAdminPlugin owns the
map watcher and startup refresh; the composition root owns the
prototypeRoomConfig change stream. Serialized local/external reloads update
only the missing-room creation template, never BC-observed state. Versioned
persistent room settings are applied to the live room only at supervisor
process startup through a journaled action. A failed reload disables creation
instead of using a stale template.CharacterCommunicationApi.chatRedacted().
Session documents contain only room/config metadata, prompt IDs, member
numbers, accusations, phase/deadline, outcome and settlement metadata, and
timestamps—never submission text. An exclusive renewable room lease
prevents a second process from serving the same room; restart, disconnect,
shutdown, or lease loss voids an active round and clears the buffer. The game
remains disabled until an operator confirms the room is adults-only and
enables its room record.CASINO_GAMEPLAY_ENABLED gate is true following
operator-reported successful live qualification and activation approval on
2026-10-15, including location access. It is not an environment variable.
The slot/roulette service starts recovery with the plugin, persists outcomes
before settlement, and drains game work on shutdown. Operator grants,
transfers, purchases, and appearance stakes remain unavailable.
Gaining access to a bound, enabled roulette region sends a private invitation
once per observed occupancy; the bot is excluded. Invitations never open a
round or wager automatically. Repeated entries are deduplicated, exits remove
access, and pending notifications are logged/drained before the game worker
stops. An optional roulettePresentation rule routes public opening, spin, and
settled aggregate-result messages through RoulettePresentationRouter and the
existing leased helper-task collection; the supervisor retains roulette
authority, player access checks, settlement, and private participant reports.
Helper presentation defaults to the existing supervisor whisper path, and
stale stages are fenced against the persisted session’s saved roulette-region
binding. The router starts before the casino plug-in and stops after it. Only
reviewed Chat or Emote templates are accepted for opening/spinning, and
settled results remain Emotes. The helper’s own live position or a fresh
supervisor/peer-helper observation of its registered member can establish
region membership; its own uploaded position is not independent evidence.
Matching inbound message observations complete opening/spin tasks. Only
explicit region geometry qualifies; legacy bottom-right metadata
does not promote a point to a region.
The same per-member access tracker selects private recipients for round-open,
renewal, and closing announcements. Composition callbacks route through the
lifecycle-owned casino plugin; no local room-chat broadcast or second socket
is used. The feature-owned result reporter sends the public winning-bet count
and combined net result before one consolidated private bet list/net result
per participant (paginated only at BC’s message limit). Cancelled wagers are
excluded by settlement. Shared formatting covers new-bet confirmations and
manual/automatic round-start messages; no extra close-status whisper is sent.
Participant results remain private even after leaving the region, and
dispatch remains unconfirmed in the journal.
Feature-owned presentation helpers format help/rules, wallet and entry
notices, and slot results as labelled multiline messages. Long command
replies paginate privately at line boundaries, with room for BC’s OOC
wrapping; the command suite logs each dispatched part. Visibility and
authorization still use observed per-member access, not formatting helpers.
Idle roulette starts on the first valid funded bet, serialized with all other
table operations; optional explicit opening stays admin-only. Start messages
run outside the economic queue after reservation. Played rounds renew after
their saved cooldown; empty/fully cancelled windows close quietly and become
idle. Startup restores pending renewal only for durable played results, not
refunded/empty sessions. Interrupted no-result sessions still refund.
Entry grants are active independently and use the validated,
room-scoped casino.rules amount and cooldown (bounded to 1–20 chips and
24 hours–365 days) on server-observed entry or ready/reconnect. Startup seeds
the 20-chip / 24-hour baseline only when the explicit main room key is
missing; invalid stored rules still fail closed. Default game-location
bindings are empty, and the seeding does not enable wagering. The composition
root injects the subscription; the plug-in
disposes it and drains grant work before Mongo closes.
Wallet balance, grant timestamp, and ledger update share one transaction.
The transaction-topology gate is satisfied by read-only test evidence
and operator confirmation that production shares the same backend; production
credentials and transaction permissions were not independently tested.
Entry funding, cryptographic randomness, and recovery policy are approved.
Revised four-reel weighted slots, all supplied roulette selections, multiple
bets/last-active-bet cancellation, and 20-player round pacing are implemented.
Optional terminal result/renewal timestamps preserve cooldowns after restart.
The documented slot baseline measures 95.4383735% theoretical RTP while retaining
100x diamond and 500x seven jackpots. Private rules/fairness commands
explain the current odds and verification limitations. Game availability is
per-sender: the map runtime publishes observed point/region entry and exit
transitions; a supported game-ID/key/shape policy from the room-scoped
casino.rules snapshot filters private help and every game command. Actual
keys exist only in database location records/configuration, never runtime
constants. Invalid or empty bindings fail closed. Game sessions persist the
effective rules snapshot and document revision; recovery reuses saved rules
rather than current configuration.shop.rules snapshot. Supported handheld drink assets are applied through the
shared appearance action; gifts and release orders bind a short-lived quote to
the recipient, who must accept it. Purchase reservations settle only after
matching appearance evidence, with unresolved mutations retained for
reconciliation. Releases and favors are blocked until the adults-only room
attestation is enabled. Self-service favors
use strict observed-state checks, pay their all-or-nothing reward only after
completion, and void on disconnect, departure, or restart gaps. Leaving the
shop region alone does not end a favor. Durable prototypeShopOrders records
and stable economy operation IDs make recovery idempotent.
Optional shopkeeper flavor is queued as a short-lived helper template task;
transaction details remain in private supervisor whispers, and the task
contains no command text, quote code, balance, or room code.prototypePartyGameConfigs and
prototypeTruthDarePrompts; sessions use revision-checked transitions and
recoverable economy operation IDs in prototypeTruthDareSessions. Shared
credits are reserved or adjusted through EconomyPort; no bids or balances
are public. The winner’s stake is consumed and reward granted only after an
explicit self-attestation. Decline, departure, and timeout release the stake.
The lowest-bid challenge is optional and has no economic consequence.
TRUTH_DARE_GAMEPLAY_ENABLED is true after operator-reported live
qualification and explicit activation approval on 2026-10-15. The room
configuration still defaults to disabled. The plug-in recovers persisted
deadlines and reservations, cancels timers, drains work, and disposes BC
listeners on stop.prototypePartyGameConfigs
record and reviewed prompt in the selected runtime database; missing config
disables new rounds. The room-policy confirmation is an operator attestation,
not an age check. The service pins configuration and prompt snapshots,
enforces one active session per room, recovers persisted deadlines, and uses
the shared economy’s stable grant-once operations after a durable outcome.
Session records retain bounded round choices only; terminal rounds expire
after 60 days./bot safeword reaches its activity handler through the
shared SafetyStopRegistry. Optional appearance consent pins validated
itemsets and release effects to the session; guarded, cancelable BC mutations
advance only on observed state. Per-member cleanup deadlines survive restart
without terminal TTL while release is pending.BcCommandParser to validate native Hidden
targets, bang-command channels, sender identity, and BC content normalization.
The core BotCommandParser accepts only normalized command inputs, matches
the longest registered prefix, resolves help/aliases, and invokes handlers.Native /bot <command> messages arrive as targeted hidden BC bot packets.
Bang-prefixed commands are accepted in supported chat/emote/whisper messages.
The parser handles routing and syntax, not feature permissions or business
policy. Command handlers and event-driven features call services/actions; they
do not directly emit BC packets.
State-changing BC operations use a dedicated action and the shared
ActionCoordinator. It writes requested before dispatch, records dispatch,
then records only evidence supported by the BC response, a later BC state
observation, or a documented read-back. One-way sends remain unconfirmed.
Timeouts are not successes and do not trigger blind retries. The coordinator
does not establish causal attribution when BC responses have no request ID.
BcApiPlane is a typed event/request boundary for contracts, safe packet
summaries, subscriptions, and selected response/read-back flows; it does not
replace domain-specific actions or the guarded methods on BcClient. Each
action has one journal owner. In particular, do not wrap an already journaled
action in another journaled BcApiPlane.dispatch().
Feature use cases own domain validation and policy and depend on repository or action ports. Channel adapters own parsing, authentication/authorization, and response formatting. Location Entry Behavior uses world-config administration; its appearance sets use the standard appearance itemset editor. Permissions are channel-specific and must be checked independently by every adapter. Keep transport types and chat/Discord APIs out of shared feature services so another channel does not fork business behavior.
| Information | Authority and use |
|---|---|
| BC room and character snapshots, member presence, map positions, appearance, pose, arousal progress, reputation, title, and relationship-presence observations | Latest valid inbound BC sync/read-back held in memory by BcClient; owner/lovership are reduced to presence booleans, reputation/title are validated projections, and arousal retains only progress. Dispatch never optimistically edits these snapshots. They represent observed BC state, not proof this bot caused it. |
| Veratown economy balances | prototypeEconomyAccounts through the shared EconomyService; condition credits reads available credits only, excluding reservations. BC Money is not used. |
| Shared helper position evidence | Optional five-second bounded MapPositionObservationBus entry received from the authenticated supervisor stream. Original BC observation time/source and stream epoch/sequence are retained; disconnect/departure invalidates entries. It is never persisted or used for another member. |
| Shared helper appearance evidence | Optional five-second bounded per-observer appearance observations received from the authenticated helper stream. Matching local BC room updates are sufficient for shower actions; helper corroboration is not required. Local room syncs clear only the local cache entry. Appearance observations are never persisted. |
| Room template and stored map revisions | Shared validated Mongo reader selects the highest room revision (legacy fallback only without revisions). Watched and locally refreshed for missing-room creation only; inbound BC sync remains the live-room authority. |
| Persistent BC room settings | Append-only prototypeRoomConfig revisions; the supervisor imports the synchronized room as revision 1 when no version exists and reconciles the latest version at process startup. Admin-Map writes alone never mutate live BC state. |
| Locations, door definitions, groups and memberships | MongoDB configuration loaded at startup; the map plugin reloads it on change-stream notifications. Room/BC observations are still used to evaluate live access and occupants. |
| Generic feature configuration | Revisioned prototypeGameConfig, with bounded JSON stored globally or under an explicit room key. Startup seeds absent main-room casino, shop, and location-entry behavior baselines; features validate strict schemas and room-scoped consumers never fall back to global values. Replaced revisions are backed up transactionally. |
| Shop offers | prototypeGameConfig.rooms.main["shop.rules"], operator-managed shop region, and prototypeItemSets for itemset favors. The region and itemsets are not seeded; adult offers require explicit policy confirmation. |
| Shower settings and songs | prototypeShowerConfig, prototypeShowerSongs, and atomic prototypeShowerSongCounts; only the selected runtime database. Settings and songs are disabled/unseeded; clothing snapshots remain in bounded process memory only. |
| Anonymous Whisperer configuration and prompts | prototypePartyGameConfigs and prototypeWhispererPrompts, validated at startup. The room game is disabled by default; prompt seeds are idempotent and preserve operator edits. |
| Anonymous Whisperer active submission | One bounded process-memory value under the room lease. It is never reconstructed from BC chat history or stored in MongoDB, logs, debug records, errors, or action journals. |
| Anonymous Whisperer session and lease | prototypeWhispererSessions owns metadata-only revisioned rounds and durable pending awards; prototypeWhispererLeases owns room process exclusivity. |
| Appearance itemsets and location-entry policy | Operator-managed standard prototypeItemSets and room-scoped prototypeGameConfig.rooms.main["location-entry-behavior.rules"], validated before use. The startup generic baseline is disabled; configured itemset timer options pass through to BC. |
| Action outcomes | prototypeActions, written by the coordinator with lifecycle history, five-day TTL, and a 10,000-terminal-record FIFO cap. Structured stdout also emits each transition joined by operationId. This is an audit of the attempted operation and evidence—not an alternate live-state store. |
| Process status | prototypeRuntime heartbeat/state records. These are operational liveness data, not BC room state. |
| Door/command diagnostics | Optional prototypeDoorDebug records with 30-day TTL, enabled by DOOR_DEBUG=true. These supplement action records and are not feature configuration. |
src/adapters/mongo/ owns generic persistence adapters and the guarded
connection. Feature repositories own feature-specific MongoDB operations;
domain services, the core, and the composition root do not manipulate
collections. Operator scripts are explicit exceptions and must be
dry-run-first, target-isolated, bounded, and non-deleting.
| Collection | Owner / purpose | Retention and compatibility |
|---|---|---|
veratownRooms, veratownMap |
Shared Mongo room/map reader; world map-admin repository writes append-only revisions | No automatic pruning/TTL. Legacy fallback preserved; indexed highest-revision reads, partial unique room/revision writes. |
prototypeMapBackups |
World map-admin repository; stored-map snapshots | Operator-managed retention; backup restore appends a revision. |
prototypeRoomConfig |
World room-config repository; append-only persistent BC room settings by room key and revision | No TTL or automatic pruning; indexed unique room/revision history, with the newest 50 revisions exposed in Admin-Map. |
prototypeAdminAudit |
Independent web administration; sanitized actor, operation and affected-record metadata transactionally paired with configuration writes | 30-day expiresAt TTL; no raw configuration, access codes, prompts, songs or map sign text. |
prototypeAdminWebSessions |
Independent admin web service; local-login session keyed by the SHA-256 of the cookie token, with email, lastSeenAt and absolute expiresAt. Authentication only, never BC or configuration authority |
expiresAt TTL (1 hour); no tokens, passwords or roles (roles are resolved from current configuration). |
prototypeAdminSnapshots |
Independent web administration; versioned, non-secret configuration recovery plus a per-room serialization guard | 30-day expiresAt TTL and at most 20 snapshots per room; guard has no TTL. Maximum 8 MiB / 8,000 configuration records per snapshot. |
prototypeGameConfig |
World game-config repository/runtime | Revisioned bounded JSON with global and explicit room scopes; no automatic retention. |
prototypePartyGameConfigs / prototypeWhispererPrompts |
Anonymous Whisperer repository; room rules and curated adult prompts | Config and curated text have no TTL; startup seeds disabled defaults and ten prompts without overwriting operator edits. |
prototypeWhispererSessions / prototypeWhispererLeases |
Anonymous Whisperer repository; metadata-only rounds and exclusive room ownership | Terminal sessions expire after 30 days; pending bonus sessions and leases remain until settled or application expiry. |
veratownMapBackups |
Configuration copy operator | No runtime consumer or automatic retention policy; do not prune implicitly. |
veratownLocations, keypadDoorDefinitions, keypadGroupDefinitions, keypadGroupMemberships |
Map repository | Legacy map values normalize at the read boundary; only additive non-unique query indexes are initialized. Membership expiry remains application-enforced. |
prototypeItemSets |
Shared appearance itemset repository | Generic appearance contract, with location-entry behavior as a consumer; missing version is accepted only after full legacy validation. Item lists remain bounded. |
prototypeEconomyAccounts |
Economy repository | Global per-member available/reserved credits and entry-grant timestamp; no TTL. |
prototypeEconomyLedger |
Economy repository | Stable idempotency records; 60-day TTL after finalization. Unsettled reservations and the cutover marker remain durable. |
prototypeShopOrders |
Shop repository | Orders remain durable while open/unconfirmed; terminal orders expire after 60 days. |
prototypeShowerConfig, prototypeShowerSongs, prototypeShowerSongCounts |
Shower repository; room-scoped settings, bounded song catalog and atomic capacity counters | No TTL; settings default disabled, songs are admin-provided, and appearance snapshots are never persisted. |
prototypeCasinoWallets, prototypeCasinoLedger |
Legacy casino migration/rollback | Retained as rollback source; wallets have no TTL and settled ledger expiry is extended to 60 days. |
prototypeCasinoSessions |
Casino session repository | 30-day TTL after terminal closure. Accepting, settling, and recovery-pending sessions remain durable. |
prototypePartyGameConfigs |
Room-scoped party-game repositories, including <roomKey>:staged-table and <roomKey>:trick-or-treat |
Revisioned settings; no TTL. Missing configuration is disabled; Staged Table appearance requires an explicit validated stage/release plan. |
prototypeStagedTablePrompts |
Staged Table repository; reviewed English prompt revisions | No TTL; edits append revisions and retire the previous prompt without overwriting its text. |
prototypeStagedTableSessions |
Staged Table repository; bounded roster, in-progress hand, scores, markers, prompt attestations, and pinned appearance/release deadlines | Active and cleanup-pending terminal sessions have no TTL; fully released terminal sessions expire after 30 days. Private cards are removed when a hand resolves or is voided. |
prototypeHotSeatPrompts |
Trusted Hot Seat prompt operator | Moderated records; no TTL. Only validated approved English prompts are selectable. |
prototypeHotSeatSessions |
Hot Seat session repository | Active sessions are durable without TTL; terminal sessions expire after 30 days. |
prototypeTrickOrTreatSessions |
Trick-or-Treat session repository | Bounded room roster, route, score, and consented interaction state; active sessions are durable and terminal sessions expire after 30 days. |
prototypeTrickOrTreatProgress |
Trick-or-Treat session repository | Per-room/member eligible loss streak; stage progress commits only after observed appearance and expires after 30 days. |
prototypeTrickOrTreatProgress |
Trick-or-Treat session repository | Per-room/member eligible loss streak; stage progress commits only after observed appearance and expires after 30 days. |
prototypeHelperAgents, prototypeHelperSettings, prototypeHelperTemplates |
Helper Bots repository | Operator-managed identity, bounded fleet settings, and public templates; no TTL. |
prototypeHelperTasks |
Helper Bots repository and roulette presentation router | Active/reconciliation tasks are preserved; roulette stages use stable session/stage IDs and bounded ordering; terminal tasks have a five-day TTL and 10,000-record FIFO cap. |
prototypeHelperActionBudgets |
Helper Bots repository | Per-helper rolling-minute action budgets; short TTL. |
prototypeActions |
Generic Mongo action journal | Five-day Date-based TTL; oldest terminal records are pruned after 10,000. |
prototypeRuntime |
MongoRuntimeHeartbeat |
Each heartbeat refreshes expiresAt as a BSON Date; its 30-day TTL index is initialized before use. |
prototypeDoorDebug |
Generic Mongo diagnostics adapter | Optional diagnostics with existing 30-day Date-based TTL. |
Itemsets use one shared appearance schema and compatibility reader; conditional
variants are part of that generic schema, not feature-owned fields. The
AppearanceItemSetService resolves the first matching variant from the target’s
latest BC-observed appearance and the BC adapter’s injected prerequisite check.
Unknown evidence never selects a variant. This selection is separate from the
transport-independent core/ condition contracts and does not mutate observed
state. Feature policies may reject variants when they require a fixed itemset.
The versioned schema remains strict for writes. Contract normalization can be
inspected and, with explicit operator approval, applied with
npm run migrate:mongodb-contracts -- --apply. It validates before writing,
uses the selected fixed runtime database only, preserves document IDs, and
deletes nothing. It does not deploy database validators or unique indexes.
The source/production database is only read by the dry-run-first, allowlisted configuration-copy script. The running bot reads and writes only the isolated prototype database; it does not query production player profiles or session state as a fallback.
The implementation matches the intended core boundaries: the core contains transport/database-independent contracts, one composition root owns shared instances, the BC adapter owns transport and snapshots, plugins clean up their subscriptions, and actions distinguish dispatch from evidence-backed outcomes. The action journal and isolation guard are implemented rather than aspirational.
The following are improvements to consider, not claims that the runtime already provides them:
mapDataSchema, including bounded optional sign CellData; room settings
still use the established explicit defaults. Preserve that shared map contract
when adding new storage surfaces rather than introducing another projection.Db or concrete BcClient types directly. This is workable in the
current composition, but narrower injected repository/observation ports
would make feature logic easier to test and reduce migration cost. Extract
ports only where they clarify ownership; do not add a generic DI framework.See BC-API-PLANE.md for contracts and class-level UML, BC-SOCKET-API-ACKNOWLEDGEMENTS.md for the packet evidence matrix, and the feature guides for detailed flows.
flowchart LR
Sync[Inbound BC room sync] --> Observed[Defensive live map observation]
Observed --> Save[Authorized map save]
Save --> Schema[Shared mapDataSchema including optional CellData]
Schema --> Revisions[Append-only stored revisions]
Editor[Authenticated visual layout draft] --> Layout[Catalog and protected-metadata validation]
Layout --> Revisions
Editor --> Analysis[Shared stateless map analyzer]
Analysis --> Review[Exact candidates and explicit import decisions]
Review --> Forms[Normal version-checked location and door editors]
Revisions --> Backup[Validated backup and restore]
Backup --> Revisions
Revisions --> Template[Validated future room-creation template]
Template --> Create[ChatRoomCreate only when room is missing]
Metadata edits preserve the complete validated map; restore appends a revision and refreshes the template without changing the live room. A malformed selected head fails rather than falling back. Sign text is bounded authorized map configuration, never action-journal/debug metadata. Door updates preserve unrelated signs, and only inbound BC sync changes observed state. Composition, plugin lifecycle, collections, indexes, and shutdown order are unchanged.
The visual editor appends layout_edit revisions with the existing transaction,
expected-version and sanitized-audit boundaries. It preserves effects and
CellData, rejecting object replacement at protected cells. Analysis consumes
only bounded geometry/export input and reuses the offline analyzer, renderers
and import planner; artifacts never include sign text or private cell metadata.
Its same-origin embedded walkthrough is illustrative, not a movement/access
authority. Import validation hands records to normal reviewed forms, never a
second bulk-write path. Leaving the workspace disposes message listeners and
artifact URLs.