bzl

self-hosted ephemeral community engine
Log | Files | Refs | README | LICENSE

MAPS_V2_IMPLEMENTATION_SPEC.md (19492B)


      1 # Maps V2 Implementation Spec
      2 
      3 Last updated: 2026-02-22  
      4 Status: Draft (implementation-ready)
      5 
      6 ## 1) Goal
      7 
      8 Upgrade Maps from a good prototype into a first-class social-world system that supports:
      9 - immersive fullscreen play
     10 - in-world GM authoring overlays
     11 - stronger avatar systems (token/image/frame-animation + advanced spritesheet import)
     12 - reliable spatial audio (walkie + stream audio)
     13 - robust TTRPG authoring and runtime operations
     14 
     15 This spec is intentionally scoped to be shippable in phases without breaking current maps.
     16 
     17 ## 2) Product outcomes
     18 
     19 ### Primary outcomes
     20 - Players can enter a map and feel “present” immediately.
     21 - GMs can author while playing (without modal-heavy context switching).
     22 - Chat/map “open chat” flows are reliable and predictable.
     23 - Audio features are reliable before adding complexity.
     24 
     25 ### Success metrics
     26 - <1% map join failure rate (client-side measured)
     27 - <1% walkie send failure rate after retry
     28 - 95th percentile map interaction latency <150ms on same region
     29 - 0 known panel-layout breakages in rack mode + mobile for Maps panel
     30 
     31 ## 3) Scope / non-scope
     32 
     33 ### In scope
     34 - Fullscreen focus mode
     35 - GM in-map tool overlays
     36 - avatar mode system
     37 - walkie reliability and directional audio
     38 - opt-in stream audio with spatial routing
     39 - stronger TTRPG editing workflow
     40 
     41 ### Not in scope (V2 baseline)
     42 - full MMO combat/quest economy engine
     43 - persistent server-authoritative physics
     44 - cross-instance shared map worlds
     45 
     46 ## 4) Design principles
     47 
     48 - **Play-first UI**: map gets screen priority, tools stay accessible.
     49 - **Author in context**: no forced exit from gameplay to edit geometry/props.
     50 - **Backward compatibility**: old maps load and work.
     51 - **Capability flags**: client adapts by features supported by server/plugin version.
     52 - **Fail-soft networking**: local prediction + clear recovery paths.
     53 
     54 ## 4.1 Design locks (locked decisions)
     55 
     56 1. **Focus mode defaults to panel-focus, not browser fullscreen**
     57    - Primary behavior is map-focused panel mode.
     58    - Browser fullscreen (`requestFullscreen`) is optional and explicit.
     59 
     60 2. **GM overlay is mode-driven**
     61    - One active mode at a time (initial set: `play`, `select`, `place`, `polygon`).
     62    - Hotbar binds to current mode actions, not free-floating UI state.
     63    - Default mode is always `play`.
     64    - Entering focus mode sets mode to `play`.
     65    - Exiting any tool returns mode to `play`.
     66 
     67 3. **Capabilities handshake is mandatory on map join**
     68    - Server emits `plugin:maps:capabilities` immediately after `joinOk`.
     69    - Client does not guess feature support.
     70 
     71 4. **Avatar state is user-owned**
     72    - Avatar configuration is stored in user prefs/plugin prefs, not map documents.
     73    - Maps may apply runtime display overrides only (e.g., possession/speak-as-token).
     74    - Default avatar animation pipeline is frame-based “Quick Mode”; spritesheets are advanced/optional.
     75 
     76 5. **Walkie V2 ships before stream audio**
     77    - Walkie reliability is a hard prerequisite for stream audio mode.
     78 
     79 6. **Undo/redo envelope defined early**
     80    - `toolCommand` command envelope exists in Phase 1, even with a small command set.
     81 
     82 7. **GM tools remain in-map overlays**
     83    - No required separate “GM tools panel” in V2 baseline.
     84 
     85 8. **ACL starts minimal**
     86    - Begin with `editors[]` + one/two policy toggles.
     87    - Defer role-group ACL complexity until usage evidence exists.
     88 
     89 9. **Input priority is explicit**
     90    - Input arbitration order is defined up front: text inputs > drag/edit > movement.
     91    - Prevents WASD/overlay/tool conflicts.
     92    - Movement is never blocked silently; blocked-state reason must be visible in UI.
     93 
     94 ## 5) UX architecture
     95 
     96 ## 5.1 Map Focus mode
     97 
     98 Add `Map Focus` state in Maps panel:
     99 - hides rack clutter and non-essential panel controls
    100 - uses full panel canvas area
    101 - toggle from corner button or `F`
    102 - `Esc` exits focus mode
    103 - preserve previous layout state after exiting
    104 
    105 ### Desktop behavior
    106 - “Focus” expands map panel to workspace emphasis
    107 - optional true browser fullscreen (`requestFullscreen`) behind user action
    108 
    109 ### Mobile behavior
    110 - map screen takes full app viewport
    111 - overlays collapse to compact hotbar + slide-up drawers
    112 - safe-area insets respected
    113 
    114 ## 5.2 GM overlays
    115 
    116 Replace right-side heavy tool density with in-map overlays:
    117 - top-left: mode + selected tool + map name
    118 - bottom: hotbar (`1-9`) and quick actions
    119 - right drawer: inspector (contextual, collapsible)
    120 - command palette (`/` or `Ctrl/Cmd+K`) for advanced actions
    121 
    122 Authoring must allow movement + editing without mode break.
    123 
    124 ### Movement feedback rule
    125 - If movement is blocked, the user must get immediate visible feedback.
    126 - Required cues:
    127   - drag/edit: pointer/cursor state
    128   - typing: obvious focused input state
    129   - tool lock: highlighted active tool/mode
    130 
    131 ## 5.3 Panel strategy
    132 
    133 - Keep Maps as a standalone panel id `maps`.
    134 - Do not embed maps inside hives panel.
    135 - TTRPG/GM controls belong to map overlays/drawers, not separate mandatory panel.
    136 
    137 ## 6) Avatar system v2
    138 
    139 Introduce `avatarMode` per user per map session:
    140 - `profile_token`
    141 - `image_token`
    142 - `frame_animation` (default Quick Mode)
    143 - `spritesheet`
    144 
    145 ## 6.1 Profile token
    146 - circular token using profile image
    147 - optional display name label
    148 
    149 ## 6.2 Image token
    150 - uploaded image + metadata:
    151   - pivot/foot anchor
    152   - collision radius
    153   - facing direction support (flip/rotate)
    154 
    155 ## 6.3 Frame-based avatar animation (Quick Mode, default)
    156 
    157 Users define animation states as ordered frame lists (individual uploaded images), without spritesheet slicing.
    158 
    159 Baseline states:
    160 - `idle`
    161 - `walk_vertical`
    162 - `walk_horizontal`
    163 
    164 Optional states:
    165 - directional idles/walks (`idle_up/down/left/right`, `walk_up/down/left/right`)
    166 - emotes (`wave`, `dance`, etc.)
    167 
    168 Quick Mode behavior:
    169 - upload frame images one-by-one
    170 - drag/reorder frames within each state
    171 - instant preview in editor
    172 - per-state loop toggle
    173 - movement-driven state switching (`idle` vs walk states)
    174 - directional horizontal flip by default (for left/right when dedicated states are not supplied)
    175 
    176 Emote behavior:
    177 - hotkey-triggered animation override
    178 - emote plays then auto-returns to prior locomotion state
    179 - emote can be non-looping (default) or looping with cancel
    180 
    181 ## 6.4 Spritesheet mode (advanced import)
    182 - upload sheet + metadata:
    183   - frame width/height
    184   - rows/columns
    185   - animation sets by direction (`idle_up/down/left/right`, `walk_*`)
    186   - frame rate
    187 
    188 ## 6.5 Server avatar presets (staff-authored library)
    189 
    190 Add a per-instance avatar preset library that can be authored by `owner`/`admin`/`moderator`, then selected by members.
    191 
    192 Preset goals:
    193 - remove setup friction for regular users
    194 - let server team define coherent visual style packs
    195 - keep user choice simple (`pick preset`, optional display name override)
    196 
    197 Preset behavior:
    198 - staff can create, update, delete, and publish presets
    199 - users can browse and apply any published preset
    200 - applying a preset copies its avatar config into the user avatar state (not a hard live link)
    201 - optional server setting can enforce `preset-only` mode for members
    202 
    203 Preset scope:
    204 - supports `profile_token`, `image_token`, `frame_animation`, and `spritesheet`
    205 - supports starter emotes/hotkeys bundled by staff
    206 - supports tag/category metadata for browsing (e.g., fantasy, sci-fi, cozy)
    207 
    208 ### Data model (client/server)
    209 ```ts
    210 type AvatarMode = "profile_token" | "image_token" | "frame_animation" | "spritesheet";
    211 
    212 type AvatarState = {
    213   mode: AvatarMode;
    214   displayName?: string;
    215   showUsername?: boolean;
    216   frameAnimation?: {
    217     defaultFps: number;
    218     states: Record<
    219       string,
    220       {
    221         frames: Array<{ url: string }>;
    222         fps?: number;
    223         loop?: boolean;
    224         flipXWithDirection?: boolean;
    225       }
    226     >;
    227     movementMap?: {
    228       idle?: string;
    229       walkVertical?: string;
    230       walkHorizontal?: string;
    231       walkUp?: string;
    232       walkDown?: string;
    233       walkLeft?: string;
    234       walkRight?: string;
    235     };
    236     emotes?: Array<{
    237       name: string;
    238       state: string;
    239       hotkey?: string; // e.g. "Digit1", "KeyZ"
    240       loop?: boolean;
    241       interruptible?: boolean;
    242     }>;
    243   };
    244   imageToken?: {
    245     url: string;
    246     collisionRadius: number; // normalized / map scale aware
    247     pivotX: number;
    248     pivotY: number;
    249     facing: "left" | "right" | "up" | "down";
    250     flipWithDirection: boolean;
    251   };
    252   spritesheet?: {
    253     url: string;
    254     frameW: number;
    255     frameH: number;
    256     rows: number;
    257     cols: number;
    258     fps: number;
    259     animations: Record<string, number[]>; // key -> frame indices
    260   };
    261 };
    262 
    263 type AvatarPreset = {
    264   id: string;
    265   name: string;
    266   description?: string;
    267   tags?: string[];
    268   mode: AvatarMode;
    269   avatar: Omit<AvatarState, "displayName" | "showUsername">;
    270   createdBy: string;
    271   updatedBy: string;
    272   createdAt: number;
    273   updatedAt: number;
    274   published: boolean;
    275 };
    276 ```
    277 
    278 ## 7) Audio systems
    279 
    280 ## 7.1 Walkie reliability pass (before stream mode)
    281 
    282 Current walkie exists but requires hardening:
    283 - explicit state machine: `idle -> recording -> encoding -> uploading -> sent -> played/timeout`
    284 - retry policy for upload failure
    285 - client-visible status + error toasts
    286 - server ack tracking improvements + cleanup guarantees
    287 - telemetry events for each failure stage
    288 
    289 ## 7.2 Directional spatial audio abstraction
    290 
    291 Create shared spatial engine for all short/long audio:
    292 - distance attenuation
    293 - stereo pan
    294 - configurable falloff curve
    295 - future occlusion hook (not required in first pass)
    296 
    297 ## 7.3 Stream audio mode (opt-in)
    298 
    299 Add low-latency stream channels per user/source:
    300 - listeners opt-in per source
    301 - source appears in map with directional spatialization
    302 - per-source mute + quick volume controls
    303 
    304 Implementation target:
    305 - RTC-based media for live stream audio
    306 - keep walkie upload model as fallback mode
    307 
    308 ## 8) TTRPG tool overhaul
    309 
    310 ## 8.1 Editing workflow
    311 
    312 - drag/drop sprite placement
    313 - transform gizmos (move/rotate/scale)
    314 - layer controls (z-order, lock, hide)
    315 - snap options (grid + vertex snapping)
    316 - multi-select for batch actions
    317 - undo/redo command stack
    318 
    319 ## 8.2 Geometry authoring improvements
    320 
    321 - polygon tools unified in one editor rail:
    322   - collisions
    323   - masks
    324   - exits
    325   - hidden masks/fog
    326   - fall-through
    327   - occluders
    328 - inline inspector for selected polygon metadata
    329 - validity checks before save
    330 
    331 ## 8.3 Runtime token controls
    332 
    333 - possession states
    334 - token cards (hp/name/owner)
    335 - token context menu
    336 - optional “speak as token” clearly surfaced
    337 
    338 ## 9) Role and permissions model
    339 
    340 Align maps plugin with core role hierarchy:
    341 - `owner`, `admin`, `moderator`, `member`
    342 
    343 Recommended matrix:
    344 - create/delete/update map: owner/admin/moderator (+ map owner)
    345 - ttrpg structural edits: owner/admin/moderator (+ delegated map editors)
    346 - runtime token move/possess: configurable per map policy
    347 - avatar preset create/update/delete/publish: owner/admin/moderator
    348 - avatar preset apply: all authenticated users (published presets)
    349 
    350 Add map-level ACL field:
    351 ```ts
    352 type MapAcl = {
    353   editors: string[]; // usernames
    354   canMembersPlaceProps: boolean;
    355   canMembersUseTokens: boolean;
    356 };
    357 ```
    358 
    359 ## 10) Networking contracts (v2 additions)
    360 
    361 Keep existing events; add versioned extensions:
    362 - `plugin:maps:capabilities` (server -> client)
    363 - `plugin:maps:getCapabilities` (client -> server, on-demand refresh/debug)
    364 - `plugin:maps:setAvatar`
    365 - `plugin:maps:listAvatarPresets`
    366 - `plugin:maps:upsertAvatarPreset` (staff only)
    367 - `plugin:maps:deleteAvatarPreset` (staff only)
    368 - `plugin:maps:applyAvatarPreset`
    369 - `plugin:maps:walkieState`
    370 - `plugin:maps:typing` (throttled presence signal)
    371 - `plugin:maps:presence` (room activity snapshot)
    372 - `plugin:maps:streamOffer` / `streamAnswer` / `streamIce` (if RTC in plugin WS channel)
    373 - `plugin:maps:toolCommand` (for undoable commands)
    374 
    375 ## 10.1 Handshake timing contract
    376 
    377 - On successful join:
    378   1. `plugin:maps:joinOk`
    379   2. `plugin:maps:capabilities`
    380 - Client may request re-send with `plugin:maps:getCapabilities` after reconnects or plugin reload.
    381 
    382 Server capability payload example:
    383 ```json
    384 {
    385   "type": "plugin:maps:capabilities",
    386   "version": "2.0.0",
    387   "features": {
    388     "focusMode": true,
    389     "gmOverlay": true,
    390     "avatarModes": ["profile_token", "image_token", "frame_animation", "spritesheet"],
    391     "walkieV2": true,
    392     "spatialStreamAudio": true,
    393     "undoRedo": true
    394   }
    395 }
    396 ```
    397 
    398 ### 10.2 Safe fallback contract
    399 
    400 For capability mismatches, clients must degrade gracefully:
    401 - unsupported feature UI is hidden (not shown as broken/disabled unless needed for explanation)
    402 - unsupported avatar modes fallback to `profile_token`
    403 - unsupported advanced tools fallback to `play` mode + baseline map interaction
    404 - no capability mismatch should block join/movement/chat basics
    405 
    406 ## 11) Persistence and migration
    407 
    408 Current map data lives at `data/plugin-data/maps.json`.
    409 
    410 Migration strategy:
    411 1. Add `schemaVersion` to map objects.
    412 2. On load, migrate in memory to latest schema.
    413 3. Persist migrated schema safely (write temp + atomic rename pattern).
    414 4. Keep compatibility reader for at least 1 major cycle.
    415 
    416 Key new persisted fields:
    417 - map ACL
    418 - optional map settings for focus defaults and tool preferences
    419 - avatar metadata references (per user state can remain session/prefs scoped)
    420 
    421 ## 12) Performance constraints
    422 
    423 - target 60fps render loop on mid-range desktop
    424 - stable 30fps minimum on typical mobile
    425 - throttle non-critical broadcasts
    426 - avoid full-map payload rebroadcast for small edits (delta-based)
    427 - lazy load spritesheet textures; cap memory per map
    428 - texture guardrails:
    429   - max texture dimension: default 4096 (optional stricter deploy default 2048)
    430   - max decoded spritesheet memory budget per map (configurable hard cap)
    431   - reject/downscale oversized assets with clear user-visible error
    432 
    433 ## 12.1 Presence signal
    434 
    435 Maps should expose an explicit activity signal to support discoverability:
    436 - room user count
    437 - active/live boolean (e.g., recent movement/chat/audio window)
    438 - optional freshness timestamp in map list payloads
    439 
    440 ## 13) Security constraints
    441 
    442 - enforce upload URL/path validation (already present, keep strict)
    443 - sanitize all user-provided labels/display names
    444 - rate limit audio/message actions
    445 - avoid trusting client movement for restricted zones when game rules matter
    446 - map ACL checks must be server-side authoritative
    447 
    448 ## 14) Rollout plan
    449 
    450 ## Phase 1: UX shell + reliability
    451 - Must ship:
    452   - role update to include `admin` parity in maps permission gates
    453   - Focus mode (panel-focus first, Esc exit)
    454   - walkie reliability state machine + retry + deterministic cleanup
    455   - capabilities handshake (`joinOk` + `capabilities`)
    456 - Scaffold (acceptable as minimal stubs):
    457   - GM overlay shell + hotbar
    458   - command palette stub
    459   - profile-token avatar UI stub
    460 
    461 Exit criteria:
    462 - no panel/layout regressions
    463 - walkie failure rate materially reduced
    464 
    465 ## Phase 2: Avatar + TTRPG ergonomics
    466 - profile/image token modes
    467 - frame-based avatar animation Quick Mode (default)
    468 - emote animation overrides + hotkeys
    469 - server avatar preset library + user preset picker
    470 - drag/drop props, gizmos, inspector improvements
    471 - undo/redo command stack
    472 
    473 Exit criteria:
    474 - GM can build a playable scene without leaving map panel
    475 - users can animate avatars in Quick Mode without spritesheet tooling
    476 
    477 ## Phase 3: Spritesheet + stream audio
    478 - advanced spritesheet import mode
    479 - opt-in spatial stream audio
    480 - per-source controls + metrics
    481 
    482 Exit criteria:
    483 - stable multi-user session with directional stream audio in production
    484 
    485 ## 15) QA checklist
    486 
    487 - rack mode + legacy mode + mobile behavior
    488 - map focus enter/exit with preserved layout
    489 - map editing permissions across roles
    490 - chat open reliability from hives/maps/streams
    491 - walkie send/play cleanup and timeout behavior
    492 - migration on old `maps.json` files
    493 - frame-state upload/reorder/preview flow (Quick Mode)
    494 - emote override start/finish/return-to-idle behavior
    495 
    496 ## 16) Open decisions
    497 
    498 - Stream audio stack: **reuse shared stream-pack/LiveKit infra**; maps plugin stays UI/routing layer.
    499 - ACL grouping: **per-map ACL first**, role groups deferred.
    500 - Spritesheet asset location: **per-user profile library first** (portable identity).
    501 - Token movement authority: keep client-driven baseline; optional server-authoritative mode may be introduced later under a map flag.
    502 
    503 ## 17) Immediate implementation backlog (ready to build)
    504 
    505 1. Add maps role checks for `admin` parity.
    506 2. Add `Map Focus` mode state + UI toggle + `Esc` exit.
    507 3. Move GM controls into overlay hotbar/drawer shell.
    508 4. Instrument walkie state transitions and failures.
    509 5. Introduce capability handshake event.
    510 6. Add avatar mode schema + `profile_token` first.
    511 7. Add `frame_animation` Quick Mode editor + runtime playback.
    512 8. Add emote override/hotkey handling with auto-return behavior.
    513 9. Add advanced spritesheet import path (after Quick Mode ships).
    514 10. Add avatar preset CRUD (staff) + preset picker (users).
    515 
    516 ## 17.1 Phase 1 build checklist (file-by-file)
    517 
    518 ### A) Admin parity (server)
    519 - File: `plugins_dev/maps/server.js`
    520 - Update role checks for:
    521   - `createMap`
    522   - `updateMap`
    523   - `deleteMap`
    524   - `ttrpgSetEnabled`
    525   - all `ttrpg*` edit actions
    526   - other destructive map actions
    527 - Target role policy: owner/admin/moderator (+ map owner where already supported).
    528 
    529 ### B) Focus mode (client)
    530 - File: `plugins_dev/maps/client.js`
    531 - Add state:
    532   - `isFocusMode: boolean`
    533 - Add controls:
    534   - corner button
    535   - keyboard: `F` toggle, `Esc` exit
    536 - Persist optional preference per-user (local preference key).
    537 - Ensure safe-area and rack-mode resize behavior.
    538 
    539 ### C) GM overlay shell (client)
    540 - File: `plugins_dev/maps/client.js`
    541 - Add overlay frame:
    542   - top-left mode pill
    543   - bottom hotbar (`1-9`)
    544   - right inspector drawer
    545   - command palette stub (`/`, `Ctrl/Cmd+K`)
    546 - Keep movement available while overlay is present.
    547 - Enforce mode defaulting:
    548   - maps load in `play`
    549   - focus enter sets `play`
    550   - exiting tools returns `play`
    551 
    552 ### D) Walkie V2 reliability (client + server)
    553 - Files:
    554   - `plugins_dev/maps/client.js`
    555   - `plugins_dev/maps/server.js`
    556 - Client:
    557   - explicit state machine
    558   - one retry with backoff minimum
    559   - status UI (recording/uploading/sent/failed)
    560 - Server:
    561   - deterministic cleanup on ack/timeout/disconnect
    562   - telemetry counters/logs for failure stages
    563 
    564 ### E) Capabilities handshake
    565 - Files:
    566   - `plugins_dev/maps/server.js`
    567   - `plugins_dev/maps/client.js`
    568 - Add:
    569   - server emit `plugin:maps:capabilities` after `joinOk`
    570   - server support `plugin:maps:getCapabilities` refresh
    571   - server handler `plugin:maps:getCapabilities`
    572   - client gating for feature-dependent UI elements
    573   - safe fallback rendering for unsupported capabilities
    574 
    575 ### F) Profile-token avatar (minimal)
    576 - Files:
    577   - `plugins_dev/maps/client.js`
    578   - `plugins_dev/maps/server.js`
    579   - core prefs surface if needed (`server.js` / `public/app.js`) for plugin-pref persistence
    580 - Add:
    581   - `setAvatar` event shape
    582   - profile-token render path
    583   - optional `displayName` + `showUsername` toggle
    584 
    585 ### G) Typing + presence baseline (low-cost, high-impact)
    586 - Files:
    587   - `plugins_dev/maps/client.js`
    588   - `plugins_dev/maps/server.js`
    589 - Add:
    590   - throttled `plugin:maps:typing`
    591   - lightweight `plugin:maps:presence` publish/update in map list + room state
    592 
    593 ## 17.2 Phase 1 edge cases (must test)
    594 
    595 - Rack mode resize while in focus mode
    596 - Switching away from maps panel and back while focused
    597 - Mobile safe-area + virtual keyboard interactions
    598 - Drag/edit interactions while movement keys are pressed
    599 - Reconnect flow (`joinOk` + `capabilities`) consistency
    600 - Walkie clip cleanup if sender/receiver disconnects mid-play