veratown-prototype

Map Movement and Rudimentary Pathfinding — Historical Proposal

Status: Superseded on 2026-10-29 by Map Character Movement and Pathfinding Proposal. This earlier proposal is retained as design history; its speculative graph schema and position-persistence concepts were not adopted.

The implemented helper startup teleport described below remains in place. The new proposal validates BC’s existing admin-targeted map teleport flow and defines the no-server-change design for moving other room members.

Assessment and implemented scope

The original design describes a larger navigation feature than the current runtime supports. The repository already stores room-scoped point/region locations in veratownLocations and emits location transitions from BC-observed coordinates. It does not have graph exits, movement permissions/capacity rules, or a player movement command. Adding the proposed mapLocationDefinitions and mapCharacterPositions collections would duplicate existing configuration and make MongoDB compete with BC as the authority for a character’s live position.

Bondage Club exposes ChatRoomCharacterMapDataUpdate for a character map-data update, followed (when delivered) by ChatRoomSyncMapData. The update has no request ID or direct acknowledgement. MapViewTeleport is a map-view chat dictionary tag, not a movement packet. The implementation therefore treats teleport as an immediate position-update request and reports success only after the bot’s matching position is observed; it does not claim that BC accepted a particular operation causally.

BcClient.start() continues to own login, join-or-create, and the room sync barrier. When a helper agent has an optional startLocationKey, its worker requests a teleport from the ready event, which occurs only after the matching room has synchronized. The target must be an enabled point location in the configured room. The worker does not poll or execute tasks unless the resulting position is observed (or was already observed at that point). A missing or unconfirmed teleport fences the worker; it is not blindly retried.

This implementation does not move other characters, choose a location in a region, bypass location enablement, change the room/map template, or persist a second position record. Operators select the location through the existing helper-agent configuration. Live BC qualification is still required before relying on movement in an operational room.

Purpose and scope

The long-term proposal is a shared movement system for bots and player-controlled characters on the map. Its currently implemented slice is operator-configured same-room helper startup teleport. The graph navigation and player-facing behavior described below is a future design, not implemented runtime behavior.

Long-term target scope:

Explicitly unchanged:

Design constraints

Movement modes

The BC MapViewTeleport dictionary tag is not a movement mode or packet.

Future graph model (not implemented)

Room and location graph

A room is the top-level container on the BC map. Each room has:

A location is a position identifier within a room. It has:

An exit is a directional connection from one location to another. It has:

Location state and accessibility

At any moment, a location may be accessible or inaccessible based on:

  1. Room access: The player or bot must have valid membership/access to the room.
  2. Location eligibility: The location must exist and not be gated by a config flag (e.g., "player-areas-disabled").
  3. Exit conditions: If moving via an exit, all conditions must be satisfied (e.g., door unlocked, admin permission, economy requirement).
  4. Capacity or state: Configured capacity limits, exclusive occupancy, or ongoing game sessions may block access.

Movement is denied if any condition fails. The system must report which condition was violated to support user-facing feedback.

Position authority

The proposed mapCharacterPositions collection is not adopted. The current room snapshot and its character MapData.Pos, updated only from BC syncs, are the live position authority. Existing veratownLocations documents store room configuration, not character state. Any future durable movement intent or reconciliation record must remain distinct from the current server-observed coordinate.

Future movement validation contract (not implemented)

Movement attempt

A movement attempt is a request to relocate a character from current to target (a room + location pair). The system validates in this order:

  1. Character eligibility: The character exists, is not in an exclusive game session, and has no active movement restriction.
  2. Room access: The character has access to the target room (membership, permissions, etc.).
  3. Location eligibility: The target location exists in the room and is not gated off by configuration.
  4. Path availability:
    • If moving to a different location in the same room, verify the exit is valid.
    • If moving to a different room, verify the room-exit is valid.
    • Check all exit conditions (door locks, admin permissions, economy requirements, etc.).
  5. Capacity: If the location has a capacity limit, verify there is space.
  6. No collision: If exclusivity is configured (e.g., “only one player per furniture”), verify the location is unoccupied.

If all checks pass, the movement is approved. If any check fails, the system reports the first failure reason and does not update position.

Validation result types

type MovementResult =
    | { success: true; from: Location; to: Location; path?: Location[] }
    | {
          success: false;
          reason:
              | "no-room-access"
              | "invalid-location"
              | "blocked-exit"
              | "capacity-full"
              | "exclusive-occupied"
              | "character-restricted";
          details?: string; // e.g., "door requires admin permission"
      };

Rudimentary pathfinding: breadth-first search (deferred)

Algorithm

For multi-step movement, use breadth-first search (BFS) to compute the shortest accessible path:

  1. Input: A starting location and a goal location (both within the same room or across adjacent rooms).
  2. Queue: Initialize with the start location.
  3. Visited set: Track already-explored locations to avoid cycles.
  4. Search loop:
    • Dequeue a location.
    • If it matches the goal, reconstruct and return the path.
    • For each exit from the current location:
      • Validate the exit is passable (no locked doors, access permitted, etc.).
      • If the destination is not visited, enqueue it.
    • Repeat until the queue is empty or goal is found.
  5. No path: If the queue empties without reaching the goal, return “no accessible path” and an optional list of reachable locations for diagnostics.

Complexity bounds

Pathfinding scope

Fallback and diagnostics

Character movement: player commands (deferred)

Movement commands

A player issues an explicit movement command using:

Command handling

  1. Parse the destination (location and room).
  2. Validate movement from the player’s current position to the target.
  3. Pathfinding: If the target is not directly adjacent, compute a path using BFS.
  4. Report the result:
    • Success: Update position, list the path (if multi-step), and describe the new location.
    • Failure: Report the denial reason and (if helpful) list nearby accessible locations.

Permission filtering

Movement is allowed if the player’s character:

Future player commands must show only destinations the requester can access. Any administrative override needs a separately defined policy and must not be assumed to bypass BC room access or feature restrictions.

Bot and NPC movement

Bot movement drivers

The implemented bot movement is limited to startup positioning for helper workers. With startLocationKey configured on the helper-agent document, the worker requests a same-room teleport after the BcClient ready event. The target must be an enabled point location, and the worker remains inactive until BC observes that position. Leaving the field unset preserves existing worker behavior.

Future bot movement could include:

  1. Explicit command: An admin or feature routine calls the movement API directly.
  2. Patrol: A bot follows a configured sequence of locations on a timer (e.g., every 5 minutes).
  3. Reactive: A bot moves in response to an event (e.g., relocate to join a game session, move away when a player commands it).
  4. Idle reposition: A bot moves to an idle location to keep the room “lively” or to maintain presence in a game area.

Patrol configuration

A patrol is a sequence of locations (in one or more rooms) that a bot repeats:

{
  botMemberNumber: number;
  name: string;
  locations: Array<{ roomKey: string; location: string }>;
  intervalMs: number; // e.g., 5 * 60_000 for every 5 minutes
  enabled: boolean;
  validation?: "strict" | "skip-inaccessible"; // strict fails if any step is blocked; skip-inaccessible retries on next interval
}

Patrols are stored in a mapBotPatrols collection (or feature-scoped equivalent). A bot executes only one active patrol per session. If validation fails, the bot either:

Reactive movement

Bots move reactively in response to:

Reactive movements use the same validation and pathfinding as player commands. If a reactive move fails, the bot logs the failure and optionally falls back to the nearest accessible location.

Position consistency and recovery (future, not implemented)

Startup and reconnection

When the bot starts or a character reconnects:

  1. Load position: Query the mapCharacterPositions collection for the character’s last recorded position.
  2. Validate: Check if that position is still valid (location exists, room is accessible, character is not in a game session).
  3. Recovery:
    • Valid: Use the last position.
    • Invalid: Fall back to a configured default location (e.g., "Bondage Club:center") and log the fallback reason.
    • Missing: Assign the default location.

Position staleness

If a position record is older than a configured threshold (e.g., 24 hours offline), optionally flag it for manual review or reset to the default location.

Map reconfiguration

If a location is removed, renamed, or gated off during a game session:

  1. Check: Validate characters’ current positions against the new map configuration.
  2. Offline characters: Attempt a recovery move to the fallback location on next login.
  3. Online characters: Optionally prompt them to move or force a graceful relocation.

Database schema (future concepts only)

No movement-specific collection is created by the current implementation. Coordinates are observed from BC; helper startLocationKey is an optional field in prototypeHelperAgents, and location coordinates continue to come from the existing veratownLocations configuration. The schemas below are unimplemented concepts and must not be treated as current runtime storage.

mapCharacterPositions collection

{
  _id: ObjectId;
  memberNumber: number; // unique index
  roomKey: string;
  location: string;
  timestamp: Date; // last update time
  movementReason?: string;
  lastValidated: Date;
  recoveryFallback?: {
    roomKey: string;
    location: string;
  };
}

mapBotPatrols collection

{
  _id: ObjectId;
  botMemberNumber: number;
  name: string;
  locations: Array<{
    roomKey: string;
    location: string;
  }>;
  intervalMs: number;
  enabled: boolean;
  createdAt: Date;
  updatedAt: Date;
  lastExecutedAt?: Date;
  validationMode: "strict" | "skip-inaccessible";
}

mapLocationDefinitions collection

{
  _id: ObjectId;
  roomKey: string;
  location: string; // unique per room
  category: string;
  metadata?: {
    description?: string;
    capacity?: number;
    exclusive?: boolean;
  };
  exits: Array<{
    name: string;
    destination: {
      roomKey: string;
      location: string;
    };
    conditions?: Array<string>;
    metadata?: object;
  }>;
  gateFlags?: Array<string>; // e.g., ["player-areas-enabled"]
  createdAt: Date;
  updatedAt: Date;
}

Proposed API contract (not implemented)

Core movement port

interface MapMovementPort {
  /**
   * Validate and perform a movement attempt.
   */
  move(
    character: { memberNumber: number },
    destination: { roomKey: string; location: string },
    reason: string
  ): Promise<MovementResult>;

  /**
   * Compute a multi-step path between two locations.
   */
  findPath(
    start: { roomKey: string; location: string },
    goal: { roomKey: string; location: string }
  ): Promise<{ path: Array<{ roomKey: string; location: string }> | null; reachable: Array<...> }>;

  /**
   * Get the current position of a character.
   */
  getPosition(memberNumber: number): Promise<{ roomKey: string; location: string } | null>;

  /**
  /**
   * List all accessible locations from a given position.
   */
  getAccessibleExits(
    memberNumber: number,
    position: { roomKey: string; location: string }
  ): Promise<Array<{ name: string; destination: { roomKey: string; location: string } }>>;
}

Boundary with world administration

Movement uses the world administration subsystem to:

There is no circular dependency: movement does not modify map definitions; world admin does not directly execute movement (it delegates to the movement port).

Boundary with game sessions

Exclusive game sessions mark a character as “in-session” and block movement. When a session ends, position is restored to the pre-session location or a configured fallback.

Error handling and logging

Validation failures

When movement validation fails, report:

Examples:

Pathfinding diagnostics

If pathfinding fails to find a goal, optionally offer alternatives:

Bot movement failures

If a bot’s patrol or reactive movement fails:

State inconsistency

If a character’s position is invalid or corrupt:

Implementation phases

Phase 1: Startup teleport (implemented)

Phase 2: Graph navigation (deferred)

Phase 3: Bot autonomy (deferred)

Phase 4: Polish and integration (deferred)

Testing strategy

Known limitations and future work

Future enhancements could include: