This repository runs as a long-lived Node.js worker on Railway. The existing
veratown Railway project and production service can be reused; connect the
service to the Rarsus/veratown-prototype GitHub repository and deploy its
main branch.
An optional, independent map analysis service can be deployed in the same project. It has its own build/start commands and does not launch the bot process.
For end-to-end service setup, see HOWTO: Configure Railway Supervisor And Helper Bots. Supervisor-managed ABCL and its required region helper are covered by HOWTO: Enable Supervisor-Managed ABCL.
Use Railway’s Railpack builder; a Dockerfile is not needed. The Node.js engine
in package.json requires Node 24.
npm run buildnpm startThe build compiles TypeScript into dist/ and generates the API reference.
The start command runs dist/index.js. Railway should redeploy when changes
are pushed to the connected branch.
This process maintains an outbound Bondage Club Socket.IO connection. It serves no HTTP traffic by default. When optional helper position sharing is enabled, the supervisor accepts a small authenticated HTTP stream only on its Railway private network port; helpers make outbound requests to that private endpoint. Do not configure a public domain or HTTP healthcheck.
The map-web service (build:map-web / start:map-web) has been removed. The
walkthrough, map analysis and image export are part of the
configuration dashboard (Map tools), authenticated by the
dashboard’s own login and available to viewers. Delete the old Railway service
and its WEB_AUTH_* variables once the dashboard is deployed; no replacement
service is needed.
The separate configuration dashboard has its own Railway service, database
identity, and local passkey-only authentication by default; it does not
require Cloudflare Access. Follow the configuration management service
guide for account bootstrap, passkey setup, and test-database-first
setup. Do not add BC credentials or helper-sharing secrets to that service.
It remains read-only unless ADMIN_WEB_WRITES_ENABLED=true explicitly enables
administrator writes. Qualify on the test database with a dedicated,
least-privilege identity and transaction support before considering production.
Local mode stores passwordless accounts, passkey public credentials, and
single-use invitation metadata in dedicated prototype collections. Environment
operator password recovery is optional and explicitly enabled with
AUTHENTICATION_RECOVERY_METHOD=environment-password; see the Admin Web guide
for bootstrap and recovery details. Keep the prototype database encrypted at
rest.
Writable deployments require exact WEB_AUTH_ORIGIN protection in either
authentication mode. Deployment does not itself enable configuration writes.
The dashboard supports Serverless idle sleep: it closes its MongoDB client after 15 minutes without database work and reconnects on demand. Admin login sessions are stored in MongoDB, so reconnects and cold wakes do not require signing in again. Enable Serverless only on the independent web service and redeploy for the setting to take effect. The bot and helper workers must remain always-on.
Set runtime values in the Railway service’s production environment. Use the
matching entries from your local .env file; never commit .env or paste
credential values into this document.
| Variable | Required | Notes |
|---|---|---|
RUNTIME_TARGET |
Yes | Set to production; pins the runtime database to veratown_prototype. |
BOT_INSTANCE_ID |
No | Stable ID of the database-managed profile; its role and non-secret settings override runtime variables. |
RUNTIME_ROLE |
No | Environment-only mode: supervisor by default; use helper or abcl for dedicated services. Ignored with a profile. |
BC_SERVER_URL |
Conditional | Required without a profile; otherwise supplied by the profile. |
BC_USERNAME |
Yes | Dedicated prototype bot account. |
BC_PASSWORD |
Yes | Password for the dedicated prototype account. |
BC_ROOM |
Conditional | Required without a profile; otherwise supplied by the profile. |
MONGODB_URI |
Yes | MongoDB connection string. |
MONGODB_DATABASE |
No | Must be veratown_prototype; this is the runtime’s isolated database. |
BC_ORIGIN |
No | Defaults to https://www.bondageprojects.elementfx.com; overridden by a selected profile. |
HELPER_AGENT_ID |
Conditional | Required for an environment-configured helper; a selected profile supplies its helper identity. |
MOTD_TEXT |
No | Fallback greeting when no valid scheduled MOTD rule matches; defaults to the prototype welcome message. |
APPEARANCE_QUALIFICATION_ENABLED |
No | Defaults to false; the profile can also set it. Enable only for an explicitly approved qualification. |
DOOR_DEBUG |
No | Set to true only when door debug logging is needed. |
POSITION_OBSERVATION_ENABLED |
No | Defaults to false; environment or profile must enable it on both supervisor and helper. |
POSITION_OBSERVATION_LISTEN_HOST |
Sharing | Supervisor only; profile or environment value; bind to 0.0.0.0. |
POSITION_OBSERVATION_PORT |
Sharing | Supervisor only; profile or environment value must match the Railway private service port. |
POSITION_OBSERVATION_HELPER_TOKENS |
Sharing | Supervisor-only JSON object mapping enabled helper IDs to distinct random bearer tokens. |
POSITION_OBSERVATION_ENDPOINT |
Sharing | Helper only; profile or environment value for the supervisor’s private endpoint. |
POSITION_OBSERVATION_HELPER_TOKEN |
Sharing | Helper-only token matching its supervisor mapping. |
POSITION_OBSERVATION_HANDSHAKE_TIMEOUT_MS |
No | Helper handshake budget; profile or environment value, default 3,000 ms (500–10,000 ms). |
SOURCE_MONGODB_DATABASE is only used by the manual configuration-copy
script. Do not configure it as a runtime database variable. The runtime
refuses any MONGODB_DATABASE other than veratown_prototype.
With BOT_INSTANCE_ID, startup uses only RUNTIME_TARGET, MONGODB_URI,
optional fixed-mapping MONGODB_DATABASE, and the instance ID to connect and
load the profile. The profile role overrides RUNTIME_ROLE and selects the
existing static composition; non-secret profile values override their
environment equivalents. Create the profile in Admin Web before deploying
that ID. Profile writes do not restart Railway services, and all current
profile-controlled changes require an operator-managed restart. The profile’s
runtime-target field is validation metadata only; RUNTIME_TARGET selects the
fixed database mapping and cannot be overridden by a profile. Keep
BC_USERNAME, BC_PASSWORD, Mongo settings, and position-observation tokens
in Railway variables/secrets; they are never profile data. The Admin Web
profile workflow and revision/audit behavior are documented in the
bot instance profile guide.
The prototype can share a MongoDB cluster with another bot, but its runtime must use the isolated database above. Ensure the MongoDB user can access that database and the provider permits connections from Railway.
Railway’s private WireGuard network provides encrypted service-to-service
traffic. This implementation uses Node’s built-in HTTP client/server on that
private network and does not require TLS certificates. Application-level
authentication remains required: give each helper a distinct random token,
configure the exact same value for its helper ID in the supervisor’s
POSITION_OBSERVATION_HELPER_TOKENS JSON object and the helper’s
POSITION_OBSERVATION_HELPER_TOKEN, and store those values only in Railway
variables. The supervisor also checks that the helper is currently enabled and
registered for the exact room/runtime, verifies its fleet membership, and sends
only that helper’s member position. An HMAC proof authenticates the supervisor
stream response. Tokens and packet contents are never logged.
POSITION_OBSERVATION_LISTEN_HOST=0.0.0.0, and set
POSITION_OBSERVATION_PORT to the same port shown in Railway Networking.POSITION_OBSERVATION_HELPER_TOKENS to a JSON object whose keys are
enabled helper agentId values and whose values are distinct random
URL-safe tokens of at least 32 characters.POSITION_OBSERVATION_ENDPOINT to the
supervisor’s Railway private domain with that port and the documented
/v1/map-position-observations path. Set its own matching
POSITION_OBSERVATION_HELPER_TOKEN.POSITION_OBSERVATION_ENABLED=true on the supervisor and the intended
single helper only after verifying the room, runtime target, registry record,
service port, private DNS name, and token mapping. Leave all other helpers
disabled until separately qualified.listening/connected transport status, helper startup teleport outcome,
one dispatch/one journal record, and no task polling after any timeout,
disconnect, or stop. Then qualify production with one helper and monitor
sanitized status/latency and timeout outcomes before expanding.Sharing defaults off and can be rolled back by disabling
POSITION_OBSERVATION_ENABLED on both services and restarting them. The helper
then uses its existing local BC observations; no cached/persisted positions are
used. On stream loss, room/runtime mismatch, invalid frame, or stale/future
observation, remote entries are invalidated. Timeouts remain fenced, and no
movement is retried. The transport has bounded client count, frame size, cache,
reconnect backoff, and per-helper scope; do not add a public route, database
polling, whispers, or another BC connection as a fallback.
The sender emits a sequenced liveness heartbeat every second. The receiver invalidates its remote cache and reconnects after 3.5 seconds without stream data. Heartbeats are control frames only; they do not create or refresh BC position evidence.
Rarsus/veratown-prototype, branch main,
while keeping it in the existing veratown project and production
environment.Do not enable a public domain or HTTP healthcheck for this worker. Monitor
availability through Railway deployment status and logs, and through the
prototypeRuntime heartbeat in MongoDB.
Records emitted through the shared runtime logger are one JSON object per line.
Schema version 1
records include timestamp, severity, eventName, deployment, service,
and replica; correlated work adds a correlation object and event-specific
data lives under fields. Errors use name, message, and stack; non-Error
rejection values are omitted. Sensitive field names and configured secrets are
redacted by the shared logger.
Startup phase records use eventName: "startup.phase" with phase and
status (started, completed, or failed) under fields. Phases cover
config, database, repositories, defaults, room_configuration,
composition, plugins, heartbeat, bc, and ready. Concurrent repository
initialization and room loading include a component name so a failed index or
document load can be traced to its owner. ready means startup finished after
BC readiness, not merely socket dispatch.
Action transitions emit separate action.lifecycle records joined by
correlation.operationId; dispatch and confirmation remain distinct. Position
observation rejections use position_observation.rejected and include the
bounded observation metadata, computed age, freshness limit, and rejection
reason when those values are available.
Fatal records go to stderr with event names startup_failed,
unhandled_rejection, or uncaught_exception. They include the same process
envelope, classification, startup phase when available, error message, stack
trace, and nested causes/aggregate failures.
Plugin startup errors identify the failing plugin; rollback errors identify the
plugin whose cleanup failed without replacing the original failure.
Check the final fatal record and preceding phase records:
BC_SERVER_URL must be a URL.
RUNTIME_TARGET=production permits only veratown_prototype.MONGODB_URI, MongoDB authentication, permissions on the
isolated database, provider network rules, and connectivity from Railway.Configuration validation logs variable names and issue codes, not supplied values. Other error messages and stacks redact configured credentials/account values, configured URLs/URIs, and inline connection URLs; arbitrary properties and non-Error rejection payloads are omitted. Do not add credentials, player text, or private payloads to error messages. Treat logs as operator-only data.
Fatal records are written synchronously before exiting with status 1, including when open handles would otherwise leave the container alive. Startup failures retain the existing cleanup paths. Unhandled rejections and uncaught exceptions terminate immediately rather than continuing potentially inconsistent state; they do not attempt asynchronous cleanup. SIGINT/SIGTERM still use normal graceful shutdown. Configure Railway’s restart policy as appropriate.