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