Phase 17: map & fog VTT overhaul + player-facing projection

- Pure geometry library src/lib/map/* (grid, distance 5e/pf2e/euclid, AoE
  circle/cone/line/square, polygon rasterize + point-in-polygon, brush, fog set
  algebra, player projection) with unit tests.
- Schema: map tokens gain size/kind/characterId/hp/conditions/gmOnly; maps gain
  drawings/gridUnit(feet)/gridType/fogVersion + point/drawing sub-schemas. Dexie v8
  additive migration; mapsRepo.get + transactional mutate.
- Editor split into a shared MapCanvas renderer + MapEditor tool state machine:
  fog via brush/rectangle/polygon (+ size, reveal-all/hide-all), measurement
  (system-aware distance in feet), AoE templates (circle/cone/line/square preview),
  drawing annotations (freehand/line/arrow/rect/circle/text, GM-only), pings, and
  richer tokens (NxN size, character link with live HP ring, condition dots, edit popover).
- Player-facing projection: toPlayerProjection strips GM-only content; PlayerMapView
  renders it read-only with opaque fog; uiStore.activeMapId + 'Show to players'; the
  player view now shows the live battle map. enemyStatus extracted to
  src/lib/combat/playerProjection.ts (reused, and for Phase 18).

9 geometry unit tests; maps e2e covers upload→token→reveal-all→player projection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 10:31:26 +02:00
parent ffe24495b6
commit fb459ad92c
20 changed files with 1047 additions and 223 deletions
+18
View File
@@ -0,0 +1,18 @@
import type { PlayerMap } from '@/lib/map';
import { MapCanvas, type CanvasToken } from '@/features/world/map/MapCanvas';
/** Read-only render of a player-facing map projection + its image. */
export function PlayerMapView({ map, image, maxWidth }: { map: PlayerMap; image?: string; maxWidth?: number }) {
const tokens: CanvasToken[] = map.tokens.map((t) => ({
id: t.id, label: t.label, color: t.color, col: t.col, row: t.row,
size: t.size, kind: t.kind, hp: t.hp, conditions: t.conditions,
}));
return (
<MapCanvas
readOnly
playerFog
{...(maxWidth !== undefined ? { maxWidth } : {})}
view={{ image, gridSize: map.gridSize, showGrid: map.showGrid, fogEnabled: map.fogEnabled, revealed: map.revealed, tokens, drawings: map.drawings }}
/>
);
}