mudlet-map-renderer 2.3.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flushSceneShapes-c7ttw7D-.js","names":[],"sources":["../src/utils/color.ts","../src/scene/RoomFlags.ts","../src/TypedEventEmitter.ts","../src/lens/RoomLens.ts","../src/camera/Camera.ts","../src/coord/CoordFn.ts","../src/CullingManager.ts","../src/reader/Exit.ts","../src/directions.ts","../src/ExitRenderer.ts","../src/scene/StubStyle.ts","../src/scene/SpecialExitStyle.ts","../src/utils/textMeasure.ts","../src/scene/RoomStyle.ts","../src/scene/elements/RoomLayout.ts","../src/scene/InnerExitStyle.ts","../src/scene/elements/ExitLayout.ts","../src/scene/elements/SpecialExitLayout.ts","../src/scene/elements/StubLayout.ts","../src/scene/elements/LabelLayout.ts","../src/lens/hiddenAwareLens.ts","../src/scene/NeighborProjector.ts","../src/ScenePipeline.ts","../src/InteractionHandler.ts","../src/PathData.ts","../src/scene/OverlayStyle.ts","../src/scene/elements/OverlayLayout.ts","../src/hit/HitTester.ts","../src/style/Style.ts","../src/style/applyStyle.ts","../src/export/flushSceneShapes.ts"],"sourcesContent":["export function colorLightness(color: string): number {\n let r: number, g: number, b: number;\n const rgbMatch = color.match(/(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)/);\n if (rgbMatch) {\n r = parseInt(rgbMatch[1]) / 255;\n g = parseInt(rgbMatch[2]) / 255;\n b = parseInt(rgbMatch[3]) / 255;\n } else if (color.startsWith('#') && color.length >= 7) {\n r = parseInt(color.slice(1, 3), 16) / 255;\n g = parseInt(color.slice(3, 5), 16) / 255;\n b = parseInt(color.slice(5, 7), 16) / 255;\n } else {\n return 0.5;\n }\n return (Math.max(r, g, b) + Math.min(r, g, b)) / 2;\n}\n\n/**\n * Parse the RGB channels (and optional alpha) of a colour string. Accepts\n * `rgb(...)`, `rgba(...)`, and `#rrggbb`. Returns `undefined` for formats we\n * can't shade (named colours, `#rgb` shorthand) so callers pass them through.\n */\nfunction parseRgb(color: string): {r: number; g: number; b: number; a?: number} | undefined {\n const rgbMatch = color.match(/(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*([\\d.]+)\\s*)?/);\n if (rgbMatch) {\n return {\n r: parseInt(rgbMatch[1]),\n g: parseInt(rgbMatch[2]),\n b: parseInt(rgbMatch[3]),\n a: rgbMatch[4] !== undefined ? parseFloat(rgbMatch[4]) : undefined,\n };\n }\n if (color.startsWith('#') && color.length >= 7) {\n return {\n r: parseInt(color.slice(1, 3), 16),\n g: parseInt(color.slice(3, 5), 16),\n b: parseInt(color.slice(5, 7), 16),\n };\n }\n return undefined;\n}\n\n/** Emit `rgb(...)`, or `rgba(...)` when an alpha channel was present, so that\n * shading a translucent colour preserves its transparency. */\nfunction formatRgb(r: number, g: number, b: number, a?: number): string {\n return a !== undefined ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})`;\n}\n\nexport function darkenColor(color: string, factor: number): string {\n const rgb = parseRgb(color);\n if (!rgb) return color;\n return formatRgb(\n Math.round(rgb.r * (1 - factor)),\n Math.round(rgb.g * (1 - factor)),\n Math.round(rgb.b * (1 - factor)),\n rgb.a,\n );\n}\n\nexport function lightenColor(color: string, factor: number): string {\n const rgb = parseRgb(color);\n if (!rgb) return color;\n return formatRgb(\n Math.min(255, Math.round(rgb.r + (255 - rgb.r) * factor)),\n Math.min(255, Math.round(rgb.g + (255 - rgb.g) * factor)),\n Math.min(255, Math.round(rgb.b + (255 - rgb.b) * factor)),\n rgb.a,\n );\n}\n\n// Most-used CSS named colours. Covers what callers reach for in highlight/marker\n// settings; unknown names fall back to the input string so they at least render.\nconst NAMED_COLORS: Record<string, [number, number, number]> = {\n black: [0, 0, 0], white: [255, 255, 255], red: [255, 0, 0], green: [0, 128, 0],\n lime: [0, 255, 0], blue: [0, 0, 255], yellow: [255, 255, 0], cyan: [0, 255, 255],\n aqua: [0, 255, 255], magenta: [255, 0, 255], fuchsia: [255, 0, 255],\n silver: [192, 192, 192], gray: [128, 128, 128], grey: [128, 128, 128],\n maroon: [128, 0, 0], olive: [128, 128, 0], purple: [128, 0, 128], teal: [0, 128, 128],\n navy: [0, 0, 128], orange: [255, 165, 0], pink: [255, 192, 203], gold: [255, 215, 0],\n brown: [165, 42, 42], violet: [238, 130, 238], indigo: [75, 0, 130],\n transparent: [0, 0, 0],\n};\n\n/**\n * Apply an alpha value to any CSS colour. Accepts `#rgb`, `#rrggbb`, `rgb(...)`,\n * `rgba(...)` (alphas multiply), and the named colours in {@link NAMED_COLORS}.\n * Falls back to returning the input unchanged for unrecognised formats so the\n * shape stays visible rather than collapsing to black.\n *\n * The name is historical — see also call sites that take any user-supplied colour.\n */\nexport function hexToRgba(color: string, alpha: number): string {\n const c = color.trim();\n\n if (c.startsWith('#')) {\n let r: number, g: number, b: number;\n if (c.length === 4) {\n r = parseInt(c[1] + c[1], 16);\n g = parseInt(c[2] + c[2], 16);\n b = parseInt(c[3] + c[3], 16);\n } else if (c.length >= 7) {\n r = parseInt(c.slice(1, 3), 16);\n g = parseInt(c.slice(3, 5), 16);\n b = parseInt(c.slice(5, 7), 16);\n } else {\n return color;\n }\n if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return color;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n\n const rgbaMatch = c.match(/^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*([\\d.]+)\\s*)?\\)$/i);\n if (rgbaMatch) {\n const r = parseInt(rgbaMatch[1], 10);\n const g = parseInt(rgbaMatch[2], 10);\n const b = parseInt(rgbaMatch[3], 10);\n const a = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) * alpha : alpha;\n return `rgba(${r}, ${g}, ${b}, ${a})`;\n }\n\n const named = NAMED_COLORS[c.toLowerCase()];\n if (named) {\n return `rgba(${named[0]}, ${named[1]}, ${named[2]}, ${alpha})`;\n }\n\n return color;\n}\n","/**\n * Per-room flags Mudlet stores in {@link MapData.Room.userData}.\n *\n * Mudlet keeps a handful of newer room properties in `userData` (rather than as\n * typed binary fields) so older map files stay forward-compatible. This module\n * is the single place that knows those key strings, so adding a typed source\n * later (e.g. the binary `hidden` field from map format v21+) is a one-line\n * change here rather than a hunt across the renderer.\n */\n\nimport type {HiddenRoomMode} from \"../types/Settings\";\n\n/**\n * userData key Mudlet uses for a room's hidden state on pre-v21 map formats\n * (case-insensitive `\"true\"`). On v21+ formats Mudlet stores this as a binary\n * `hidden` field instead — which the JSON pipeline does not currently surface,\n * so only maps carrying this fallback key are detected as hidden here.\n */\nexport const ROOM_UI_HIDDEN = \"system.fallback_hidden\";\n\n/** userData key for a room's custom border colour. */\nexport const ROOM_UI_BORDER_COLOR = \"room.ui_borderColor\";\n\n/** userData key for a room's custom border thickness (Mudlet clamps it to 1..10). */\nexport const ROOM_UI_BORDER_THICKNESS = \"room.ui_borderThickness\";\n\n/** Whether Mudlet has marked this room hidden on the 2D map. */\nexport function isRoomHidden(room: MapData.Room): boolean {\n const v = room.userData?.[ROOM_UI_HIDDEN];\n return typeof v === \"string\" && v.toLowerCase() === \"true\";\n}\n\n/**\n * Convert a Mudlet QColor string to a CSS colour. Mudlet stores colours via\n * Qt's `QColor::name(HexArgb)` — i.e. `#AARRGGBB` (alpha first), which CSS would\n * misread as `#RRGGBBAA`. Reorder to `#RRGGBB` (dropping a fully-opaque alpha)\n * or `#RRGGBBAA`. Anything else (`#rrggbb`, named, `rgb(...)`) passes through.\n */\nfunction qtColorToCss(value: string): string {\n const m = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{6})$/.exec(value.trim());\n if (!m) return value;\n const [, alpha, rgb] = m;\n return alpha.toLowerCase() === \"ff\" ? `#${rgb}` : `#${rgb}${alpha}`;\n}\n\n/**\n * The room's custom border colour as a CSS colour, or `undefined` when none is\n * set. Mudlet stores it as a Qt `#AARRGGBB` string (see {@link qtColorToCss}).\n */\nexport function getRoomBorderColor(room: MapData.Room): string | undefined {\n const v = room.userData?.[ROOM_UI_BORDER_COLOR];\n return v ? qtColorToCss(v) : undefined;\n}\n\n/** Opacity applied to hidden rooms in {@link HiddenRoomMode} `\"faded\"`. */\nexport const HIDDEN_ROOM_FADE = 0.35;\n\n/**\n * The {@link layoutRoom} options that render a room's hidden state for the given\n * mode (`{}` when the room isn't hidden / the mode draws it normally). Shared by\n * the scene build and the current-room overlay so a redrawn hidden room keeps\n * its fade / dashed border.\n */\nexport function hiddenRoomLayoutOptions(\n room: MapData.Room,\n mode: HiddenRoomMode,\n): {fade?: number; dashedBorder?: boolean} {\n if (!isRoomHidden(room)) return {};\n if (mode === \"faded\") return {fade: HIDDEN_ROOM_FADE};\n if (mode === \"dashed\") return {dashedBorder: true};\n return {};\n}\n\n/**\n * The room's custom border thickness clamped to Mudlet's 1..10 range, or\n * `undefined` when unset or unparseable.\n */\nexport function getRoomBorderThickness(room: MapData.Room): number | undefined {\n const raw = room.userData?.[ROOM_UI_BORDER_THICKNESS];\n if (raw === undefined) return undefined;\n const t = parseInt(raw, 10);\n if (!Number.isFinite(t)) return undefined;\n return Math.min(10, Math.max(1, t));\n}\n","/**\n * Lightweight typed event emitter.\n * Also dispatches DOM CustomEvents on an optional container element\n * for backwards compatibility.\n */\nexport class TypedEventEmitter<EventMap extends Record<string, unknown>> {\n\n private listeners = new Map<keyof EventMap, Set<(detail: any) => void>>();\n private container?: HTMLElement;\n\n /**\n * @param container Optional DOM element to also dispatch CustomEvents on (for backwards compat)\n */\n constructor(container?: HTMLElement) {\n this.container = container;\n }\n\n on<K extends keyof EventMap>(event: K, handler: (detail: EventMap[K]) => void): void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(handler);\n }\n\n off<K extends keyof EventMap>(event: K, handler: (detail: EventMap[K]) => void): void {\n this.listeners.get(event)?.delete(handler);\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n\n emit<K extends keyof EventMap>(event: K, detail: EventMap[K]): void {\n // Typed listeners\n this.listeners.get(event)?.forEach(handler => handler(detail));\n\n // DOM CustomEvent for backwards compat\n if (this.container) {\n this.container.dispatchEvent(\n new CustomEvent(event as string, {detail}),\n );\n }\n }\n}\n","import type IExit from \"../reader/Exit\";\n\n/**\n * How the renderer should treat an exit whose endpoints are not both visible.\n *\n * - `\"full\"` — draw the exit normally (both endpoints are visible, or the\n * lens overrides the default and wants the full line anyway).\n * - `\"stub\"` — draw only a short stub leaving the visible endpoint; the\n * far end isn't shown. This is the \"explored frontier\" look.\n * - `\"hidden\"` — don't draw the exit at all.\n *\n * Cross-area exits (rooms in different `area` ids) are an orthogonal concept\n * decided by raw room data, not by the lens. A lens that returns `\"full\"` for\n * a cross-area exit still gets the cross-area arrow treatment.\n */\nexport type ExitTreatment = \"full\" | \"stub\" | \"hidden\";\n\n/**\n * Visibility filter applied during scene build.\n *\n * `IArea` / `IPlane` / `IMapReader` are pure data — they return every room and\n * every link exit the underlying map defines. The lens decides which of those\n * actually paint, and (optionally) how partially-visible exits should be\n * stubbed or hidden. Multiple concerns (exploration, guild scope, quest\n * overlay, …) compose through {@link composeLenses}.\n */\nexport interface RoomLens {\n isVisible(room: MapData.Room): boolean;\n\n /**\n * Decide how an exit between `a` and `b` should be drawn.\n *\n * Optional — when omitted, treatment is derived from endpoint visibility:\n * both visible → `\"full\"`, one visible → `\"stub\"`, neither → `\"hidden\"`.\n */\n getExitTreatment?(exit: IExit, a: MapData.Room, b: MapData.Room): ExitTreatment;\n\n /**\n * Monotonically increasing version. The renderer caches the value from the\n * last build and rebuilds when it changes. Mutations on a stateful lens\n * (e.g. {@link ExplorationLens.addVisited}) bump this; callers that mutate\n * the lens still call `renderer.refresh()` explicitly.\n */\n getVersion(): number;\n}\n\nexport const ALL_VISIBLE: RoomLens = {\n isVisible: () => true,\n getVersion: () => 0,\n};\n\n/**\n * Default treatment when no lens supplies `getExitTreatment`: derive purely\n * from endpoint visibility.\n */\nexport function defaultExitTreatment(\n lens: RoomLens,\n _exit: IExit,\n a: MapData.Room,\n b: MapData.Room,\n): ExitTreatment {\n const aVis = lens.isVisible(a);\n const bVis = lens.isVisible(b);\n if (aVis && bVis) return \"full\";\n if (aVis || bVis) return \"stub\";\n return \"hidden\";\n}\n","import type {ViewportBounds} from \"../types/Settings\";\nimport {TypedEventEmitter} from \"../TypedEventEmitter\";\n\n/** Pixels-per-world-unit at zoom = 1. Multiply by `Camera.zoom` for actual scale. */\nexport const BASE_SCALE = 75;\n\nfunction easeInOut(t: number): number {\n return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n}\n\n/** Events fired by {@link Camera}. */\nexport type CameraEventMap = {\n /** Any state change: zoom, pan, size, animation tick. */\n change: void;\n};\n\n/**\n * Engine-agnostic camera — owns transform state, drag, and animation.\n *\n * Subscribers ({@link KonvaRenderBackend}, {@link CullingManager}, {@link HitTester},\n * scene overlays) listen to the `change` event to react to view updates.\n *\n * No Konva, no DOM. Only dependency is requestAnimationFrame (with a\n * setTimeout fallback for Node.js).\n */\nexport class Camera extends TypedEventEmitter<CameraEventMap> {\n zoom: number = 1;\n minZoom: number = 0.05;\n position: { x: number; y: number } = {x: 0, y: 0};\n width: number;\n height: number;\n\n /** When true, resizing re-centers on the last panToMapPoint target. */\n centerOnResize: boolean = true;\n\n private dragging = false;\n private dragStart = {x: 0, y: 0};\n private positionAtDragStart = {x: 0, y: 0};\n\n private animationId?: number;\n\n private batchDepth = 0;\n private batchDirty = false;\n\n constructor(width: number, height: number) {\n super();\n this.width = width;\n this.height = height;\n }\n\n getScale(): number {\n return BASE_SCALE * this.zoom;\n }\n\n setZoom(zoom: number): boolean {\n const clamped = Math.max(this.minZoom, zoom);\n if (this.zoom === clamped) return false;\n this.zoom = clamped;\n this.notify();\n return true;\n }\n\n /** Zoom keeping the center of the viewport fixed. */\n zoomToCenter(zoom: number): boolean {\n const clamped = Math.max(this.minZoom, zoom);\n if (this.zoom === clamped) return false;\n\n const oldScale = this.getScale();\n const centerX = this.width / 2;\n const centerY = this.height / 2;\n\n const centerMapPoint = {\n x: (centerX - this.position.x) / oldScale,\n y: (centerY - this.position.y) / oldScale,\n };\n\n this.zoom = clamped;\n const newScale = this.getScale();\n\n this.position = {\n x: centerX - centerMapPoint.x * newScale,\n y: centerY - centerMapPoint.y * newScale,\n };\n\n this.notify();\n return true;\n }\n\n /** Zoom keeping a specific screen point fixed (for mouse wheel zoom). */\n zoomToPoint(zoom: number, screenX: number, screenY: number): boolean {\n const oldScale = this.getScale();\n const mapPoint = {\n x: (screenX - this.position.x) / oldScale,\n y: (screenY - this.position.y) / oldScale,\n };\n\n const clamped = Math.max(this.minZoom, zoom);\n if (this.zoom === clamped) return false;\n this.zoom = clamped;\n\n const newScale = this.getScale();\n this.position = {\n x: screenX - mapPoint.x * newScale,\n y: screenY - mapPoint.y * newScale,\n };\n this.notify();\n return true;\n }\n\n /** Bounds of the visible area in map space. */\n getViewportBounds(): ViewportBounds {\n const scale = this.getScale();\n return {\n minX: (0 - this.position.x) / scale,\n maxX: (this.width - this.position.x) / scale,\n minY: (0 - this.position.y) / scale,\n maxY: (this.height - this.position.y) / scale,\n };\n }\n\n /**\n * Map-space bounds to use for culling. When `cullingBounds` is a\n * screen-pixel sub-rect it is converted to map space via the current\n * camera transform; otherwise the full viewport is returned.\n */\n getCullingViewport(cullingBounds?: {x: number; y: number; width: number; height: number} | null): ViewportBounds {\n if (!cullingBounds) return this.getViewportBounds();\n const scale = this.getScale();\n const pos = this.position;\n return {\n minX: (cullingBounds.x - pos.x) / scale,\n maxX: (cullingBounds.x + cullingBounds.width - pos.x) / scale,\n minY: (cullingBounds.y - pos.y) / scale,\n maxY: (cullingBounds.y + cullingBounds.height - pos.y) / scale,\n };\n }\n\n /**\n * Create a Camera whose full viewport covers exactly the given world-space bounds.\n * Used by headless exporters that need a Camera but have no real display.\n */\n static forMapBounds(minX: number, maxX: number, minY: number, maxY: number): Camera {\n const scale = BASE_SCALE;\n const camera = new Camera((maxX - minX) * scale, (maxY - minY) * scale);\n camera.zoom = 1;\n camera.position = {x: -minX * scale, y: -minY * scale};\n return camera;\n }\n\n /**\n * Create a Camera from an explicit render-camera transform (scale + offset).\n * Used by {@link CanvasExporter} to derive culling bounds from the already-\n * computed fitted transform without re-doing the letterbox math.\n */\n static forRenderCamera(width: number, height: number, scale: number, offsetX: number, offsetY: number): Camera {\n const camera = new Camera(width, height);\n camera.zoom = scale / BASE_SCALE;\n camera.position = {x: offsetX, y: offsetY};\n return camera;\n }\n\n /** Convert client/screen coordinates to map coordinates. */\n clientToMapPoint(clientX: number, clientY: number, containerOffset?: { left: number; top: number }) {\n const stageX = clientX - (containerOffset?.left ?? 0);\n const stageY = clientY - (containerOffset?.top ?? 0);\n const scale = this.getScale();\n if (!scale) return null;\n return {\n x: (stageX - this.position.x) / scale,\n y: (stageY - this.position.y) / scale,\n };\n }\n\n /** Center on a map coordinate, instantly. */\n panToMapPoint(x: number, y: number) {\n const scale = this.getScale();\n this.position = {\n x: this.width / 2 - x * scale,\n y: this.height / 2 - y * scale,\n };\n this.notify();\n }\n\n /** Center on a map coordinate, with optional animation. */\n panToMapPointAnimated(x: number, y: number, instant: boolean) {\n if (instant) {\n this.panToMapPoint(x, y);\n return;\n }\n\n const startPos = {...this.position};\n const scale = this.getScale();\n const targetPos = {\n x: this.width / 2 - x * scale,\n y: this.height / 2 - y * scale,\n };\n\n this.animate(200, (t) => {\n this.position = {\n x: startPos.x + (targetPos.x - startPos.x) * t,\n y: startPos.y + (targetPos.y - startPos.y) * t,\n };\n });\n }\n\n /**\n * Compute the zoom level that would fit the given map bounds in the current\n * viewport (with the same padding/insets as {@link fitToMapBounds}).\n */\n computeFitZoom(\n minX: number,\n maxX: number,\n minY: number,\n maxY: number,\n insets?: { top?: number; right?: number; bottom?: number; left?: number },\n ): number {\n const mapW = maxX - minX;\n const mapH = maxY - minY;\n if (mapW <= 0 || mapH <= 0) return this.zoom;\n\n const top = insets?.top ?? 0;\n const right = insets?.right ?? 0;\n const bottom = insets?.bottom ?? 0;\n const left = insets?.left ?? 0;\n const availW = Math.max(1, this.width - left - right);\n const availH = Math.max(1, this.height - top - bottom);\n\n const padding = 2;\n const zoomX = availW / ((mapW + padding * 2) * BASE_SCALE);\n const zoomY = availH / ((mapH + padding * 2) * BASE_SCALE);\n return Math.min(zoomX, zoomY);\n }\n\n /**\n * Fit the camera to show the given map bounds with padding.\n * Optional `insets` (screen pixels) reserve space at each edge.\n */\n fitToMapBounds(\n minX: number,\n maxX: number,\n minY: number,\n maxY: number,\n insets?: { top?: number; right?: number; bottom?: number; left?: number },\n ) {\n const mapW = maxX - minX;\n const mapH = maxY - minY;\n if (mapW <= 0 || mapH <= 0) return;\n\n const top = insets?.top ?? 0;\n const left = insets?.left ?? 0;\n const availW = Math.max(1, this.width - left - (insets?.right ?? 0));\n const availH = Math.max(1, this.height - top - (insets?.bottom ?? 0));\n\n this.zoom = this.computeFitZoom(minX, maxX, minY, maxY, insets);\n this.minZoom = this.zoom;\n\n const scale = this.getScale();\n const centerMapX = (minX + maxX) / 2;\n const centerMapY = (minY + maxY) / 2;\n this.position = {\n x: left + availW / 2 - centerMapX * scale,\n y: top + availH / 2 - centerMapY * scale,\n };\n this.notify();\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.notify();\n }\n\n // --- Drag ---\n\n startDrag(screenX: number, screenY: number) {\n this.cancelAnimation();\n this.dragging = true;\n this.dragStart = {x: screenX, y: screenY};\n this.positionAtDragStart = {...this.position};\n }\n\n updateDrag(screenX: number, screenY: number) {\n if (!this.dragging) return;\n this.position = {\n x: this.positionAtDragStart.x + (screenX - this.dragStart.x),\n y: this.positionAtDragStart.y + (screenY - this.dragStart.y),\n };\n this.notify();\n }\n\n endDrag() {\n this.dragging = false;\n }\n\n isDragging(): boolean {\n return this.dragging;\n }\n\n // --- Animation ---\n\n private animate(durationMs: number, update: (t: number) => void) {\n this.cancelAnimation();\n\n const start = performance.now();\n const raf = typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : (cb: FrameRequestCallback) => setTimeout(() => cb(performance.now()), 16) as unknown as number;\n\n const step = (now: number) => {\n const elapsed = now - start;\n const progress = Math.min(elapsed / durationMs, 1);\n update(easeInOut(progress));\n this.notify();\n\n if (progress < 1) {\n this.animationId = raf(step);\n } else {\n this.animationId = undefined;\n }\n };\n\n this.animationId = raf(step);\n }\n\n cancelAnimation() {\n if (this.animationId !== undefined) {\n const caf = typeof cancelAnimationFrame !== 'undefined' ? cancelAnimationFrame : (id: number) => clearTimeout(id);\n caf(this.animationId);\n this.animationId = undefined;\n }\n }\n\n isAnimating(): boolean {\n return this.animationId !== undefined;\n }\n\n /**\n * Run a function that performs multiple camera mutations and emit only one\n * `change` event at the end (if anything actually mutated). Use this when a\n * single logical operation needs to touch more than one camera field —\n * resize-then-recenter, for example — so subscribers never observe a\n * transient inconsistent state (new size with stale position).\n *\n * Nested batches are supported: only the outermost batch emits.\n */\n batch<T>(fn: () => T): T {\n this.batchDepth++;\n try {\n return fn();\n } finally {\n this.batchDepth--;\n if (this.batchDepth === 0 && this.batchDirty) {\n this.batchDirty = false;\n this.emit('change', undefined);\n }\n }\n }\n\n private notify() {\n if (this.batchDepth > 0) {\n this.batchDirty = true;\n return;\n }\n this.emit('change', undefined);\n }\n}\n","/**\n * Forward / inverse 2D coordinate transform. Used by camera-aware code that\n * needs to translate between world space (the map's flat XY) and scene space\n * (whatever the active style projects into — e.g. an isometric diamond).\n *\n * `IDENTITY_TRANSFORM` returns the input unchanged; flat styles (Parchment,\n * Sketchy, plain identity) use it for both the forward and inverse direction.\n */\n\nexport type CoordFn = (x: number, y: number) => { x: number; y: number };\n\nexport const IDENTITY_TRANSFORM: CoordFn = (x, y) => ({x, y});\n","/**\n * CullingManager — RAF-debounced scheduler for the shared clip step.\n *\n * The actual shape-filtering logic lives in {@link clipSceneToViewport} so\n * every rendering path (interactive Konva, SVG export, PNG export) uses the\n * same predicate. CullingManager's jobs are:\n *\n * - Scheduling: batches rapid camera changes into one clip pass per frame via\n * `requestAnimationFrame` (`scheduleCulling`).\n * - Coordinate transform: stores the world→scene projection so callers can\n * retrieve it without threading it through every call site.\n */\n\nimport type {Settings} from \"./types/Settings\";\nimport type {CoordFn} from \"./coord/CoordFn\";\nimport {IDENTITY_TRANSFORM} from \"./coord/CoordFn\";\n\nexport class CullingManager {\n private cullingScheduled = false;\n private coordinateTransform: CoordFn = IDENTITY_TRANSFORM;\n\n constructor(\n private readonly settings: Settings,\n private readonly onCullingNeeded: () => void,\n ) {}\n\n setCoordinateTransform(fn: CoordFn) {\n this.coordinateTransform = fn;\n }\n\n getCoordinateTransform(): CoordFn {\n return this.coordinateTransform;\n }\n\n /** Schedule a cull pass on the next animation frame (no-op if already scheduled). */\n scheduleCulling() {\n if (this.cullingScheduled) return;\n this.cullingScheduled = true;\n const cb = () => {\n this.cullingScheduled = false;\n this.onCullingNeeded();\n };\n if (typeof requestAnimationFrame !== 'undefined') {\n requestAnimationFrame(cb);\n } else {\n cb();\n }\n }\n\n /** Run a cull pass immediately (used when mode changes). */\n updateCulling() {\n this.onCullingNeeded();\n }\n}\n","export type Kind = \"exit\" | \"specialExit\";\n\nexport default interface IExit {\n a: number;\n b: number;\n aDir?: MapData.direction;\n bDir?: MapData.direction;\n kind?: Kind;\n zIndex: number[];\n}\n\nexport type { IExit };\n\n/** @deprecated Use {@link IExit}. Retained as a structural alias for backwards compatibility. */\nexport type Exit = IExit;\n\nexport const regularExits: MapData.direction[] = [\"north\", \"south\", \"east\", \"west\", \"northeast\", \"northwest\", \"southeast\", \"southwest\"];\nexport const shortTolong: Record<string, MapData.direction> = {\n \"n\": \"north\",\n \"s\": \"south\",\n \"e\": \"east\",\n \"w\": \"west\",\n \"ne\": \"northeast\",\n \"nw\": \"northwest\",\n \"se\": \"southeast\",\n \"sw\": \"southwest\"\n}\nexport const longToShort: Record<MapData.direction, string> = {\n \"north\": \"n\",\n \"south\": \"s\",\n \"east\": \"e\",\n \"west\": \"w\",\n \"northeast\": \"ne\",\n \"northwest\": \"nw\",\n \"southeast\": \"se\",\n \"southwest\": \"sw\",\n \"up\": \"u\",\n \"down\": \"d\",\n \"in\": \"i\",\n \"out\": \"o\"\n}\n","export type PlanarDirection =\n | \"north\"\n | \"south\"\n | \"east\"\n | \"west\"\n | \"northeast\"\n | \"northwest\"\n | \"southeast\"\n | \"southwest\";\n\nconst planarDirectionOffsets: Record<PlanarDirection, {x: number; y: number}> = {\n north: {x: 0, y: -1},\n south: {x: 0, y: 1},\n east: {x: 1, y: 0},\n west: {x: -1, y: 0},\n northeast: {x: 1, y: -1},\n northwest: {x: -1, y: -1},\n southeast: {x: 1, y: 1},\n southwest: {x: -1, y: 1},\n};\n\nexport const planarDirections: PlanarDirection[] = [\n \"north\",\n \"south\",\n \"east\",\n \"west\",\n \"northeast\",\n \"northwest\",\n \"southeast\",\n \"southwest\",\n];\n\nexport const oppositeDirections: Record<PlanarDirection, PlanarDirection> = {\n north: \"south\",\n south: \"north\",\n east: \"west\",\n west: \"east\",\n northeast: \"southwest\",\n northwest: \"southeast\",\n southeast: \"northwest\",\n southwest: \"northeast\",\n};\n\nfunction isPlanarDirection(direction: MapData.direction | undefined): direction is PlanarDirection {\n if (!direction) {\n return false;\n }\n return Object.prototype.hasOwnProperty.call(planarDirectionOffsets, direction);\n}\n\nexport function movePoint(\n x: number,\n y: number,\n direction?: MapData.direction,\n distance: number = 1,\n) {\n if (!isPlanarDirection(direction)) {\n return {x, y};\n }\n\n const offset = planarDirectionOffsets[direction];\n return {\n x: x + offset.x * distance,\n y: y + offset.y * distance,\n };\n}\n\n/**\n * Calculate the edge point of a rounded rectangle for a given direction.\n * Cardinal directions use the flat edge (same as rectangle).\n * Diagonal directions intersect the corner arc.\n */\nexport function movePointRoundedRect(\n x: number,\n y: number,\n direction?: MapData.direction,\n distance: number = 1,\n cornerRadius: number = 0,\n) {\n if (!isPlanarDirection(direction)) {\n return {x, y};\n }\n\n const offset = planarDirectionOffsets[direction];\n const isDiagonal = offset.x !== 0 && offset.y !== 0;\n\n if (!isDiagonal || cornerRadius <= 0) {\n return movePoint(x, y, direction, distance);\n }\n\n // For diagonal directions, find the point on the rounded corner arc.\n // Arc center is (distance - r) from room center along each axis.\n // The intersection with a 45° line from center is at arcCenter + r/√2.\n const edgeDist = (distance - cornerRadius) + cornerRadius / Math.SQRT2;\n\n return {\n x: x + offset.x * edgeDist,\n y: y + offset.y * edgeDist,\n };\n}\n\n/**\n * Calculate the edge point of a circle for a given direction\n * For circles, we need to calculate the exact point on the circle's circumference\n * based on the angle to the direction\n */\nexport function movePointCircle(\n x: number,\n y: number,\n direction?: MapData.direction,\n radius: number = 1,\n) {\n if (!isPlanarDirection(direction)) {\n return {x, y};\n }\n\n const offset = planarDirectionOffsets[direction];\n // Calculate the angle from the direction offset\n const angle = Math.atan2(offset.y, offset.x);\n\n // Calculate the point on the circle's edge\n return {\n x: x + Math.cos(angle) * radius,\n y: y + Math.sin(angle) * radius,\n };\n}\n","import IExit, {longToShort, shortTolong, regularExits} from \"./reader/Exit\";\nimport type {IMapReader} from \"./reader/MapReader\";\nimport type {Settings} from \"./types/Settings\";\nimport {movePoint, movePointCircle, movePointRoundedRect} from \"./directions\";\n\nconst Colors = {\n OPEN_DOOR: 'rgb(10, 155, 10)',\n CLOSED_DOOR: 'rgb(226, 205, 59)',\n LOCKED_DOOR: 'rgb(155, 10, 10)',\n ONE_WAY_FILL: 'rgb(155, 10, 10)'\n}\n\nexport type ExitDrawLine = {\n points: number[];\n stroke: string;\n strokeWidth: number;\n dash?: number[];\n};\n\nexport type ExitDrawArrow = {\n points: number[];\n pointerLength: number;\n pointerWidth: number;\n stroke: string;\n strokeWidth: number;\n fill: string;\n dash?: number[];\n};\n\nexport type ExitDrawDoor = {\n x: number;\n y: number;\n width: number;\n height: number;\n stroke: string;\n strokeWidth: number;\n};\n\nexport type ExitDrawData = {\n lines: ExitDrawLine[];\n arrows: ExitDrawArrow[];\n doors: ExitDrawDoor[];\n bounds: { x: number; y: number; width: number; height: number };\n /** Set when this exit leads to a room in a different area (cross-area exit). */\n targetRoomId?: number;\n /** Source room center in map coords — used to compute label direction. */\n from?: { x: number; y: number };\n /** Arrow tip / far endpoint in map coords — anchor for placed labels. */\n tip?: { x: number; y: number };\n /** Stroke colour of the rendered arrow — used to colour area-exit labels. */\n arrowColor?: string;\n};\n\nfunction getDoorColor(doorType: 1 | 2 | 3) {\n switch (doorType) {\n case 1:\n return Colors.OPEN_DOOR\n case 2:\n return Colors.CLOSED_DOOR\n default:\n return Colors.LOCKED_DOOR\n }\n}\n\nexport default class ExitRenderer {\n\n private mapReader: IMapReader;\n private readonly settings: Settings;\n\n constructor(mapReader: IMapReader, settings: Settings) {\n this.mapReader = mapReader;\n this.settings = settings;\n }\n\n /**\n * Get the edge point of a room based on its shape.\n * The inset accounts for the inner border so exit lines reach the visible room edge.\n */\n private getRoomEdgePoint(x: number, y: number, direction: MapData.direction, distance: number) {\n const inset = this.settings.borders ? this.settings.lineWidth / 2 : 0;\n const d = distance - inset;\n if (this.settings.roomShape === \"circle\") {\n return movePointCircle(x, y, direction, d);\n } else if (this.settings.roomShape === \"roundedRectangle\") {\n return movePointRoundedRect(x, y, direction, d, this.settings.roomSize * 0.2);\n } else {\n return movePoint(x, y, direction, d);\n }\n }\n\n renderData(exit: IExit, zIndex: number): ExitDrawData | undefined {\n return this.renderDataWithColor(exit, this.settings.lineColor, zIndex);\n }\n\n renderDataWithColor(exit: IExit, color: string, zIndex: number): ExitDrawData | undefined {\n const aIsRegular = exit.aDir && regularExits.includes(exit.aDir);\n const bIsRegular = exit.bDir && regularExits.includes(exit.bDir);\n\n if (aIsRegular && bIsRegular) {\n return this.renderTwoWayExitData(exit, color, zIndex);\n } else if (aIsRegular || bIsRegular) {\n const regularDir = aIsRegular ? 'a' : 'b';\n return this.renderOneWayExitData(exit, color, regularDir);\n }\n return;\n }\n\n private renderTwoWayExitData(exit: IExit, color: string, zIndex: number): ExitDrawData | undefined {\n const sourceRoom = this.mapReader.getRoom(exit.a);\n const targetRoom = this.mapReader.getRoom(exit.b);\n if (!sourceRoom || !targetRoom || !exit.aDir || !exit.bDir) return;\n if (sourceRoom.customLines[longToShort[exit.aDir]] && targetRoom.customLines[longToShort[exit.bDir]]) return;\n if (sourceRoom.z !== targetRoom.z) {\n if (zIndex !== targetRoom.z && sourceRoom.customLines[longToShort[exit.aDir]]) return;\n if (zIndex !== sourceRoom.z && targetRoom.customLines[longToShort[exit.bDir]]) return;\n }\n\n const p1 = this.getRoomEdgePoint(sourceRoom.x, sourceRoom.y, exit.aDir, this.settings.roomSize / 2);\n const p2 = this.getRoomEdgePoint(targetRoom.x, targetRoom.y, exit.bDir, this.settings.roomSize / 2);\n const points = [p1.x, p1.y, p2.x, p2.y];\n\n const lines: ExitDrawLine[] = [{ points, stroke: color, strokeWidth: this.settings.lineWidth }];\n const doors: ExitDrawDoor[] = [];\n const doorType = sourceRoom.doors[longToShort[exit.aDir]] ?? targetRoom.doors[longToShort[exit.bDir]];\n if (doorType) {\n const dx = points[0] + (points[2] - points[0]) / 2;\n const dy = points[1] + (points[3] - points[1]) / 2;\n doors.push({\n x: dx - this.settings.roomSize / 4,\n y: dy - this.settings.roomSize / 4,\n width: this.settings.roomSize / 2,\n height: this.settings.roomSize / 2,\n stroke: getDoorColor(doorType),\n strokeWidth: this.settings.lineWidth,\n });\n }\n\n const minX = Math.min(points[0], points[2]);\n const maxX = Math.max(points[0], points[2]);\n const minY = Math.min(points[1], points[3]);\n const maxY = Math.max(points[1], points[3]);\n // If rooms are on different z-levels, make the exit clickable to navigate to the other z\n const crossZTarget = sourceRoom.z !== targetRoom.z\n ? (sourceRoom.z === zIndex ? targetRoom.id : sourceRoom.id)\n : undefined;\n // Two-way cross-area exits also deserve area-exit metadata so labels can be placed.\n const targetRoomId = crossZTarget ?? (sourceRoom.area !== targetRoom.area ? targetRoom.id : undefined);\n const labelMeta = targetRoomId !== undefined\n ? {\n from: { x: sourceRoom.x, y: sourceRoom.y },\n tip: { x: p2.x, y: p2.y },\n arrowColor: this.mapReader.getColorValue(targetRoom.env),\n }\n : {};\n return {\n lines, arrows: [], doors,\n bounds: { x: minX, y: minY, width: maxX - minX, height: maxY - minY },\n targetRoomId,\n ...labelMeta,\n };\n }\n\n private renderOneWayExitData(exit: IExit, color: string, fromSide?: 'a' | 'b'): ExitDrawData | undefined {\n const useA = fromSide === 'a' || (!fromSide && exit.aDir);\n const sourceRoom = useA ? this.mapReader.getRoom(exit.a) : this.mapReader.getRoom(exit.b);\n const targetRoom = useA ? this.mapReader.getRoom(exit.b) : this.mapReader.getRoom(exit.a);\n const dir = useA ? exit.aDir : exit.bDir;\n if (!dir || !sourceRoom || !targetRoom || !regularExits.includes(dir) || sourceRoom.customLines[longToShort[dir] || dir]) return;\n\n if (sourceRoom.area != targetRoom.area && dir) {\n const targetEnvColor = this.mapReader.getColorValue(targetRoom.env);\n const start = this.getRoomEdgePoint(sourceRoom.x, sourceRoom.y, dir, this.settings.roomSize / 2);\n const end = movePoint(sourceRoom.x, sourceRoom.y, dir, this.settings.roomSize * 1.5);\n const stroke = targetEnvColor;\n return {\n lines: [],\n arrows: [{\n points: [start.x, start.y, end.x, end.y],\n pointerLength: 0.3, pointerWidth: 0.3,\n strokeWidth: this.settings.lineWidth * 1.4,\n stroke, fill: stroke,\n }],\n doors: [],\n bounds: { x: Math.min(start.x, end.x), y: Math.min(start.y, end.y), width: Math.abs(end.x - start.x), height: Math.abs(end.y - start.y) },\n targetRoomId: targetRoom.id,\n from: { x: sourceRoom.x, y: sourceRoom.y },\n tip: { x: end.x, y: end.y },\n arrowColor: stroke,\n };\n }\n\n const isCrossZone = targetRoom.area !== sourceRoom.area || targetRoom.z !== sourceRoom.z;\n let targetPoint = { x: targetRoom.x, y: targetRoom.y };\n if (isCrossZone) {\n targetPoint = movePoint(sourceRoom.x, sourceRoom.y, dir, this.settings.roomSize / 2);\n }\n\n const startPoint = movePoint(sourceRoom.x, sourceRoom.y, dir, 0.3);\n const midX = startPoint.x - (startPoint.x - targetPoint.x) / 2;\n const midY = startPoint.y - (startPoint.y - targetPoint.y) / 2;\n const edgePoint = this.getRoomEdgePoint(sourceRoom.x, sourceRoom.y, dir, this.settings.roomSize / 2);\n const linePoints = [edgePoint.x, edgePoint.y, targetPoint.x, targetPoint.y];\n\n const allX = [edgePoint.x, targetPoint.x, midX];\n const allY = [edgePoint.y, targetPoint.y, midY];\n const minX = Math.min(...allX);\n const maxX = Math.max(...allX);\n const minY = Math.min(...allY);\n const maxY = Math.max(...allY);\n\n return {\n lines: [{ points: linePoints, stroke: color, strokeWidth: this.settings.lineWidth, dash: [0.1, 0.05] }],\n arrows: [{\n points: [linePoints[0], linePoints[1], midX, midY],\n pointerLength: 0.5, pointerWidth: 0.35,\n strokeWidth: this.settings.lineWidth * 1.4,\n stroke: color, fill: Colors.ONE_WAY_FILL,\n dash: [0.1, 0.05],\n }],\n doors: [],\n bounds: { x: minX, y: minY, width: maxX - minX, height: maxY - minY },\n ...(isCrossZone ? { targetRoomId: targetRoom.id } : {}),\n };\n }\n\n /**\n * Returns hit-zone bounds for special exits (custom lines) that lead to rooms in another area.\n */\n /**\n * Hit zones for cross-area inner exits (up/down/in/out). These are drawn as\n * triangles inside the room, not through the exit pipeline, so they need\n * their own plumbing for area-exit labelling and clickability.\n */\n getInnerExitAreaTargets(room: MapData.Room): {\n bounds: { x: number; y: number; width: number; height: number };\n targetRoomId: number;\n from: { x: number; y: number };\n tip: { x: number; y: number };\n arrowColor: string;\n }[] {\n const innerExits: MapData.direction[] = ['up', 'down', 'in', 'out'];\n const rs = this.settings.roomSize;\n const results: {\n bounds: { x: number; y: number; width: number; height: number };\n targetRoomId: number;\n from: { x: number; y: number };\n tip: { x: number; y: number };\n arrowColor: string;\n }[] = [];\n for (const dir of innerExits) {\n const targetId = room.exits[dir];\n if (targetId === undefined) continue;\n const targetRoom = this.mapReader.getRoom(targetId);\n if (!targetRoom || targetRoom.area === room.area) continue;\n // No natural 2D direction for up/down/in/out — anchor at the room and\n // let the label placer fall back to compass ring placement.\n results.push({\n bounds: { x: room.x - rs / 4, y: room.y - rs / 4, width: rs / 2, height: rs / 2 },\n targetRoomId: targetId,\n from: { x: room.x, y: room.y },\n tip: { x: room.x, y: room.y },\n arrowColor: this.mapReader.getColorValue(targetRoom.env),\n });\n }\n return results;\n }\n\n getSpecialExitAreaTargets(room: MapData.Room): {\n bounds: { x: number; y: number; width: number; height: number };\n targetRoomId: number;\n from: { x: number; y: number };\n tip: { x: number; y: number };\n arrowColor: string;\n }[] {\n const results: {\n bounds: { x: number; y: number; width: number; height: number };\n targetRoomId: number;\n from: { x: number; y: number };\n tip: { x: number; y: number };\n arrowColor: string;\n }[] = [];\n // shortTolong only covers the 8 cardinal directions, so up/down/in/out\n // customLines need their own resolution.\n const vertShortToLong: Record<string, MapData.direction> = {\n u: 'up', d: 'down', i: 'in', o: 'out',\n };\n for (const [dir, line] of Object.entries(room.customLines)) {\n let targetId: number | undefined = room.specialExits[dir];\n if (targetId === undefined) {\n const longDir = shortTolong[dir] ?? vertShortToLong[dir];\n if (longDir) {\n targetId = room.exits[longDir] ?? room.specialExits[longDir];\n }\n }\n if (targetId === undefined) {\n // Fallback: dir may already be the long form key.\n targetId = room.exits[dir as MapData.direction] ?? room.specialExits[dir];\n }\n if (targetId === undefined) continue;\n const targetRoom = this.mapReader.getRoom(targetId);\n if (!targetRoom || (targetRoom.area === room.area && targetRoom.z === room.z)) continue;\n const points = [room.x, room.y];\n line.points.reduce((acc, point) => { acc.push(point.x, -point.y); return acc; }, points);\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (let i = 0; i < points.length; i += 2) {\n minX = Math.min(minX, points[i]);\n maxX = Math.max(maxX, points[i]);\n minY = Math.min(minY, points[i + 1]);\n maxY = Math.max(maxY, points[i + 1]);\n }\n // Tip = farthest custom-line vertex from the source room.\n const lastIdx = points.length - 2;\n const tip = { x: points[lastIdx], y: points[lastIdx + 1] };\n results.push({\n bounds: { x: minX, y: minY, width: maxX - minX, height: maxY - minY },\n targetRoomId: targetId,\n from: { x: room.x, y: room.y },\n tip,\n arrowColor: this.mapReader.getColorValue(targetRoom.env),\n });\n }\n return results;\n }\n}\n","import type {Settings} from \"../types/Settings\";\nimport {movePoint, movePointCircle, movePointRoundedRect} from \"../directions\";\n\nconst dirNumbers: Record<number, MapData.direction> = {\n 1: \"north\", 2: \"northeast\", 3: \"northwest\", 4: \"east\", 5: \"west\",\n 6: \"south\", 7: \"southeast\", 8: \"southwest\", 9: \"up\", 10: \"down\",\n 11: \"in\", 12: \"out\",\n};\n\nexport type StubData = {\n roomId: number;\n direction: MapData.direction;\n x1: number; y1: number;\n x2: number; y2: number;\n stroke: string;\n strokeWidth: number;\n};\n\nfunction getRoomEdgePoint(settings: Settings, x: number, y: number, direction: MapData.direction, distance: number) {\n if (settings.roomShape === \"circle\") return movePointCircle(x, y, direction, distance);\n if (settings.roomShape === \"roundedRectangle\") return movePointRoundedRect(x, y, direction, distance, settings.roomSize * 0.2);\n return movePoint(x, y, direction, distance);\n}\n\n/**\n * Compute stub line data for a room's one-way exit indicators.\n */\nexport function computeStubs(room: MapData.Room, settings: Settings, colorOverride?: string): StubData[] {\n const stubs: StubData[] = [];\n for (const stub of room.stubs) {\n const direction = dirNumbers[stub];\n if (!direction) continue;\n const start = getRoomEdgePoint(settings, room.x, room.y, direction, settings.roomSize / 2);\n const end = movePoint(room.x, room.y, direction, settings.roomSize / 2 + 0.5);\n stubs.push({\n roomId: room.id,\n direction,\n x1: start.x, y1: start.y,\n x2: end.x, y2: end.y,\n stroke: colorOverride ?? settings.lineColor,\n strokeWidth: settings.lineWidth,\n });\n }\n return stubs;\n}\n","import type {Settings} from \"../types/Settings\";\n\nconst DoorColors: Record<number, string> = {\n 1: 'rgb(10, 155, 10)',\n 2: 'rgb(226, 205, 59)',\n 3: 'rgb(155, 10, 10)',\n};\n\nexport type SpecialExitLineData = {\n points: number[];\n stroke: string;\n strokeWidth: number;\n dash?: number[];\n};\n\nexport type SpecialExitArrowData = {\n tipX: number; tipY: number;\n x1: number; y1: number;\n x2: number; y2: number;\n fill: string;\n stroke: string;\n strokeWidth: number;\n};\n\nexport type SpecialExitDoorData = {\n x: number; y: number;\n width: number; height: number;\n stroke: string;\n strokeWidth: number;\n};\n\nexport type SpecialExitData = {\n /** Exit name (customLines key) — short or long form as stored on the room. */\n dir: string;\n line: SpecialExitLineData;\n arrow?: SpecialExitArrowData;\n door?: SpecialExitDoorData;\n};\n\n/**\n * Compute draw data for all custom lines (special exits) on a room.\n */\nexport function computeSpecialExits(room: MapData.Room, settings: Settings, colorOverride?: string): SpecialExitData[] {\n const results: SpecialExitData[] = [];\n\n for (const [dir, line] of Object.entries(room.customLines)) {\n const points: number[] = [room.x, room.y];\n for (const pt of line.points) {\n points.push(pt.x, -pt.y);\n }\n\n const strokeColor = colorOverride ?? `rgb(${line.attributes.color.r}, ${line.attributes.color.g}, ${line.attributes.color.b})`;\n let dash: number[] | undefined;\n if (line.attributes.style === \"dot line\") {\n dash = [0.05, 0.05];\n } else if (line.attributes.style === \"dash line\") {\n dash = [0.4, 0.2];\n } else if (line.attributes.style === \"dash dot line\") {\n dash = [0.4, 0.15, 0.05, 0.15];\n } else if (line.attributes.style === \"dash dot dot line\") {\n dash = [0.4, 0.15, 0.05, 0.15, 0.05, 0.15];\n }\n\n const lineData: SpecialExitLineData = {\n points,\n stroke: strokeColor,\n strokeWidth: settings.lineWidth,\n dash,\n };\n\n let arrow: SpecialExitArrowData | undefined;\n if (line.attributes.arrow && points.length >= 4) {\n const li = points.length - 2;\n const tipX = points[li], tipY = points[li + 1];\n const prevX = points[li - 2], prevY = points[li - 1];\n const angle = Math.atan2(tipY - prevY, tipX - prevX);\n const pl = 0.3, pw = 0.1;\n arrow = {\n tipX, tipY,\n x1: tipX - pl * Math.cos(angle - Math.atan2(pw, pl)),\n y1: tipY - pl * Math.sin(angle - Math.atan2(pw, pl)),\n x2: tipX - pl * Math.cos(angle + Math.atan2(pw, pl)),\n y2: tipY - pl * Math.sin(angle + Math.atan2(pw, pl)),\n fill: strokeColor,\n stroke: strokeColor,\n strokeWidth: settings.lineWidth,\n };\n }\n\n let door: SpecialExitDoorData | undefined;\n const doorType = room.doors[dir];\n if (doorType && points.length >= 4) {\n const dx = points[0] + (points[2] - points[0]) / 2;\n const dy = points[1] + (points[3] - points[1]) / 2;\n const s = settings.roomSize / 2;\n door = {\n x: dx - s / 2, y: dy - s / 2,\n width: s, height: s,\n stroke: DoorColors[doorType] ?? DoorColors[3],\n strokeWidth: settings.lineWidth,\n };\n }\n\n results.push({dir, line: lineData, arrow, door});\n }\n\n return results;\n}\n","/**\n * Pixel-accurate vertical centering helper.\n *\n * The canvas TextMetrics API (actualBoundingBoxAscent / Descent) is unreliable\n * across browsers and fonts. Instead we draw the glyph on an offscreen canvas\n * with a known baseline position, scan the actual rendered pixels to find the\n * true visual top and bottom, and derive the offset we need.\n *\n * We use textBaseline='alphabetic' so that the result is consistent with the\n * original rendering code (which used alphabetic + a hard-coded 0.35em offset).\n * For a typical uppercase letter the function returns ~0.35 (same as the old\n * magic number), but for glyphs that extend below the baseline (e.g. \"[]\") it\n * returns a smaller value, shifting them up to their true visual centre.\n *\n * Return value: multiply by fontSize and add to room.y; use with\n * ctx.textBaseline = 'alphabetic'\n * ctx.fillText(text, room.x, room.y + result * fontSize)\n */\n\nconst MEASURE_PX = 72; // large enough for sub-pixel accuracy\nconst CANVAS_PX = 200;\nconst BASELINE_Y = 120; // baseline well below mid so ascenders always fit\n\nimport Konva from \"konva\";\n\nexport interface BaselineResult {\n /** Offset from room-centre to alphabetic baseline, as a ratio of fontSize (for SVG). */\n baselineRatio: number;\n /**\n * Correction for Konva's built-in verticalAlign='middle', as a ratio of fontSize.\n * Konva centres text using font-wide bounding-box metrics (measured from 'M'),\n * which is wrong for glyphs whose visual extent differs (e.g. \"[]\").\n * Apply as offsetY = konvaCorrectionRatio * fontSize (positive = shift up).\n */\n konvaCorrectionRatio: number;\n}\n\nconst cache = new Map<string, BaselineResult>();\n\nlet _canvas: any = null;\n\nfunction getCanvas(): any {\n if (!_canvas) {\n _canvas = Konva.Util.createCanvasElement();\n _canvas.width = CANVAS_PX;\n _canvas.height = CANVAS_PX;\n }\n return _canvas;\n}\n\n/**\n * Returns the Y offset from room-centre to alphabetic baseline, as a ratio of\n * fontSize, so that the glyph appears visually centred.\n *\n * For a standard uppercase letter (no descenders) this is close to\n * capHeight/2 ≈ 0.35. For characters that extend below the baseline the\n * returned value is smaller, which moves the rendered text upward.\n */\nexport function measureTextBaselineOffset(text: string, fontFamily: string): BaselineResult {\n const cacheKey = `${text}::${fontFamily}`;\n const cached = cache.get(cacheKey);\n if (cached !== undefined) return cached;\n\n const canvas = getCanvas();\n const ctx = canvas.getContext('2d', { willReadFrequently: true })!;\n const font = `bold ${MEASURE_PX}px ${fontFamily}`;\n ctx.clearRect(0, 0, CANVAS_PX, CANVAS_PX);\n ctx.font = font;\n ctx.textBaseline = 'alphabetic';\n ctx.textAlign = 'center';\n ctx.fillStyle = '#ffffff';\n ctx.fillText(text, CANVAS_PX / 2, BASELINE_Y);\n\n const { data } = ctx.getImageData(0, 0, CANVAS_PX, CANVAS_PX);\n\n let top = CANVAS_PX;\n let bottom = -1;\n for (let y = 0; y < CANVAS_PX; y++) {\n for (let x = 0; x < CANVAS_PX; x++) {\n if (data[(y * CANVAS_PX + x) * 4 + 3] > 16) {\n if (y < top) top = y;\n if (y > bottom) bottom = y;\n }\n }\n }\n\n if (bottom === -1) {\n // Nothing rendered — fall back to the original magic number\n const result: BaselineResult = { baselineRatio: 0.35, konvaCorrectionRatio: 0 };\n cache.set(cacheKey, result);\n return result;\n }\n\n // ascent = pixels above the baseline (always positive)\n // descent = pixels below the baseline (0 for caps, positive for descenders)\n const ascent = BASELINE_Y - top;\n const descent = Math.max(0, bottom - BASELINE_Y);\n\n // Offset from room-centre to baseline so that the glyph is visually centred:\n // visual_centre = baseline - ascent + (ascent + descent) / 2\n // = baseline - (ascent - descent) / 2\n // we want visual_centre == room.y\n // → baseline = room.y + (ascent - descent) / 2\n const baselineRatio = (ascent - descent) / 2 / MEASURE_PX;\n\n // Konva centres text using font-wide bounding-box metrics (from 'M').\n // Compute the delta between that and the true visual centre for this glyph.\n const metrics = ctx.measureText('M');\n const fontAscent = (metrics as any).fontBoundingBoxAscent ?? (metrics as any).actualBoundingBoxAscent ?? 0;\n const fontDescent = (metrics as any).fontBoundingBoxDescent ?? (metrics as any).actualBoundingBoxDescent ?? 0;\n // Konva's centering offset (as a ratio of fontSize):\n const konvaCenterRatio = (fontAscent - fontDescent) / 2 / MEASURE_PX;\n // Correction = how much Konva overshoots vs true visual centre\n const konvaCorrectionRatio = konvaCenterRatio - baselineRatio;\n\n const result: BaselineResult = { baselineRatio, konvaCorrectionRatio };\n cache.set(cacheKey, result);\n return result;\n}\n","import type {Settings} from \"../types/Settings\";\nimport {darkenColor, lightenColor} from \"../utils/color\";\nimport type {IMapReader} from \"../reader/MapReader\";\nimport {getRoomBorderColor, getRoomBorderThickness} from \"./RoomFlags\";\n\nexport type RoomColors = {\n fillColor: string;\n strokeColor: string;\n borderWidth: number;\n symbolColor: string;\n envColor: string;\n /** True when the room carries an explicit per-room border colour (userData). */\n customBorder: boolean;\n};\n\nexport type EmbossEdge = {\n points: number[];\n stroke: string;\n strokeWidth: number;\n lineCap?: string;\n lineJoin?: string;\n};\n\nexport type EmbossStyle = {\n highlight: EmbossEdge;\n shadow: EmbossEdge;\n} | null;\n\n/**\n * Compute the fill/stroke/border colors for a room based on env color and settings.\n * This is the single source of truth — used by Konva renderer, SVG exporter, and Canvas exporter.\n */\nexport function computeRoomColors(\n room: MapData.Room,\n mapReader: IMapReader,\n settings: Settings,\n strokeOverride?: string,\n): RoomColors {\n const envColor = mapReader.getColorValue(room.env);\n const fillColor = settings.coloredMode ? darkenColor(envColor, 0.5)\n : settings.frameMode ? settings.backgroundColor : envColor;\n const brightEnvColor = settings.coloredMode ? lightenColor(envColor, 0.1) : envColor;\n let strokeColor = strokeOverride\n ? ((settings.frameMode || settings.coloredMode) ? brightEnvColor : strokeOverride)\n : ((settings.frameMode || settings.coloredMode) ? brightEnvColor : settings.lineColor);\n let borderWidth = settings.borders ? settings.lineWidth : 0;\n\n // Per-room border overrides (Mudlet \"room.ui.border_color\" / \"room.ui.border_thickness\").\n // An explicit per-room border wins over the global/env-derived stroke and is\n // drawn even when global borders are off — matching Mudlet, where a room's\n // border colour overrides the map border colour for that room.\n const borderColor = getRoomBorderColor(room);\n const borderThickness = getRoomBorderThickness(room);\n if (borderColor) strokeColor = borderColor;\n if (borderThickness !== undefined) {\n borderWidth = settings.lineWidth * borderThickness;\n } else if (borderColor && borderWidth === 0) {\n borderWidth = settings.lineWidth;\n }\n\n const symbolColor = room.userData?.[\"system.fallback_symbol_color\"]\n ?? ((settings.frameMode || settings.coloredMode)\n ? brightEnvColor\n : mapReader.getSymbolColor(room.env));\n\n return {fillColor, strokeColor, borderWidth, symbolColor, envColor, customBorder: borderColor !== undefined};\n}\n\n/**\n * Compute the emboss bevel for a room.\n * Returns highlight (top+left) and shadow (bottom+right) edges using\n * lightened/darkened versions of the fill color for a raised 3D look.\n * Works for all room shapes: rectangle, roundedRectangle, and circle.\n */\nexport function computeEmboss(fillColor: string, settings: Settings): EmbossStyle {\n if (!settings.emboss) return null;\n const rs = settings.roomSize;\n const inset = settings.borders ? settings.lineWidth / 2 : 0;\n const sw = settings.lineWidth;\n const hi = lightenColor(fillColor, 0.35);\n const sh = darkenColor(fillColor, 0.45);\n\n if (settings.roomShape === \"circle\") {\n const cx = rs / 2;\n const cy = rs / 2;\n const r = rs / 2 - inset;\n const segs = 48;\n\n // Full circle in shadow color (base)\n const shPoints: number[] = [];\n for (let i = 0; i <= segs; i++) {\n const a = (i / segs) * 360 * Math.PI / 180;\n shPoints.push(cx + Math.cos(a) * r, cy + Math.sin(a) * r);\n }\n\n // Highlight arc on top-left only (~140°), drawn on top of the shadow circle\n const hiPoints: number[] = [];\n const hiSegs = Math.round(segs * 140 / 360);\n for (let i = 0; i <= hiSegs; i++) {\n const a = (200 + (i / hiSegs) * 140) * Math.PI / 180;\n hiPoints.push(cx + Math.cos(a) * r, cy + Math.sin(a) * r);\n }\n\n return {\n shadow: { points: shPoints, stroke: sh, strokeWidth: sw, lineCap: 'round', lineJoin: 'round' },\n highlight: { points: hiPoints, stroke: hi, strokeWidth: sw, lineCap: 'round', lineJoin: 'round' },\n };\n }\n\n if (settings.roomShape === \"roundedRectangle\") {\n const cr = (rs - inset * 2) * 0.2;\n const x1 = inset, y1 = inset;\n const x2 = rs - inset, y2 = rs - inset;\n const segs = 10;\n\n // Generate perimeter clockwise starting from bottom-left diagonal (135°).\n // This way the first half = left + top (highlight), second half = right + bottom (shadow).\n const perimeter: number[] = [];\n\n // Bottom-left corner: 135° → 180° (half corner)\n for (let i = 0; i <= segs / 2; i++) {\n const a = (135 + (i / (segs / 2)) * 45) * Math.PI / 180;\n perimeter.push(x1 + cr + Math.cos(a) * cr, y2 - cr + Math.sin(a) * cr);\n }\n // Top-left corner: 180° → 270° (full corner)\n for (let i = 1; i <= segs; i++) {\n const a = (180 + (i / segs) * 90) * Math.PI / 180;\n perimeter.push(x1 + cr + Math.cos(a) * cr, y1 + cr + Math.sin(a) * cr);\n }\n // Top-right corner: 270° → 315° (half corner)\n for (let i = 1; i <= segs / 2; i++) {\n const a = (270 + (i / (segs / 2)) * 45) * Math.PI / 180;\n perimeter.push(x2 - cr + Math.cos(a) * cr, y1 + cr + Math.sin(a) * cr);\n }\n\n const hiPoints = perimeter.slice();\n\n // Continue for shadow: top-right 315° → 360° (half corner)\n const shPerimeter: number[] = [];\n for (let i = 0; i <= segs / 2; i++) {\n const a = (315 + (i / (segs / 2)) * 45) * Math.PI / 180;\n shPerimeter.push(x2 - cr + Math.cos(a) * cr, y1 + cr + Math.sin(a) * cr);\n }\n // Bottom-right corner: 0° → 90° (full corner)\n for (let i = 1; i <= segs; i++) {\n const a = (i / segs) * 90 * Math.PI / 180;\n shPerimeter.push(x2 - cr + Math.cos(a) * cr, y2 - cr + Math.sin(a) * cr);\n }\n // Bottom-left corner: 90° → 135° (half corner)\n for (let i = 1; i <= segs / 2; i++) {\n const a = (90 + (i / (segs / 2)) * 45) * Math.PI / 180;\n shPerimeter.push(x1 + cr + Math.cos(a) * cr, y2 - cr + Math.sin(a) * cr);\n }\n\n return {\n highlight: { points: hiPoints, stroke: hi, strokeWidth: sw, lineCap: 'round', lineJoin: 'round' },\n shadow: { points: shPerimeter, stroke: sh, strokeWidth: sw, lineCap: 'round', lineJoin: 'round' },\n };\n }\n\n // Plain rectangle: simple L-shapes\n return {\n highlight: {\n points: [inset, rs - inset, inset, inset, rs - inset, inset],\n stroke: hi,\n strokeWidth: sw,\n },\n shadow: {\n points: [inset, rs - inset, rs - inset, rs - inset, rs - inset, inset],\n stroke: sh,\n strokeWidth: sw,\n },\n };\n}\n","import type {IMapReader} from \"../../reader/MapReader\";\nimport type {Settings} from \"../../types/Settings\";\nimport {measureTextBaselineOffset} from \"../../utils/textMeasure\";\nimport {computeRoomColors, computeEmboss} from \"../RoomStyle\";\nimport {darkenColor, hexToRgba} from \"../../utils/color\";\nimport type {GroupShape, Shape} from \"../Shape\";\n\nexport interface RoomLayoutOptions {\n /** Override stroke colour (used for highlighted rooms). */\n strokeOverride?: string;\n /**\n * Whether the active style preserves a flat coordinate space. Iso-style\n * pipelines pass `false` to suppress concentric rings (which would render\n * as flat diamonds outside the cube footprint).\n */\n flatPipeline: boolean;\n /**\n * When set (0..1), the room's fill, border, and symbol colours are drawn at\n * this opacity — used to render hidden rooms faded. Baked into the colour\n * strings so every renderer (Konva, canvas, SVG) honours it identically.\n */\n fade?: number;\n /**\n * Draw the room with a dashed border at full opacity — a more distinct\n * \"hidden\" marker than {@link fade}. Suppresses emboss (the dashed outline\n * is the signal) and forces a visible border even when borders are off.\n */\n dashedBorder?: boolean;\n}\n\n/**\n * Pure layout for a single room. Returns a {@link GroupShape} positioned at\n * the room's top-left, with rect/circle/line/text children for the body,\n * border, emboss, and symbol.\n */\nexport function layoutRoom(\n room: MapData.Room,\n mapReader: IMapReader,\n settings: Settings,\n options: RoomLayoutOptions,\n): GroupShape {\n const colors = computeRoomColors(\n room, mapReader, settings, options.strokeOverride,\n );\n // Fade hidden rooms by baking the opacity into the colour strings. darken/\n // lighten (used for emboss + rings below) preserve the alpha channel, so the\n // whole body inherits the fade.\n const fade = options.fade;\n const fillColor = fade !== undefined ? hexToRgba(colors.fillColor, fade) : colors.fillColor;\n const strokeColor = fade !== undefined ? hexToRgba(colors.strokeColor, fade) : colors.strokeColor;\n const symbolColor = fade !== undefined ? hexToRgba(colors.symbolColor, fade) : colors.symbolColor;\n const borderWidth = colors.borderWidth;\n\n const rs = settings.roomSize;\n const children: Shape[] = [];\n\n const dashedBorder = options.dashedBorder ?? false;\n\n // A per-room custom border colour (userData), or a dashed hidden-room marker,\n // must show — so suppress emboss for that room (the emboss edges would\n // otherwise replace the border and lose the chosen colour / dash).\n const emboss = (colors.customBorder || dashedBorder) ? null : computeEmboss(fillColor, settings);\n // When emboss is active, skip the regular border — the emboss lines serve as the border.\n let drawBorder = emboss ? 0 : borderWidth;\n // A dashed hidden-room outline needs a visible border even when borders are off.\n if (dashedBorder && drawBorder === 0) drawBorder = settings.lineWidth;\n const borderDash = dashedBorder\n ? [Math.max(rs * 0.32, settings.lineWidth * 4), Math.max(rs * 0.13, settings.lineWidth * 1.5)]\n : undefined;\n\n const multiRing = !dashedBorder && settings.coloredMode && drawBorder > 0 && options.flatPipeline;\n const cornerOf = (ins: number) =>\n settings.roomShape === \"roundedRectangle\" ? Math.max(0, (rs - 2 * ins) * 0.2) : 0;\n\n if (multiRing) {\n const ringColors = [darkenColor(strokeColor, 0.5), strokeColor];\n const fillInset = borderWidth * 2;\n\n if (settings.roomShape === \"circle\") {\n children.push({\n type: \"circle\",\n cx: rs / 2, cy: rs / 2, radius: rs / 2 - fillInset,\n paint: {fill: fillColor},\n });\n } else {\n children.push({\n type: \"rect\",\n x: fillInset, y: fillInset,\n width: rs - fillInset * 2, height: rs - fillInset * 2,\n cornerRadius: cornerOf(fillInset),\n paint: {fill: fillColor},\n });\n }\n\n for (let i = 0; i < ringColors.length; i++) {\n const ins = borderWidth / 2 + i * borderWidth;\n if (settings.roomShape === \"circle\") {\n children.push({\n type: \"circle\",\n cx: rs / 2, cy: rs / 2, radius: rs / 2 - ins,\n paint: {stroke: ringColors[i], strokeWidth: borderWidth},\n });\n } else {\n children.push({\n type: \"rect\",\n x: ins, y: ins,\n width: rs - ins * 2, height: rs - ins * 2,\n cornerRadius: cornerOf(ins),\n paint: {stroke: ringColors[i], strokeWidth: borderWidth},\n });\n }\n }\n } else if (settings.roomShape === \"circle\") {\n children.push({\n type: \"circle\",\n cx: rs / 2, cy: rs / 2, radius: rs / 2,\n paint: {\n fill: fillColor,\n stroke: drawBorder ? strokeColor : undefined,\n strokeWidth: drawBorder,\n dash: borderDash,\n dashEnabled: borderDash ? true : undefined,\n },\n });\n } else {\n children.push({\n type: \"rect\",\n x: 0, y: 0, width: rs, height: rs,\n cornerRadius: settings.roomShape === \"roundedRectangle\" ? rs * 0.2 : 0,\n paint: {\n fill: fillColor,\n stroke: drawBorder ? strokeColor : undefined,\n strokeWidth: drawBorder,\n dash: borderDash,\n dashEnabled: borderDash ? true : undefined,\n },\n });\n }\n\n if (emboss) {\n children.push({\n type: \"line\",\n points: emboss.shadow.points,\n paint: {\n stroke: emboss.shadow.stroke,\n strokeWidth: emboss.shadow.strokeWidth,\n },\n lineCap: emboss.shadow.lineCap as \"butt\" | \"round\" | \"square\" | undefined,\n lineJoin: emboss.shadow.lineJoin as \"miter\" | \"round\" | \"bevel\" | undefined,\n });\n children.push({\n type: \"line\",\n points: emboss.highlight.points,\n paint: {\n stroke: emboss.highlight.stroke,\n strokeWidth: emboss.highlight.strokeWidth,\n },\n lineCap: emboss.highlight.lineCap as \"butt\" | \"round\" | \"square\" | undefined,\n // Original used emboss.shadow.lineJoin here — preserve that quirk for parity.\n lineJoin: emboss.shadow.lineJoin as \"miter\" | \"round\" | \"bevel\" | undefined,\n });\n }\n\n if (room.roomChar) {\n const fontSize = rs * 0.75;\n const {baselineRatio, konvaCorrectionRatio} = measureTextBaselineOffset(\n room.roomChar, settings.fontFamily,\n );\n const textWidth = Math.max(rs, room.roomChar.length * fontSize * 0.8);\n const textOffset = (textWidth - rs) / 2;\n children.push({\n type: \"text\",\n x: -textOffset,\n y: 0,\n text: room.roomChar,\n fontSize,\n fontFamily: settings.fontFamily,\n fontStyle: \"bold\",\n fill: symbolColor,\n align: \"center\",\n verticalAlign: \"middle\",\n width: textWidth,\n height: rs,\n baselineRatio,\n konvaCorrectionRatio,\n });\n }\n\n return {\n type: \"group\",\n x: room.x - rs / 2,\n y: room.y - rs / 2,\n layer: \"room\",\n hit: {kind: \"room\", id: room.id, payload: room},\n children,\n };\n}\n","import type {Settings} from \"../types/Settings\";\nimport type {IMapReader} from \"../reader/MapReader\";\nimport {movePoint} from \"../directions\";\n\nconst innerExits: MapData.direction[] = [\"up\", \"down\", \"in\", \"out\"];\n\nexport type TriangleData = {\n cx: number;\n cy: number;\n /** Pre-computed polygon vertices (3 pairs of x,y) */\n vertices: number[];\n fill: string;\n stroke: string;\n strokeWidth: number;\n};\n\nexport type InnerExitData = {\n triangles: TriangleData[];\n};\n\nfunction computeTriangleVertices(cx: number, cy: number, radius: number, rotationDeg: number): number[] {\n const scaleX = 1.4, scaleY = 0.8;\n const angleRad = rotationDeg * Math.PI / 180;\n const vertices: number[] = [];\n for (let i = 0; i < 3; i++) {\n const a = (2 * Math.PI * i / 3) - Math.PI / 2;\n const px = Math.cos(a) * radius * scaleX;\n const py = Math.sin(a) * radius * scaleY;\n const rx = px * Math.cos(angleRad) - py * Math.sin(angleRad);\n const ry = px * Math.sin(angleRad) + py * Math.cos(angleRad);\n vertices.push(cx + rx, cy + ry);\n }\n return vertices;\n}\n\nfunction getSymbolColors(room: MapData.Room, mapReader: IMapReader, settings: Settings): { symbolColor: string; symbolFill: string } {\n const fallback = room.userData?.[\"system.fallback_symbol_color\"];\n const symbolColor = fallback\n ?? ((settings.frameMode || settings.coloredMode)\n ? mapReader.getColorValue(room.env)\n : mapReader.getSymbolColor(room.env));\n const symbolFill = fallback\n ?? ((settings.frameMode || settings.coloredMode)\n ? mapReader.getColorValue(room.env)\n : mapReader.getSymbolColor(room.env, 0.6));\n return {symbolColor, symbolFill};\n}\n\nconst DoorColors: Record<number, string> = {\n 1: 'rgb(10, 155, 10)',\n 2: 'rgb(226, 205, 59)',\n 3: 'rgb(155, 10, 10)',\n};\n\n/**\n * Pre-computed triangle positions (centre + rotation) for one inner-exit\n * direction. `in`/`out` produce two triangles, `up`/`down` one.\n *\n * Shared between {@link computeInnerExits} (room rendering) and the path\n * overlay so a \"go up\" path marker lands exactly on top of the room's\n * regular up-arrow triangle.\n */\nexport function computeInnerExitTrianglesForDirection(\n room: MapData.Room,\n direction: MapData.direction,\n settings: Settings,\n): Array<{ cx: number; cy: number; vertices: number[] }> {\n const rs = settings.roomSize;\n const triRadius = rs / 5;\n const make = (cx: number, cy: number, rot: number) => ({\n cx, cy, vertices: computeTriangleVertices(cx, cy, triRadius, rot),\n });\n switch (direction) {\n case \"up\": {\n const p = movePoint(room.x, room.y, \"south\", rs / 4);\n return [make(p.x, p.y, 0)];\n }\n case \"down\": {\n const p = movePoint(room.x, room.y, \"north\", rs / 4);\n return [make(p.x, p.y, 180)];\n }\n case \"in\": {\n const w = movePoint(room.x, room.y, \"west\", rs / 4);\n const e = movePoint(room.x, room.y, \"east\", rs / 4);\n return [make(w.x, w.y, 90), make(e.x, e.y, -90)];\n }\n case \"out\": {\n const w = movePoint(room.x, room.y, \"west\", rs / 4);\n const e = movePoint(room.x, room.y, \"east\", rs / 4);\n return [make(w.x, w.y, -90), make(e.x, e.y, 90)];\n }\n default:\n return [];\n }\n}\n\n/**\n * Compute inner exit triangle data for a room.\n * Returns pre-computed vertex positions so each backend just draws polygons.\n */\nexport function computeInnerExits(room: MapData.Room, mapReader: IMapReader, settings: Settings): InnerExitData {\n const triangles: TriangleData[] = [];\n const {symbolColor, symbolFill} = getSymbolColors(room, mapReader, settings);\n\n for (const exit of innerExits) {\n if (!room.exits[exit]) continue;\n\n const doorType = room.doors[exit];\n const stroke = doorType !== undefined ? (DoorColors[doorType] ?? DoorColors[3]) : symbolColor;\n\n for (const tri of computeInnerExitTrianglesForDirection(room, exit, settings)) {\n triangles.push({\n cx: tri.cx, cy: tri.cy,\n vertices: tri.vertices,\n fill: symbolFill, stroke, strokeWidth: settings.lineWidth,\n });\n }\n }\n\n return {triangles};\n}\n\n/**\n * Compute triangle vertices for a standalone triangle (used by path overlay markers).\n */\nexport { computeTriangleVertices };\n","import type {IMapReader} from \"../../reader/MapReader\";\nimport type {Settings} from \"../../types/Settings\";\nimport type {ExitDrawArrow, ExitDrawData} from \"../../ExitRenderer\";\nimport {computeInnerExits} from \"../InnerExitStyle\";\nimport type {GroupShape, HitInfo, PolygonShape, Shape} from \"../Shape\";\n\n/**\n * Pure layout for a room's inner exits (up/down/in/out triangles). Returns\n * polygon shapes in coordinates relative to the room's top-left, ready to\n * drop into the room's GroupShape as children.\n */\nexport function layoutInnerExits(\n room: MapData.Room,\n mapReader: IMapReader,\n settings: Settings,\n): PolygonShape[] {\n const rs = settings.roomSize;\n const gx = room.x - rs / 2;\n const gy = room.y - rs / 2;\n const {triangles} = computeInnerExits(room, mapReader, settings);\n\n return triangles.map(tri => {\n const relVertices: number[] = new Array(tri.vertices.length);\n for (let i = 0; i < tri.vertices.length; i += 2) {\n relVertices[i] = tri.vertices[i] - gx;\n relVertices[i + 1] = tri.vertices[i + 1] - gy;\n }\n return {\n type: \"polygon\",\n vertices: relVertices,\n paint: {\n fill: tri.fill,\n stroke: tri.stroke,\n strokeWidth: tri.strokeWidth,\n },\n };\n });\n}\n\n/**\n * Pure layout for a single inter-room link exit, given its computed\n * {@link ExitDrawData}. Returns a {@link GroupShape} at the world origin;\n * the active {@link Style} (e.g. Isometric) is responsible for any\n * cube-base offset on the link layer.\n *\n * `hit` is optional — callers that want the exit to participate in\n * hit-testing pass an {@link HitInfo} carrying the exit identity (a, b,\n * aDir, bDir). Omitted for one-off exit shapes used in current-room\n * overlays where the exit isn't independently pickable.\n */\nexport function layoutLinkExit(data: ExitDrawData, hit?: HitInfo): GroupShape {\n const children: Shape[] = [];\n\n for (const line of data.lines) {\n children.push({\n type: \"line\",\n points: line.points,\n paint: {\n stroke: line.stroke,\n strokeWidth: line.strokeWidth,\n dash: line.dash,\n },\n });\n }\n\n for (const arrow of data.arrows) {\n appendArrowShapes(children, arrow);\n }\n\n for (const door of data.doors) {\n children.push({\n type: \"rect\",\n x: door.x,\n y: door.y,\n width: door.width,\n height: door.height,\n paint: {\n stroke: door.stroke,\n strokeWidth: door.strokeWidth,\n },\n });\n }\n\n return {\n type: \"group\",\n x: 0,\n y: 0,\n layer: \"link\",\n ...(hit ? {hit} : {}),\n children,\n };\n}\n\nfunction appendArrowShapes(out: Shape[], arrow: ExitDrawArrow): void {\n out.push({\n type: \"line\",\n points: arrow.points,\n paint: {\n stroke: arrow.stroke,\n strokeWidth: arrow.strokeWidth,\n dash: arrow.dash,\n },\n });\n\n const lastIdx = arrow.points.length - 2;\n const tipX = arrow.points[lastIdx];\n const tipY = arrow.points[lastIdx + 1];\n const prevX = arrow.points[lastIdx - 2];\n const prevY = arrow.points[lastIdx - 1];\n const angle = Math.atan2(tipY - prevY, tipX - prevX);\n const pl = arrow.pointerLength;\n const pw = arrow.pointerWidth / 2;\n const x1 = tipX - pl * Math.cos(angle - Math.atan2(pw, pl));\n const y1 = tipY - pl * Math.sin(angle - Math.atan2(pw, pl));\n const x2 = tipX - pl * Math.cos(angle + Math.atan2(pw, pl));\n const y2 = tipY - pl * Math.sin(angle + Math.atan2(pw, pl));\n\n out.push({\n type: \"polygon\",\n vertices: [tipX, tipY, x1, y1, x2, y2],\n paint: {\n fill: arrow.fill,\n stroke: arrow.stroke,\n strokeWidth: arrow.strokeWidth,\n },\n });\n}\n","import type {Settings} from \"../../types/Settings\";\nimport {computeSpecialExits} from \"../SpecialExitStyle\";\nimport type {SpecialExitData} from \"../SpecialExitStyle\";\nimport type {GroupShape, Shape} from \"../Shape\";\n\nexport interface SpecialExitLayoutOptions {\n /** Stroke override (used by highlighted-room overlays). */\n colorOverride?: string;\n}\n\n/**\n * Build a {@link GroupShape} for a single already-computed special-exit\n * record. Lets callers compute the data once (e.g. ScenePipeline records\n * `drawnSpecialExits` for hit-testing) and reuse it for layout.\n */\nexport function specialExitToShape(se: SpecialExitData, roomId: number): GroupShape {\n const children: Shape[] = [];\n\n children.push({\n type: \"line\",\n points: se.line.points,\n paint: {\n stroke: se.line.stroke,\n strokeWidth: se.line.strokeWidth,\n dash: se.line.dash,\n },\n });\n\n if (se.arrow) {\n const a = se.arrow;\n children.push({\n type: \"polygon\",\n vertices: [a.tipX, a.tipY, a.x1, a.y1, a.x2, a.y2],\n paint: {\n fill: a.fill,\n stroke: a.stroke,\n strokeWidth: a.strokeWidth,\n },\n });\n }\n\n if (se.door) {\n const d = se.door;\n children.push({\n type: \"rect\",\n x: d.x,\n y: d.y,\n width: d.width,\n height: d.height,\n paint: {\n stroke: d.stroke,\n strokeWidth: d.strokeWidth,\n },\n });\n }\n\n return {\n type: \"group\",\n x: 0,\n y: 0,\n layer: \"link\",\n hit: {\n kind: \"specialExit\",\n id: `${roomId}:${se.dir}`,\n payload: {roomId, exitName: se.dir},\n },\n children,\n };\n}\n\n/**\n * Pure layout for a room's custom-line special exits. Returns one\n * {@link GroupShape} per special exit at the world origin; the active\n * {@link Style} applies any depth offset for the link layer.\n */\nexport function layoutSpecialExits(\n room: MapData.Room,\n settings: Settings,\n options: SpecialExitLayoutOptions = {},\n): GroupShape[] {\n return computeSpecialExits(room, settings, options.colorOverride)\n .map(se => specialExitToShape(se, room.id));\n}\n","import type {Settings} from \"../../types/Settings\";\nimport {computeStubs} from \"../StubStyle\";\nimport type {StubData} from \"../StubStyle\";\nimport type {GroupShape} from \"../Shape\";\n\nexport interface StubLayoutOptions {\n /** Stroke override (used by highlighted-room overlays). */\n colorOverride?: string;\n}\n\n/**\n * Build a {@link GroupShape} for a single already-computed stub. Lets callers\n * compute the data once and reuse it for both layout and hit-testing records.\n */\nexport function stubToShape(stub: StubData): GroupShape {\n return {\n type: \"group\",\n x: 0,\n y: 0,\n layer: \"link\",\n hit: {\n kind: \"stub\",\n id: `${stub.roomId}:${stub.direction}`,\n payload: stub,\n },\n children: [{\n type: \"line\",\n points: [stub.x1, stub.y1, stub.x2, stub.y2],\n paint: {\n stroke: stub.stroke,\n strokeWidth: stub.strokeWidth,\n },\n }],\n };\n}\n\n/**\n * Pure layout for a room's stub indicators (the short lines for each entry\n * in `room.stubs`). Returns one {@link GroupShape} per stub at the world\n * origin; the active {@link Style} applies any depth offset for the link\n * layer.\n */\nexport function layoutStubs(\n room: MapData.Room,\n settings: Settings,\n options: StubLayoutOptions = {},\n): GroupShape[] {\n return computeStubs(room, settings, options.colorOverride).map(stubToShape);\n}\n","import type {Settings} from \"../../types/Settings\";\nimport type {GroupShape, Shape} from \"../Shape\";\n\nfunction getLabelColor(color: MapData.Color): string {\n const alpha = (color?.alpha ?? 255) / 255;\n const clamp = (value: number) => Math.min(255, Math.max(0, value ?? 0));\n return `rgba(${clamp(color?.r)}, ${clamp(color?.g)}, ${clamp(color?.b)}, ${alpha})`;\n}\n\n/**\n * Pure layout for one user-defined label. Returns a {@link GroupShape}, or\n * `null` when the label is suppressed by `labelRenderMode === \"none\"`.\n *\n * `noScaling` labels are anchored at (X, -Y) with content at (0,0) so the\n * RecordingLayerNode can cancel out stage zoom for those groups only — the\n * group origin / child origin split mirrors that requirement.\n */\nexport function labelToShape(label: MapData.Label, settings: Settings): GroupShape | null {\n if (settings.labelRenderMode === \"none\") return null;\n\n const lx = label.X;\n const ly = -label.Y;\n const noScaling = !!label.noScaling;\n const gx = noScaling ? lx : 0;\n const gy = noScaling ? ly : 0;\n const cx = noScaling ? 0 : lx;\n const cy = noScaling ? 0 : ly;\n\n if (settings.labelRenderMode === \"image\" && label.pixMap) {\n return {\n type: \"group\",\n x: gx,\n y: gy,\n layer: label.showOnTop ? \"top\" : \"link\",\n noScale: noScaling,\n hit: {kind: \"label\", payload: label},\n children: [{\n type: \"image\",\n x: cx,\n y: cy,\n width: label.Width,\n height: label.Height,\n src: `data:image/png;base64,${label.pixMap}`,\n }],\n };\n }\n\n const children: Shape[] = [];\n\n if ((label.BgColor?.alpha ?? 0) > 0 && !settings.transparentLabels) {\n children.push({\n type: \"rect\",\n x: cx,\n y: cy,\n width: label.Width,\n height: label.Height,\n paint: {fill: getLabelColor(label.BgColor)},\n });\n }\n\n if (label.Text) {\n const ratio = Math.min(0.75, label.Width / Math.max(label.Text.length / 2, 1));\n const fontSize = Math.max(0.1, Math.min(ratio, Math.max(label.Height * 0.9, 0.1)));\n children.push({\n type: \"text\",\n x: cx,\n y: cy,\n width: label.Width,\n height: label.Height,\n text: label.Text,\n fontSize,\n fill: getLabelColor(label.FgColor),\n align: \"center\",\n verticalAlign: \"middle\",\n });\n }\n\n return {\n type: \"group\",\n x: gx,\n y: gy,\n layer: label.showOnTop ? \"top\" : \"link\",\n noScale: noScaling,\n hit: {kind: \"label\", payload: label},\n children,\n };\n}\n","import type {RoomLens} from \"./RoomLens\";\nimport {defaultExitTreatment} from \"./RoomLens\";\nimport {isRoomHidden} from \"../scene/RoomFlags\";\n\n/**\n * Wrap a lens so Mudlet-hidden rooms (`settings.hiddenRooms === \"hide\"`) count\n * as not-visible: the room filter drops them via `isVisible`, and any exit\n * touching one is treated as `\"hidden\"` via `getExitTreatment`. This reuses the\n * existing lens machinery so every consumer (scene build, current-room overlay)\n * hides hidden rooms and their exits identically.\n *\n * Returns `lens` unchanged when `hideHidden` is false (the \"show\"/\"faded\" modes\n * keep hidden rooms visible — \"faded\" fades them in the room pass instead).\n */\nexport function hiddenAwareLens(lens: RoomLens, hideHidden: boolean): RoomLens {\n if (!hideHidden) return lens;\n return {\n isVisible: room => lens.isVisible(room) && !isRoomHidden(room),\n getExitTreatment: (exit, a, b) =>\n isRoomHidden(a) || isRoomHidden(b)\n ? \"hidden\"\n : lens.getExitTreatment\n ? lens.getExitTreatment(exit, a, b)\n : defaultExitTreatment(lens, exit, a, b),\n getVersion: () => lens.getVersion(),\n };\n}\n","import type {IMapReader} from \"../reader/MapReader\";\nimport type {IArea} from \"../reader/Area\";\nimport {movePoint, planarDirections} from \"../directions\";\nimport {longToShort} from \"./../reader/Exit\";\n\nconst PLANAR = new Set<string>(planarDirections);\n\n/**\n * Clone a room placed at projected `(x, y)` in the current area+plane, shifting\n * its custom-line points by the same delta so they follow the room. Custom-line\n * points are stored with world-y negated (`world = -point.y`), so a world shift\n * of `dy` maps to `point.y -= dy`.\n */\nexport function projectRoom(room: MapData.Room, x: number, y: number, area: number, z: number): MapData.Room {\n const dx = x - room.x;\n const dy = y - room.y;\n let customLines = room.customLines;\n if (customLines && Object.keys(customLines).length > 0 && (dx !== 0 || dy !== 0)) {\n customLines = {};\n for (const [key, line] of Object.entries(room.customLines)) {\n customLines[key] = {...line, points: line.points.map(p => ({x: p.x + dx, y: p.y - dy}))};\n }\n }\n return {...room, x, y, area, z, customLines};\n}\n\n/** True if `a`'s exit to `b` (regular or special) is drawn as a custom-line polyline. */\nfunction customLineBetween(a: MapData.Room, b: MapData.Room): boolean {\n const has = (key: string) => !!(a.customLines[key] || a.customLines[longToShort[key as MapData.direction]]);\n for (const [dir, t] of Object.entries(a.exits ?? {})) if (t === b.id && has(dir)) return true;\n for (const [name, t] of Object.entries(a.specialExits ?? {})) if (t === b.id && has(name)) return true;\n return false;\n}\n\n/**\n * Gap (in grid units) placed between the boundary room and the first\n * neighbouring-area room. Two units rather than one so the area change reads\n * as a visible seam instead of looking like an ordinary adjacent room.\n */\nconst BOUNDARY_GAP = 2;\n\n/** One neighbouring-area room, with its position projected into the current area's frame. */\nexport interface ProjectedRoom {\n /** The real room (its `id` is preserved so the rendered shape stays clickable). */\n room: MapData.Room;\n /** Projected X in the current area's coordinate space. */\n x: number;\n /** Projected Y in the current area's coordinate space. */\n y: number;\n}\n\n/** A connector line between two projected room centres (current-area frame). */\nexport interface ProjectedEdge {\n ax: number;\n ay: number;\n bx: number;\n by: number;\n}\n\n/** Result of {@link computeNeighborSpill}: faded preview geometry for neighbouring areas. */\nexport interface NeighborSpill {\n rooms: ProjectedRoom[];\n edges: ProjectedEdge[];\n}\n\n/**\n * Walk the exit graph outward from the player's room (BFS, depth ≤ `maxSteps`)\n * and project the positions of rooms that live in a *different* area onto the\n * current area's coordinate plane, so they can be drawn across the boundary.\n *\n * Areas use independent (area-local) coordinates, so we can't trust a\n * neighbour room's raw x/y to align with the current area. Instead we carry a\n * projected position through the BFS:\n * - within one area, we advance by the real coordinate delta between rooms;\n * - crossing a boundary, we place the target one grid step away in the exit's\n * planar direction (the only geometric anchor we have).\n *\n * Only planar exits (n/s/e/w + diagonals) are followed — non-planar links\n * (up/down/in/out, special exits) can't be placed on the plane. Only rooms on\n * the current z-level (`currentZ`) are collected.\n *\n * Returns `undefined` when there's nothing to draw.\n */\nexport function computeNeighborSpill(\n mapReader: IMapReader,\n currentAreaId: number,\n currentZ: number,\n playerRoomId: number,\n maxSteps: number,\n isVisible: (room: MapData.Room) => boolean = () => true,\n): NeighborSpill | undefined {\n const start = mapReader.getRoom(playerRoomId);\n if (!start || maxSteps <= 0) return undefined;\n\n interface QueueNode {\n id: number;\n x: number;\n y: number;\n depth: number;\n }\n\n // roomId → projected position (also serves as the visited set; first/shortest path wins).\n const visited = new Map<number, {x: number; y: number}>();\n visited.set(start.id, {x: start.x, y: start.y});\n const queue: QueueNode[] = [{id: start.id, x: start.x, y: start.y, depth: 0}];\n\n const ghostIds: number[] = [];\n const edges: ProjectedEdge[] = [];\n\n while (queue.length > 0) {\n const cur = queue.shift()!;\n if (cur.depth >= maxSteps) continue;\n\n const room = mapReader.getRoom(cur.id);\n if (!room) continue;\n\n // Follow both regular exits (which carry a planar direction) and special\n // exits (named commands, no direction) so rooms reachable only via a\n // special exit still spill.\n const candidates: Array<{targetId: number; dir?: MapData.direction}> = [\n ...Object.entries(room.exits ?? {}).map(([d, t]) => ({targetId: t, dir: d as MapData.direction})),\n ...Object.values(room.specialExits ?? {}).map(t => ({targetId: t})),\n ];\n\n for (const {targetId, dir} of candidates) {\n const target = mapReader.getRoom(targetId);\n if (!target || visited.has(target.id)) continue;\n\n const sameArea = target.area === room.area;\n let nx: number;\n let ny: number;\n if (sameArea) {\n // Same area — coordinates are consistent, advance by the real delta.\n nx = cur.x + (target.x - room.x);\n ny = cur.y + (target.y - room.y);\n } else {\n // Boundary crossing — anchor the neighbour a small gap along the\n // exit. Needs a planar direction, which special exits lack, so\n // we can only cross areas via a regular planar exit.\n if (!dir) continue;\n const stepped = movePoint(cur.x, cur.y, dir, BOUNDARY_GAP);\n if (stepped.x === cur.x && stepped.y === cur.y) continue; // non-planar exit\n nx = stepped.x;\n ny = stepped.y;\n }\n\n visited.set(target.id, {x: nx, y: ny});\n queue.push({id: target.id, x: nx, y: ny, depth: cur.depth + 1});\n\n // Collect the target as a ghost only when it's in another area on\n // this plane and actually visible (lens / fog-of-war).\n if (target.area !== currentAreaId && target.z === currentZ && isVisible(target)) {\n ghostIds.push(target.id);\n }\n }\n }\n\n if (ghostIds.length === 0) return undefined;\n\n const rooms: ProjectedRoom[] = ghostIds.map(id => {\n const p = visited.get(id)!;\n return {room: mapReader.getRoom(id), x: p.x, y: p.y};\n });\n\n // Connectors: draw a plain line for every exit (planar regular + special)\n // between two positioned, on-plane rooms where at least one side is spilled.\n // This shows the full local connectivity of the spilled rooms (not just the\n // BFS-discovery edges) and renders each boundary crossing as a plain line.\n // Exits between two current-area rooms are left to the main scene pass.\n // Exits with a custom line are skipped — they're drawn as polylines by the\n // room pass (offset to follow the spilled room). Deduped per room pair.\n const seen = new Set<string>();\n for (const [id, pos] of visited) {\n const room = mapReader.getRoom(id);\n if (!room || room.z !== currentZ || !isVisible(room)) continue;\n const roomSpilled = room.area !== currentAreaId;\n const planarTargets = Object.entries(room.exits ?? {})\n .filter(([d]) => PLANAR.has(d))\n .map(([, t]) => t);\n const targetIds = [...planarTargets, ...Object.values(room.specialExits ?? {})];\n for (const targetId of targetIds) {\n const tpos = visited.get(targetId);\n if (!tpos) continue;\n const target = mapReader.getRoom(targetId);\n if (!target || target.z !== currentZ || !isVisible(target)) continue;\n if (!roomSpilled && target.area === currentAreaId) continue; // both current-area\n if (customLineBetween(room, target) || customLineBetween(target, room)) continue;\n const key = id < targetId ? `${id}-${targetId}` : `${targetId}-${id}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({ax: pos.x, ay: pos.y, bx: tpos.x, by: tpos.y});\n }\n }\n\n return {rooms, edges};\n}\n\n/**\n * Wraps an {@link IMapReader} so that spilled neighbouring-area rooms appear to\n * live in the current area at their projected positions. {@link getRoom}\n * returns a clone of a spilled room with `x`/`y` swapped for the projected\n * coordinates, `area`/`z` rewritten to the current area+plane, and custom-line\n * points shifted by the same delta so they follow the room; every other room and\n * method passes straight through to the base reader.\n *\n * This lets the overlay code (highlights, paths) treat spilled rooms exactly\n * like current-area rooms: existing area/visibility checks pass for them and\n * all coordinate maths lands on the projected positions, with no special cases.\n */\nexport class ProjectedMapReader implements IMapReader {\n constructor(\n private readonly base: IMapReader,\n private readonly currentArea: number,\n private readonly currentZ: number,\n private readonly projected: Map<number, {x: number; y: number}>,\n ) {}\n\n getRoom(roomId: number): MapData.Room {\n const room = this.base.getRoom(roomId);\n const p = room ? this.projected.get(roomId) : undefined;\n if (!p) return room;\n return projectRoom(room, p.x, p.y, this.currentArea, this.currentZ);\n }\n\n getArea(areaId: number): IArea {\n return this.base.getArea(areaId);\n }\n\n getAreas(): IArea[] {\n return this.base.getAreas();\n }\n\n getRooms(): MapData.Room[] {\n return this.base.getRooms();\n }\n\n getColorValue(envId: number): string {\n return this.base.getColorValue(envId);\n }\n\n getSymbolColor(envId: number, opacity?: number): string {\n return this.base.getSymbolColor(envId, opacity);\n }\n}\n\n/** Build a roomId → projected-position map from a {@link NeighborSpill}. */\nexport function spillPositionMap(spill: NeighborSpill): Map<number, {x: number; y: number}> {\n return new Map(spill.rooms.map(r => [r.room.id, {x: r.x, y: r.y}]));\n}\n","import type {IMapReader} from \"./reader/MapReader\";\nimport type {IArea} from \"./reader/Area\";\nimport type {IPlane} from \"./reader/Plane\";\nimport type IExit from \"./reader/Exit\";\nimport type {Settings} from \"./types/Settings\";\nimport ExitRenderer from \"./ExitRenderer\";\nimport type {ExitDrawData} from \"./ExitRenderer\";\nimport {computeStubs} from \"./scene/StubStyle\";\nimport type {StubData} from \"./scene/StubStyle\";\nimport {computeSpecialExits} from \"./scene/SpecialExitStyle\";\nimport {layoutRoom} from \"./scene/elements/RoomLayout\";\nimport {layoutInnerExits} from \"./scene/elements/ExitLayout\";\nimport {layoutLinkExit} from \"./scene/elements/ExitLayout\";\nimport {specialExitToShape} from \"./scene/elements/SpecialExitLayout\";\nimport {stubToShape} from \"./scene/elements/StubLayout\";\nimport {labelToShape} from \"./scene/elements/LabelLayout\";\nimport type {GroupShape, Shape} from \"./scene/Shape\";\nimport {colorLightness} from \"./utils/color\";\nimport type {RoomLens, ExitTreatment} from \"./lens/RoomLens\";\nimport {ALL_VISIBLE, defaultExitTreatment} from \"./lens/RoomLens\";\nimport {hiddenAwareLens} from \"./lens/hiddenAwareLens\";\nimport {movePoint, movePointCircle, movePointRoundedRect} from \"./directions\";\nimport {longToShort, regularExits} from \"./reader/Exit\";\nimport type {NeighborSpill} from \"./scene/NeighborProjector\";\nimport {projectRoom} from \"./scene/NeighborProjector\";\nimport {isRoomHidden, hiddenRoomLayoutOptions} from \"./scene/RoomFlags\";\n\ntype Bounds = { x: number; y: number; width: number; height: number };\n\n\n/**\n * Reference to one room's body shape inside the scene. Keyed by roomId so\n * downstream consumers (cull index, hit-test bookkeeping) can find a room\n * shape by id without re-walking the scene tree.\n */\nexport type RoomShapeRef = { room: MapData.Room; shape: GroupShape };\n\n/**\n * Reference to one drawn link-exit shape, paired with its world-space bounds\n * and (optional) cross-area target room. Used by the live renderer to hang\n * cull entries on each exit.\n */\nexport type StandaloneExitShapeRef = { shape: GroupShape; bounds: Bounds; targetRoomId?: number };\nexport type AreaExitHitZone = {\n bounds: Bounds;\n targetRoomId: number;\n /** Source room center — used to compute arrow direction for labels. Optional for back-compat. */\n from?: { x: number; y: number };\n /** Arrow tip / far endpoint — anchor for placed labels. Optional for back-compat. */\n tip?: { x: number; y: number };\n /** Stroke colour of the rendered arrow — used to colour area-exit labels. */\n arrowColor?: string;\n};\n\n/**\n * One drawn two-way or one-way inter-room exit, with stable identity and the\n * exact geometry ExitRenderer produced. Consumers (e.g. an editor's\n * hit-testing layer) can run segment distance checks against\n * `data.lines[].points` / `data.arrows[].points` to match exactly what the\n * user sees — including dash patterns and the renderer's suppression rules\n * (e.g. both-sides-customLine two-ways, one-ways overridden by customLine).\n */\nexport type DrawnExitEntry = {\n readonly a: number;\n readonly b: number;\n readonly aDir?: MapData.direction;\n readonly bDir?: MapData.direction;\n readonly kind: \"exit\" | \"specialExit\";\n readonly zIndex: number[];\n readonly data: ExitDrawData;\n};\n\n/**\n * One drawn custom-line (special exit) polyline, as it was rendered for\n * this scene. `points` is the flat [x,y,x,y,...] polyline with the source\n * room's centre prepended (mirroring what the renderer actually drew), in\n * render-space coordinates.\n */\nexport type DrawnSpecialExitEntry = {\n readonly roomId: number;\n readonly exitName: string;\n readonly points: number[];\n readonly stroke: string;\n readonly strokeWidth: number;\n readonly dash?: number[];\n readonly hasArrow: boolean;\n readonly arrowTip?: { x: number; y: number };\n readonly bounds: Bounds;\n};\n\n/**\n * One drawn stub — a room's one-way exit indicator (the short line sticking\n * out of the edge for every entry in `room.stubs`). Rendered directly from\n * these coordinates by {@link ScenePipeline}, so hit-testing against them\n * matches what's on screen. Non-planar stubs (up/down/in/out) are still\n * recorded — x1==x2 and y1==y2 — so consumers can filter them out.\n */\nexport type DrawnStubEntry = {\n readonly roomId: number;\n readonly direction: MapData.direction;\n readonly x1: number;\n readonly y1: number;\n readonly x2: number;\n readonly y2: number;\n readonly stroke: string;\n readonly strokeWidth: number;\n};\n\n/**\n * World-space shapes for each logical layer. Same content the backend layer\n * nodes received, but as plain SceneIR data — consumed by exporters that drive\n * {@link buildDrawCommands} → engine renderers without going through a\n * {@link DrawingBackend}.\n *\n * Order within each list mirrors the order in which the corresponding layer\n * node received its `addNode` calls: e.g. `link` is labels → link exits → for\n * each room (special exits, stubs); `room` is rooms → area name → area-exit\n * labels.\n */\nexport type SceneShapesByLayer = {\n grid: Shape[];\n link: Shape[];\n room: Shape[];\n topLabel: Shape[];\n};\n\n/** User-defined label shape paired with its world-space bounds (for culling). */\nexport type LabelShapeRef = { shape: GroupShape; bounds: Bounds };\n\n/** Custom-line (special-exit) shape paired with its world-space bounds (for culling). */\nexport type SpecialExitShapeRef = { shape: GroupShape; bounds: Bounds };\n\n/** Stub shape paired with its world-space bounds (for culling). */\nexport type StubShapeRef = { shape: GroupShape; bounds: Bounds };\n\n/** Area-exit label shape paired with its world-space bounds (for culling). */\nexport type AreaExitLabelShapeRef = { shape: GroupShape; bounds: Bounds };\n\nexport type SceneBuildResult = {\n /** Room body shapes keyed by roomId — for cull-entry construction. */\n roomShapeRefs: Map<number, RoomShapeRef>;\n /** Link-exit shapes paired with bounds and area-target metadata. */\n standaloneExitShapeRefs: StandaloneExitShapeRef[];\n /** Non-noScaling label shapes paired with world-space bounds (for culling). */\n labelShapeRefs: LabelShapeRef[];\n /** Custom-line special-exit shapes paired with world-space bounds (for culling). */\n specialExitShapeRefs: SpecialExitShapeRef[];\n /** Stub shapes paired with world-space bounds (for culling). */\n stubShapeRefs: StubShapeRef[];\n /** Area-exit label shapes paired with world-space bounds (for culling). */\n areaExitLabelShapeRefs: AreaExitLabelShapeRef[];\n areaExitHitZones: AreaExitHitZone[];\n drawnExits: DrawnExitEntry[];\n drawnSpecialExits: DrawnSpecialExitEntry[];\n drawnStubs: DrawnStubEntry[];\n /** World-space shapes carrying {@link HitInfo} annotations — fed to {@link HitTester}. */\n hitShapes: Shape[];\n /**\n * Engine-agnostic shape lists per logical layer. Drives\n * {@link buildDrawCommands} + a per-engine renderer.\n */\n sceneShapes: SceneShapesByLayer;\n};\n\n/** Convert a `rgb(...)`, `rgba(...)`, or `#rrggbb` string to `rgba(r,g,b,alpha)`. */\nfunction colorWithAlpha(color: string, alpha: number): string {\n const rgb = parseRgb(color);\n if (!rgb) return color;\n return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;\n}\n\n/** Parse a `rgb(...)`, `rgba(...)`, or `#rrggbb` string into `{r, g, b}` (0–255). */\nfunction parseRgb(color: string): { r: number; g: number; b: number } | undefined {\n const rgbMatch = color.match(/(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)/);\n if (rgbMatch) {\n return { r: +rgbMatch[1], g: +rgbMatch[2], b: +rgbMatch[3] };\n }\n if (color.startsWith('#') && color.length >= 7) {\n return {\n r: parseInt(color.slice(1, 3), 16),\n g: parseInt(color.slice(3, 5), 16),\n b: parseInt(color.slice(5, 7), 16),\n };\n }\n return undefined;\n}\n\n/** Flatten `fg` at `alpha` over `bg` into an opaque `rgb(...)` string. */\nfunction blendOverBackground(fg: string, bg: string, alpha: number): string {\n const f = parseRgb(fg);\n const b = parseRgb(bg);\n if (!f || !b) return fg;\n const r = Math.round(f.r * alpha + b.r * (1 - alpha));\n const g = Math.round(f.g * alpha + b.g * (1 - alpha));\n const bl = Math.round(f.b * alpha + b.b * (1 - alpha));\n return `rgb(${r}, ${g}, ${bl})`;\n}\n\n/**\n * Greedy single-link clustering: each point joins the first existing cluster\n * with at least one member within `radius` (via the `tip` field), else starts\n * a new cluster. O(n²) but n is small (dozens of area exits per map).\n */\nfunction clusterByProximity<T extends { tip: { x: number; y: number } }>(\n points: T[],\n radius: number,\n): T[][] {\n const r2 = radius * radius;\n const clusters: T[][] = [];\n outer: for (const p of points) {\n for (const c of clusters) {\n for (const q of c) {\n const dx = p.tip.x - q.tip.x;\n const dy = p.tip.y - q.tip.y;\n if (dx * dx + dy * dy <= r2) {\n c.push(p);\n continue outer;\n }\n }\n }\n clusters.push([p]);\n }\n return clusters;\n}\n\n/**\n * Engine-neutral scene composition pipeline.\n *\n * Walks a {@link MapData} area/plane and emits world-space {@link Shape}s per\n * logical layer (grid / link / room / topLabel) plus the metadata downstream\n * consumers need (cull entries, hit zones, drawn-geometry snapshots).\n *\n * The pipeline knows nothing about Konva, SVG, Canvas, or the\n * {@link buildDrawCommands}: callers run the shape lists through whatever\n * renderer fits their target. Both the interactive\n * {@link KonvaRenderBackend} and the exporters consume the same\n * {@link SceneBuildResult} this pipeline produces.\n */\nexport class ScenePipeline {\n private readonly mapReader: IMapReader;\n private readonly settings: Settings;\n readonly exitRenderer: ExitRenderer;\n\n // Per-layer shape accumulators reset on each buildScene; insertion order\n // mirrors the legacy addNode call order so renderers can replay the scene\n // without re-sorting (e.g. labels → link exits → special exits & stubs on\n // the link layer; rooms → area name → area-exit labels on the room layer).\n private linkShapes: Shape[] = [];\n private roomShapes: Shape[] = [];\n private topLabelShapes: Shape[] = [];\n private labelShapeRefs: LabelShapeRef[] = [];\n private specialExitShapeRefs: SpecialExitShapeRef[] = [];\n private stubShapeRefs: StubShapeRef[] = [];\n private areaExitLabelShapeRefs: AreaExitLabelShapeRef[] = [];\n // Room ids rendered as neighbour spill (other-area rooms projected into this\n // frame). Used to suppress cross-area arrows/labels for crossings into them\n // (the spill draws plain connectors instead) and to skip area-exit zones that\n // would otherwise emanate from the spilled rooms.\n private spilledRoomIds: Set<number> = new Set();\n\n constructor(mapReader: IMapReader, settings: Settings) {\n this.mapReader = mapReader;\n this.settings = settings;\n this.exitRenderer = new ExitRenderer(mapReader, settings);\n }\n\n /**\n * Build the full scene for an area/plane.\n * Clears layers, renders grid → labels → exits → rooms → area name.\n * Returns data for culling and interaction (room nodes, exit data, hit zones).\n *\n * The `lens` filters which rooms paint and how partially-visible exits are\n * treated. Pass {@link ALL_VISIBLE} (or omit) for an unfiltered build.\n */\n buildScene(area: IArea, plane: IPlane, zIndex: number, lens: RoomLens = ALL_VISIBLE, spill?: NeighborSpill): SceneBuildResult {\n this.linkShapes = [];\n this.roomShapes = [];\n this.topLabelShapes = [];\n this.labelShapeRefs = [];\n this.specialExitShapeRefs = [];\n this.stubShapeRefs = [];\n this.areaExitLabelShapeRefs = [];\n this.spilledRoomIds = spill ? new Set(spill.rooms.map(r => r.room.id)) : new Set();\n\n // Labels\n this.renderLabels(plane.getLabels(), area.getAreaId());\n\n // Fold \"hide\"-mode hidden rooms into the lens, so both the room filter\n // (isVisible) and the exit treatment (getExitTreatment → \"hidden\") drop\n // them through the normal lens machinery rather than special cases.\n const hideHidden = this.settings.hiddenRooms === \"hide\";\n const effectiveLens = hiddenAwareLens(lens, hideHidden);\n\n // Link exits (two-way connections)\n const exitResult = this.renderLinkExits(area.getLinkExits(zIndex), zIndex, effectiveLens);\n\n // Visible rooms only — the lens drops the rest (exploration + hidden rooms).\n const visibleRooms = (plane.getRooms() ?? []).filter(r => effectiveLens.isVisible(r));\n\n // Spilled neighbour rooms become first-class rooms: cloned into this\n // frame (coords + custom lines offset) and rendered through the same room\n // pass, so their bodies, custom lines, stubs and inner exits all use the\n // normal logic. Connectors for their plain exits are drawn separately.\n const spilledRooms = spill\n ? spill.rooms\n .filter(r => !(hideHidden && isRoomHidden(r.room)))\n .map(r => projectRoom(r.room, r.x, r.y, area.getAreaId(), zIndex))\n : [];\n\n // Rooms (with stubs, special exits, inner exits)\n const roomResult = this.renderRooms([...visibleRooms, ...spilledRooms], zIndex);\n\n // Plain connectors for the spill's regular/special exits (custom-line\n // exits are already drawn as polylines by the room pass above).\n if (spill) this.renderSpillConnectors(spill);\n\n // Area name\n this.renderAreaName(area, plane);\n\n const areaExitHitZones = [...exitResult.areaExitHitZones, ...roomResult.areaExitHitZones];\n\n // Area exit labels (one per spatial cluster of exits to the same target area).\n // Labels themselves become clickable hit zones that navigate to the target area.\n const labelHitZones = this.renderAreaExitLabels(\n areaExitHitZones,\n area.getAreaId(),\n visibleRooms,\n exitResult.standaloneExitShapeRefs.map(n => n.bounds),\n );\n areaExitHitZones.push(...labelHitZones);\n\n // All hit-bearing layers concatenated. HitTester walks the tree and\n // ignores shapes without `hit`, so feeding it the union is cheap and\n // keeps every annotated shape (rooms, exits, stubs, labels, area-exit\n // labels, …) reachable from a single index.\n const hitShapes: Shape[] = [\n ...this.linkShapes,\n ...this.roomShapes,\n ...this.topLabelShapes,\n ];\n\n return {\n roomShapeRefs: roomResult.roomShapeRefs,\n standaloneExitShapeRefs: exitResult.standaloneExitShapeRefs,\n labelShapeRefs: this.labelShapeRefs,\n specialExitShapeRefs: this.specialExitShapeRefs,\n stubShapeRefs: this.stubShapeRefs,\n areaExitLabelShapeRefs: this.areaExitLabelShapeRefs,\n areaExitHitZones,\n drawnExits: exitResult.drawnExits,\n drawnSpecialExits: roomResult.drawnSpecialExits,\n drawnStubs: roomResult.drawnStubs,\n hitShapes,\n sceneShapes: {\n grid: [],\n link: this.linkShapes,\n room: this.roomShapes,\n topLabel: this.topLabelShapes,\n },\n };\n }\n\n getEffectiveBounds(area: IArea, plane: IPlane) {\n return this.settings.uniformLevelSize ? area.getFullBounds() : plane.getBounds();\n }\n\n // --- Rooms ---\n\n private renderRooms(rooms: MapData.Room[], _zIndex: number) {\n const roomShapeRefs = new Map<number, RoomShapeRef>();\n const areaExitHitZones: AreaExitHitZone[] = [];\n const drawnSpecialExits: DrawnSpecialExitEntry[] = [];\n const drawnStubs: DrawnStubEntry[] = [];\n\n // Queue room shapes and push them onto roomShapes after all rooms'\n // special exits and stubs have been pushed onto linkShapes. This keeps\n // the correct z-order (all exits/stubs under all rooms) on shared\n // logical layers.\n const queuedRoomShapes: Array<[MapData.Room, GroupShape]> = [];\n\n rooms.forEach(room => {\n // Room body + inner exits — emitted as a single SceneIR group.\n // flatPipeline is always true now: per-style coordinate warping\n // (e.g. Isometric) is applied downstream by Style.transform.\n const roomShape = layoutRoom(room, this.mapReader, this.settings, {\n flatPipeline: true,\n ...hiddenRoomLayoutOptions(room, this.settings.hiddenRooms),\n });\n roomShape.children.push(...layoutInnerExits(room, this.mapReader, this.settings));\n\n // Special exits → link layer. The active Style applies any\n // cube-base offset when transforming the link group.\n for (const se of computeSpecialExits(room, this.settings)) {\n const seShape = specialExitToShape(se, room.id);\n this.linkShapes.push(seShape);\n\n const pts = se.line.points;\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (let i = 0; i < pts.length; i += 2) {\n if (pts[i] < minX) minX = pts[i];\n if (pts[i] > maxX) maxX = pts[i];\n if (pts[i + 1] < minY) minY = pts[i + 1];\n if (pts[i + 1] > maxY) maxY = pts[i + 1];\n }\n const seBounds = {x: minX, y: minY, width: maxX - minX, height: maxY - minY};\n drawnSpecialExits.push({\n roomId: room.id,\n exitName: se.dir,\n points: pts,\n stroke: se.line.stroke,\n strokeWidth: se.line.strokeWidth,\n dash: se.line.dash,\n hasArrow: !!se.arrow,\n arrowTip: se.arrow ? {x: se.arrow.tipX, y: se.arrow.tipY} : undefined,\n bounds: seBounds,\n });\n this.specialExitShapeRefs.push({shape: seShape, bounds: seBounds});\n }\n\n // Area-exit hit zones (cross-area arrows/labels). Skip them entirely\n // for spilled rooms — a spilled preview shouldn't sprout its own\n // area-exit arrows — and for current rooms, skip targets that are\n // themselves spilled (those crossings are drawn as plain connectors).\n if (!this.spilledRoomIds.has(room.id)) {\n this.exitRenderer.getSpecialExitAreaTargets(room).forEach(zone => {\n if (this.spilledRoomIds.has(zone.targetRoomId)) return;\n areaExitHitZones.push({\n bounds: zone.bounds,\n targetRoomId: zone.targetRoomId,\n from: zone.from,\n tip: zone.tip,\n arrowColor: zone.arrowColor,\n });\n });\n this.exitRenderer.getInnerExitAreaTargets(room).forEach(zone => {\n if (this.spilledRoomIds.has(zone.targetRoomId)) return;\n areaExitHitZones.push({\n bounds: zone.bounds,\n targetRoomId: zone.targetRoomId,\n from: zone.from,\n tip: zone.tip,\n arrowColor: zone.arrowColor,\n });\n });\n }\n\n // Stubs → link layer (so they render under rooms, like exits)\n for (const stub of computeStubs(room, this.settings)) {\n const stubShape = stubToShape(stub);\n this.linkShapes.push(stubShape);\n drawnStubs.push({\n roomId: stub.roomId,\n direction: stub.direction,\n x1: stub.x1, y1: stub.y1,\n x2: stub.x2, y2: stub.y2,\n stroke: stub.stroke,\n strokeWidth: stub.strokeWidth,\n });\n this.stubShapeRefs.push({\n shape: stubShape,\n bounds: {\n x: Math.min(stub.x1, stub.x2),\n y: Math.min(stub.y1, stub.y2),\n width: Math.abs(stub.x2 - stub.x1),\n height: Math.abs(stub.y2 - stub.y1),\n },\n });\n }\n\n queuedRoomShapes.push([room, roomShape]);\n roomShapeRefs.set(room.id, {room, shape: roomShape});\n });\n\n for (const [, roomShape] of queuedRoomShapes) {\n this.roomShapes.push(roomShape);\n }\n\n return {roomShapeRefs, areaExitHitZones, drawnSpecialExits, drawnStubs};\n }\n\n // --- Neighbouring-area spill ---\n\n /**\n * Draw plain connector lines for the spill's regular/special exits (the room\n * bodies, custom lines, stubs and inner exits are rendered through the normal\n * room pass via {@link projectRoom} clones). Lines go on the link layer so\n * they sit under the rooms, like ordinary exits.\n */\n private renderSpillConnectors(spill: NeighborSpill) {\n for (const e of spill.edges) {\n this.linkShapes.push({\n type: \"line\",\n layer: \"link\",\n points: [e.ax, e.ay, e.bx, e.by],\n paint: {\n stroke: this.settings.lineColor,\n strokeWidth: this.settings.lineWidth,\n },\n });\n }\n }\n\n // --- Link Exits ---\n\n private renderLinkExits(exits: IExit[], zIndex: number, lens: RoomLens) {\n const standaloneExitShapeRefs: StandaloneExitShapeRef[] = [];\n const areaExitHitZones: AreaExitHitZone[] = [];\n const drawnExits: DrawnExitEntry[] = [];\n\n exits.forEach(exit => {\n const roomA = this.mapReader.getRoom(exit.a);\n const roomB = this.mapReader.getRoom(exit.b);\n if (!roomA || !roomB) return;\n\n const treatment: ExitTreatment = lens.getExitTreatment\n ? lens.getExitTreatment(exit, roomA, roomB)\n : defaultExitTreatment(lens, exit, roomA, roomB);\n\n if (treatment === \"hidden\") return;\n\n if (treatment === \"stub\") {\n const stub = this.buildLensStub(exit, roomA, roomB, lens);\n if (stub) this.emitLensStub(stub);\n return;\n }\n\n const data = this.exitRenderer.renderData(exit, zIndex);\n if (!data) return;\n // When the far side is a spilled neighbour room, drop the cross-area\n // arrow + its label — the spill draws a plain connector to it instead.\n if (data.targetRoomId !== undefined && this.spilledRoomIds.has(data.targetRoomId)) return;\n const exitShape = layoutLinkExit(data, {\n kind: \"exit\",\n id: `${exit.a}:${exit.b}:${exit.aDir ?? \"\"}:${exit.bDir ?? \"\"}`,\n payload: {\n a: exit.a,\n b: exit.b,\n aDir: exit.aDir,\n bDir: exit.bDir,\n kind: exit.kind ?? \"exit\",\n },\n });\n this.linkShapes.push(exitShape);\n standaloneExitShapeRefs.push({shape: exitShape, bounds: data.bounds, targetRoomId: data.targetRoomId});\n drawnExits.push({\n a: exit.a,\n b: exit.b,\n aDir: exit.aDir,\n bDir: exit.bDir,\n kind: exit.kind ?? \"exit\",\n zIndex: exit.zIndex,\n data,\n });\n if (data.targetRoomId !== undefined) {\n areaExitHitZones.push({\n bounds: data.bounds,\n targetRoomId: data.targetRoomId,\n from: data.from,\n tip: data.tip,\n arrowColor: data.arrowColor,\n });\n }\n });\n\n return {standaloneExitShapeRefs, areaExitHitZones, drawnExits};\n }\n\n /**\n * Build a stub anchored at the visible endpoint of an exit, pointing\n * toward the hidden one. Returns undefined when the exit can't be drawn\n * as a stub (no planar direction on the visible side, both sides hidden,\n * or both sides visible — the last is a caller bug).\n */\n private buildLensStub(\n exit: IExit,\n roomA: MapData.Room,\n roomB: MapData.Room,\n lens: RoomLens,\n ): StubData | undefined {\n const aVis = lens.isVisible(roomA);\n const bVis = lens.isVisible(roomB);\n if (aVis === bVis) return undefined;\n\n const sourceRoom = aVis ? roomA : roomB;\n const dir = aVis ? exit.aDir : exit.bDir;\n if (!dir || !regularExits.includes(dir)) return undefined;\n if (sourceRoom.customLines[longToShort[dir]]) return undefined;\n\n const rs = this.settings.roomSize;\n const start = this.getRoomEdgePoint(sourceRoom.x, sourceRoom.y, dir, rs / 2);\n const end = movePoint(sourceRoom.x, sourceRoom.y, dir, rs / 2 + 0.5);\n return {\n roomId: sourceRoom.id,\n direction: dir,\n x1: start.x, y1: start.y,\n x2: end.x, y2: end.y,\n stroke: this.settings.lineColor,\n strokeWidth: this.settings.lineWidth,\n };\n }\n\n private emitLensStub(stub: StubData) {\n const stubShape = stubToShape(stub);\n this.linkShapes.push(stubShape);\n this.stubShapeRefs.push({\n shape: stubShape,\n bounds: {\n x: Math.min(stub.x1, stub.x2),\n y: Math.min(stub.y1, stub.y2),\n width: Math.abs(stub.x2 - stub.x1),\n height: Math.abs(stub.y2 - stub.y1),\n },\n });\n }\n\n private getRoomEdgePoint(x: number, y: number, direction: MapData.direction, distance: number) {\n if (this.settings.roomShape === \"circle\") return movePointCircle(x, y, direction, distance);\n if (this.settings.roomShape === \"roundedRectangle\") return movePointRoundedRect(x, y, direction, distance, this.settings.roomSize * 0.2);\n return movePoint(x, y, direction, distance);\n }\n\n /**\n * Build the world-space shape tree for one inter-room exit. Used by the\n * live renderer's current-room overlay path to recolour a single exit\n * without rebuilding the whole scene.\n */\n buildExitShape(data: ExitDrawData): GroupShape {\n return layoutLinkExit(data);\n }\n\n // --- Labels ---\n\n private renderLabels(labels: MapData.Label[], areaId: number) {\n for (const label of labels) {\n const shape = labelToShape(label, this.settings);\n if (!shape) continue;\n // labelToShape leaves a placeholder `hit` without an id; replace it\n // here so the HitTester can return `{ id, areaId }` directly to\n // editor consumers without a follow-up lookup.\n shape.hit = {\n kind: \"label\",\n id: label.labelId,\n payload: {label, areaId},\n };\n if (label.showOnTop) {\n this.topLabelShapes.push(shape);\n } else {\n this.linkShapes.push(shape);\n }\n // noScaling labels have screen-pixel dimensions — their bounds can't\n // be compared to world-space viewport bounds, so skip them for culling.\n if (!label.noScaling) {\n this.labelShapeRefs.push({\n shape,\n bounds: {x: label.X, y: -label.Y, width: label.Width, height: label.Height},\n });\n }\n }\n }\n\n // --- Area Exit Labels ---\n\n private renderAreaExitLabels(\n hitZones: AreaExitHitZone[],\n currentAreaId: number,\n rooms: MapData.Room[],\n exitLineBounds: Bounds[],\n ): AreaExitHitZone[] {\n const labelHitZones: AreaExitHitZone[] = [];\n if (!this.settings.areaExitLabels || hitZones.length === 0) return labelHitZones;\n\n type Point = {\n tip: { x: number; y: number };\n dir: { x: number; y: number };\n color: string;\n bounds: Bounds;\n targetRoomId: number;\n };\n type Placement = {\n cluster: Point[];\n boxX: number; boxY: number; boxW: number; boxH: number;\n color: string;\n };\n\n // Filter to cross-area exits with direction info, group by target area.\n const byArea = new Map<number, Point[]>();\n for (const zone of hitZones) {\n if (!zone.tip || !zone.from) continue;\n const targetRoom = this.mapReader.getRoom(zone.targetRoomId);\n if (!targetRoom || targetRoom.area === currentAreaId) continue;\n const dx = zone.tip.x - zone.from.x;\n const dy = zone.tip.y - zone.from.y;\n const len = Math.hypot(dx, dy) || 1;\n const pt: Point = {\n tip: zone.tip,\n dir: { x: dx / len, y: dy / len },\n color: zone.arrowColor ?? 'white',\n bounds: zone.bounds,\n targetRoomId: zone.targetRoomId,\n };\n const arr = byArea.get(targetRoom.area);\n if (arr) arr.push(pt); else byArea.set(targetRoom.area, [pt]);\n }\n\n const CLUSTER_RADIUS = 10; // generous — same-area exits usually benefit from a single label\n const MERGE_GAP = 2; // post-placement, merge same-area labels whose boxes are this close\n const LABEL_GAP = 0.5;\n const DIR_THRESHOLD = 0.4; // averaged direction below this magnitude → treat as \"no preference\"\n const SPREAD_THRESHOLD = 3; // map units — tip spread above this means the cluster is wide\n // enough that its centroid (the gap) beats any directional push\n const fontSize = this.settings.areaExitLabelFontSize;\n const padX = fontSize * 0.6;\n const padY = fontSize * 0.333;\n const charWidth = fontSize * 0.55;\n const textHeight = fontSize * 1.1;\n const cornerRadius = fontSize * 0.6;\n const FILL_ALPHA = 0.35;\n const strokeWidth = fontSize * 0.1;\n\n // All rooms on this plane are obstacles for labels.\n const rs = this.settings.roomSize;\n const roomBoxes: Bounds[] = rooms.map(r => ({\n x: r.x - rs / 2, y: r.y - rs / 2, width: rs, height: rs,\n }));\n // All area-exit arrow bounds are obstacles too — the placer excludes\n // the current cluster's own arrows when checking.\n const allArrowBounds: Bounds[] = hitZones.map(z => z.bounds);\n\n const boxesOverlap = (a: { x: number; y: number; w: number; h: number },\n b: { x: number; y: number; w: number; h: number }) =>\n a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;\n\n /** True if boxes overlap OR are within `gap` units of each other (AABB gap check). */\n const boxesCloseOrOverlap = (\n a: { x: number; y: number; w: number; h: number },\n b: { x: number; y: number; w: number; h: number },\n gap: number,\n ) => {\n const dx = Math.max(0, Math.max(a.x, b.x) - Math.min(a.x + a.w, b.x + b.w));\n const dy = Math.max(0, Math.max(a.y, b.y) - Math.min(a.y + a.h, b.y + b.h));\n return Math.hypot(dx, dy) <= gap;\n };\n\n const overlapsAny = (\n bx: number, by: number, bw: number, bh: number,\n obstacles: Bounds[],\n ) => {\n for (const r of obstacles) {\n if (bx < r.x + r.width && bx + bw > r.x && by < r.y + r.height && by + bh > r.y) return true;\n }\n return false;\n };\n\n // Compass ring used as fallback candidates, ordered (cardinals first).\n const ring: Array<[number, number]> = [\n [0, 1], [0, -1], [1, 0], [-1, 0],\n [0.707, 0.707], [-0.707, 0.707], [0.707, -0.707], [-0.707, -0.707],\n ];\n\n const placeCluster = (\n cluster: Point[],\n name: string,\n hintCenter?: { x: number; y: number },\n ): Placement | undefined => {\n const textWidth = name.length * charWidth;\n const boxW = textWidth + padX * 2;\n const boxH = textHeight + padY * 2;\n\n let cxSum = 0, cySum = 0, dxSum = 0, dySum = 0;\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n const colorTally = new Map<string, number>();\n for (const p of cluster) {\n cxSum += p.tip.x; cySum += p.tip.y;\n dxSum += p.dir.x; dySum += p.dir.y;\n if (p.tip.x < minX) minX = p.tip.x;\n if (p.tip.x > maxX) maxX = p.tip.x;\n if (p.tip.y < minY) minY = p.tip.y;\n if (p.tip.y > maxY) maxY = p.tip.y;\n colorTally.set(p.color, (colorTally.get(p.color) ?? 0) + 1);\n }\n const n = cluster.length;\n const dLen = Math.hypot(dxSum, dySum);\n const udx = dLen > 0 ? dxSum / dLen : 0;\n const udy = dLen > 0 ? dySum / dLen : 0;\n\n // Spread = max distance between any two tips in the cluster.\n // Large spread → the cluster came from merging distant clumps, so its\n // centroid is the natural spot and directional push looks off-center.\n let spread = 0;\n for (let i = 0; i < cluster.length; i++) {\n for (let j = i + 1; j < cluster.length; j++) {\n const sx = cluster[i].tip.x - cluster[j].tip.x;\n const sy = cluster[i].tip.y - cluster[j].tip.y;\n const d = Math.hypot(sx, sy);\n if (d > spread) spread = d;\n }\n }\n const useSpreadMidpoint = spread > SPREAD_THRESHOLD;\n // A consistent direction-of-travel is meaningful even when the\n // cluster is spread — e.g. a column of rooms all exiting east.\n // Spread perpendicular to travel doesn't invalidate the push.\n const hasPreferred = dLen / n >= DIR_THRESHOLD;\n\n // Anchor precedence:\n // 1) hintCenter — merge pass passes the midpoint between colliding boxes.\n // 2) Wide clusters — use the tip bounding-box midpoint (unbiased by\n // arrow count; the weighted centroid pulls toward whichever\n // sub-group has more arrows).\n // 3) Tight clusters — weighted centroid is fine, sub-groups don't exist.\n const cx = hintCenter?.x ?? (useSpreadMidpoint ? (minX + maxX) / 2 : cxSum / n);\n const cy = hintCenter?.y ?? (useSpreadMidpoint ? (minY + maxY) / 2 : cySum / n);\n\n let color = 'white';\n let bestCount = 0;\n for (const [c, count] of colorTally) {\n if (count > bestCount) { color = c; bestCount = count; }\n }\n\n // Obstacles include rooms, every area-exit arrow, and every regular\n // link exit line — labels must not run through connecting lines either.\n const obstacles = roomBoxes.concat(allArrowBounds).concat(exitLineBounds);\n\n const offsetAlong = (dx: number, dy: number, extra = 0) =>\n LABEL_GAP + extra + Math.abs(dx) * boxW / 2 + Math.abs(dy) * boxH / 2;\n\n const candidates: Array<{ x: number; y: number }> = [];\n if (hasPreferred) {\n const off = offsetAlong(udx, udy);\n candidates.push({ x: cx + udx * off, y: cy + udy * off });\n }\n // Try the raw centroid early — it often IS the empty gap between\n // merged clumps, and in that case every directional push looks worse.\n candidates.push({ x: cx, y: cy });\n // Compass ring at three escape radii to handle dense maps.\n for (const extra of [0, 0.6, 1.4]) {\n for (const [dx, dy] of ring) {\n const off = offsetAlong(dx, dy, extra);\n candidates.push({ x: cx + dx * off, y: cy + dy * off });\n }\n }\n\n // Return undefined if no candidate clears every obstacle — better to\n // drop the label than draw it on top of rooms/arrows/lines.\n for (const c of candidates) {\n const bx = c.x - boxW / 2;\n const by = c.y - boxH / 2;\n if (!overlapsAny(bx, by, boxW, boxH, obstacles)) {\n return {\n cluster,\n boxX: bx, boxY: by,\n boxW, boxH,\n color,\n };\n }\n }\n return undefined;\n };\n\n for (const [areaId, points] of byArea) {\n const name = this.mapReader.getArea(areaId)?.getAreaName() || `Area ${areaId}`;\n\n // Initial spatial clustering by arrow-tip proximity.\n const rawClusters = clusterByProximity(points, CLUSTER_RADIUS);\n let placements = rawClusters\n .map(c => placeCluster(c, name))\n .filter((p): p is Placement => p !== undefined);\n\n // Merge placements whose final boxes collide — two distant clumps that\n // both gravitated into the same empty gap become a single combined label.\n let changed = true;\n while (changed && placements.length > 1) {\n changed = false;\n outer: for (let i = 0; i < placements.length; i++) {\n for (let j = i + 1; j < placements.length; j++) {\n const a = { x: placements[i].boxX, y: placements[i].boxY, w: placements[i].boxW, h: placements[i].boxH };\n const b = { x: placements[j].boxX, y: placements[j].boxY, w: placements[j].boxW, h: placements[j].boxH };\n // Merge when labels are close enough to read as \"two for the\n // same area\" — not only when they literally overlap.\n if (boxesCloseOrOverlap(a, b, MERGE_GAP)) {\n const combined = [...placements[i].cluster, ...placements[j].cluster];\n const midpoint = {\n x: (a.x + a.w / 2 + b.x + b.w / 2) / 2,\n y: (a.y + a.h / 2 + b.y + b.h / 2) / 2,\n };\n const merged = placeCluster(combined, name, midpoint)\n ?? placeCluster(combined, name);\n if (merged) {\n placements[i] = merged;\n placements.splice(j, 1);\n } else {\n // Combined placement couldn't find a clear spot — drop both.\n placements.splice(j, 1);\n placements.splice(i, 1);\n }\n changed = true;\n break outer;\n }\n }\n }\n }\n\n for (const p of placements) {\n const fill = colorWithAlpha(p.color, FILL_ALPHA);\n // Pick text color against the *effective* label bg (fill alpha-blended\n // over the map background), not the raw arrow color — otherwise a pale\n // arrow color at low alpha still reads as dark and needs light text.\n const effectiveBg = blendOverBackground(p.color, this.settings.backgroundColor, FILL_ALPHA);\n const textColor = colorLightness(effectiveBg) > 0.55 ? '#000' : '#fff';\n const labelShape: Shape = {\n type: \"group\",\n x: 0,\n y: 0,\n layer: \"room\",\n hit: {\n kind: \"areaExit\",\n id: p.cluster[0].targetRoomId,\n payload: {targetRoomId: p.cluster[0].targetRoomId},\n },\n children: [\n {\n type: \"rect\",\n x: p.boxX, y: p.boxY,\n width: p.boxW, height: p.boxH,\n cornerRadius,\n paint: {fill, stroke: p.color, strokeWidth},\n },\n {\n type: \"text\",\n x: p.boxX + padX,\n y: p.boxY + padY,\n width: p.boxW - padX * 2,\n height: p.boxH - padY * 2,\n text: name,\n fontSize,\n fontFamily: this.settings.fontFamily,\n fill: textColor,\n align: \"center\",\n verticalAlign: \"middle\",\n },\n ],\n };\n const labelBounds = {x: p.boxX, y: p.boxY, width: p.boxW, height: p.boxH};\n this.roomShapes.push(labelShape);\n this.areaExitLabelShapeRefs.push({shape: labelShape as GroupShape, bounds: labelBounds});\n\n // Label is clickable — navigate to the target area (any room from the\n // cluster works since they all live there).\n labelHitZones.push({\n bounds: labelBounds,\n targetRoomId: p.cluster[0].targetRoomId,\n });\n }\n }\n\n return labelHitZones;\n }\n\n // --- Area Name ---\n\n private renderAreaName(area: IArea, plane: IPlane) {\n if (!this.settings.areaName) return;\n const name = area.getAreaName();\n if (!name) return;\n const bounds = this.getEffectiveBounds(area, plane);\n const areaNameShape: GroupShape = {\n type: \"group\",\n x: 0,\n y: 0,\n layer: \"room\",\n children: [{\n type: \"text\",\n x: bounds.minX - 3.5,\n y: bounds.minY - 4.5,\n text: name,\n fontSize: 2.5,\n fontFamily: this.settings.fontFamily,\n fill: this.settings.lineColor,\n }],\n };\n this.roomShapes.push(areaNameShape);\n }\n}\n","import type {RendererEventMap} from \"./types/Settings\";\nimport type {TypedEventEmitter} from \"./TypedEventEmitter\";\nimport type {Camera} from \"./camera/Camera\";\nimport type {MapState} from \"./MapState\";\nimport type {HitResult} from \"./hit/HitTester\";\n\nexport type HitTestCallbacks = {\n clientToMapPoint: (clientX: number, clientY: number) => { x: number; y: number } | null;\n /**\n * Pick the topmost hit-annotated shape at a point in rendered space. The\n * unified entry point for all DOM-level interaction — rooms, area-exit\n * labels, and any future pickable kinds all flow through one query.\n */\n pickAtPoint: (renderedX: number, renderedY: number) => HitResult | null;\n};\n\n/**\n * Handles all DOM interaction on the map container:\n * - Camera: drag (mouse + touch), wheel zoom, pinch zoom, resize\n * - Map: hover cursor, click, right-click, long-press, area exit clicks\n *\n * No Konva dependency — works with any rendering backend.\n */\nexport class InteractionHandler {\n\n private readonly container: HTMLDivElement;\n private readonly camera: Camera;\n private readonly state: MapState;\n private readonly hitTest: HitTestCallbacks;\n private readonly events: TypedEventEmitter<RendererEventMap>;\n\n private lastPinchDistance?: number;\n private readonly cleanupFns: (() => void)[] = [];\n private destroyed = false;\n\n /** Skip cursor changes when an external consumer (e.g. the editor) owns cursor management. */\n private setCursor(value: string) {\n if (this.container.dataset.editorCursor) return;\n this.container.style.cursor = value;\n }\n\n constructor(\n container: HTMLDivElement,\n camera: Camera,\n state: MapState,\n hitTest: HitTestCallbacks,\n events: TypedEventEmitter<RendererEventMap>,\n ) {\n this.container = container;\n this.camera = camera;\n this.state = state;\n this.hitTest = hitTest;\n this.events = events;\n\n this.initViewportEvents();\n this.initMapEvents();\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n for (const cleanup of this.cleanupFns) {\n cleanup();\n }\n this.cleanupFns.length = 0;\n }\n\n private listen<K extends keyof HTMLElementEventMap>(\n target: EventTarget,\n event: K,\n handler: (e: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions,\n ): void;\n private listen(\n target: EventTarget,\n event: string,\n handler: EventListener,\n options?: AddEventListenerOptions,\n ): void;\n private listen(\n target: EventTarget,\n event: string,\n handler: EventListener,\n options?: AddEventListenerOptions,\n ) {\n target.addEventListener(event, handler, options);\n this.cleanupFns.push(() => target.removeEventListener(event, handler, options));\n }\n\n // ===== Camera events (drag, zoom, resize) =====\n\n private initViewportEvents() {\n const container = this.container;\n const camera = this.camera;\n const scaleBy = 1.1;\n\n // --- Resize ---\n const handleResize = () => {\n // Batch so subscribers only see the final (resized + recentred)\n // state. Without this, they'd first observe the new size with the\n // pre-resize position, which can briefly drop the player room out\n // of the reported viewport bounds.\n camera.batch(() => {\n camera.setSize(container.clientWidth, container.clientHeight);\n if (camera.centerOnResize && this.state.positionRoomId) {\n const room = this.state.mapReader.getRoom(this.state.positionRoomId);\n if (room) camera.panToMapPoint(room.x, room.y);\n }\n });\n };\n\n if (typeof window !== 'undefined') {\n this.listen(window, 'resize', handleResize);\n }\n this.listen(container, 'resize', handleResize);\n\n // --- Mouse drag (pointer events) ---\n let pointerDown = false;\n let pointerId: number | undefined;\n\n this.listen(container, 'pointerdown', (e) => {\n if (e.button !== 0 || e.pointerType === 'touch') return;\n pointerDown = true;\n pointerId = e.pointerId;\n container.setPointerCapture(e.pointerId);\n const rect = container.getBoundingClientRect();\n camera.startDrag(e.clientX - rect.left, e.clientY - rect.top);\n });\n\n this.listen(container, 'pointermove', (e) => {\n if (!pointerDown || e.pointerId !== pointerId) return;\n const rect = container.getBoundingClientRect();\n camera.updateDrag(e.clientX - rect.left, e.clientY - rect.top);\n this.events.emit('pan', camera.getViewportBounds());\n });\n\n this.listen(container, 'pointerup', (e) => {\n if (e.pointerId !== pointerId) return;\n pointerDown = false;\n pointerId = undefined;\n camera.endDrag();\n this.events.emit('pan', camera.getViewportBounds());\n });\n\n this.listen(container, 'pointercancel', (e) => {\n if (e.pointerId !== pointerId) return;\n pointerDown = false;\n pointerId = undefined;\n camera.endDrag();\n });\n\n // --- Touch drag (single finger) + pinch zoom (two fingers) ---\n let touchDragId: number | undefined;\n\n this.listen(container, 'touchstart', (e: TouchEvent) => {\n if (e.touches.length === 1) {\n const touch = e.touches[0];\n touchDragId = touch.identifier;\n const rect = container.getBoundingClientRect();\n camera.startDrag(touch.clientX - rect.left, touch.clientY - rect.top);\n } else {\n if (camera.isDragging()) camera.endDrag();\n touchDragId = undefined;\n }\n }, {passive: true});\n\n this.listen(container, 'touchmove', (e: TouchEvent) => {\n const touches = e.touches;\n\n // Pinch zoom (two fingers)\n if (touches.length >= 2) {\n e.preventDefault();\n if (camera.isDragging()) camera.endDrag();\n touchDragId = undefined;\n\n const rect = container.getBoundingClientRect();\n const p1 = {x: touches[0].clientX - rect.left, y: touches[0].clientY - rect.top};\n const p2 = {x: touches[1].clientX - rect.left, y: touches[1].clientY - rect.top};\n this.handlePinch(p1, p2);\n return;\n }\n\n // Single finger drag\n if (touches.length === 1 && touchDragId === touches[0].identifier) {\n const touch = touches[0];\n const rect = container.getBoundingClientRect();\n camera.updateDrag(touch.clientX - rect.left, touch.clientY - rect.top);\n this.events.emit('pan', camera.getViewportBounds());\n }\n });\n\n this.listen(container, 'touchend', (e: TouchEvent) => {\n this.lastPinchDistance = undefined;\n if (e.touches.length === 0) {\n if (camera.isDragging()) {\n camera.endDrag();\n this.events.emit('pan', camera.getViewportBounds());\n }\n touchDragId = undefined;\n } else if (e.touches.length === 1) {\n const touch = e.touches[0];\n touchDragId = touch.identifier;\n const rect = container.getBoundingClientRect();\n camera.startDrag(touch.clientX - rect.left, touch.clientY - rect.top);\n }\n }, {passive: true});\n\n this.listen(container, 'touchcancel', () => {\n this.lastPinchDistance = undefined;\n if (camera.isDragging()) camera.endDrag();\n touchDragId = undefined;\n }, {passive: true});\n\n // --- Wheel zoom ---\n this.listen(container, 'wheel', (e: WheelEvent) => {\n e.preventDefault();\n const rect = container.getBoundingClientRect();\n const screenX = e.clientX - rect.left;\n const screenY = e.clientY - rect.top;\n\n let direction = e.deltaY > 0 ? -1 : 1;\n if (e.ctrlKey) direction = -direction;\n\n const newZoom = direction > 0 ? camera.zoom * scaleBy : camera.zoom / scaleBy;\n if (camera.zoomToPoint(newZoom, screenX, screenY)) {\n this.events.emit('zoom', {zoom: camera.zoom});\n this.events.emit('pan', camera.getViewportBounds());\n }\n }, {passive: false});\n }\n\n private handlePinch(p1: {x: number; y: number}, p2: {x: number; y: number}) {\n const distance = Math.hypot(p1.x - p2.x, p1.y - p2.y);\n\n if (this.lastPinchDistance === undefined || this.lastPinchDistance === 0 || distance === 0) {\n this.lastPinchDistance = distance;\n return;\n }\n\n const centerX = (p1.x + p2.x) / 2;\n const centerY = (p1.y + p2.y) / 2;\n const newZoom = this.camera.zoom * (distance / this.lastPinchDistance);\n\n if (this.camera.zoomToPoint(newZoom, centerX, centerY)) {\n this.events.emit('zoom', {zoom: this.camera.zoom});\n this.events.emit('pan', this.camera.getViewportBounds());\n }\n\n this.lastPinchDistance = distance;\n }\n\n // ===== Map events (click, hover, context menu) =====\n\n private initMapEvents() {\n const container = this.container;\n let hoverKind: string | null = null;\n\n this.listen(container, 'mousemove', (e: MouseEvent) => {\n const hit = this.pickAtClientPoint(e.clientX, e.clientY);\n const kind = hit?.kind ?? null;\n if (kind !== hoverKind) {\n hoverKind = kind;\n this.setCursor(kind === \"room\" || kind === \"areaExit\" ? 'pointer' : 'auto');\n }\n });\n\n this.listen(container, 'mouseleave', () => {\n hoverKind = null;\n this.setCursor('auto');\n });\n\n let clickStart: { x: number; y: number } | null = null;\n\n this.listen(container, 'mousedown', (e: MouseEvent) => {\n if (e.button === 0) {\n clickStart = { x: e.clientX, y: e.clientY };\n }\n });\n\n this.listen(container, 'mouseup', (e: MouseEvent) => {\n if (e.button !== 0 || !clickStart) return;\n const dx = e.clientX - clickStart.x;\n const dy = e.clientY - clickStart.y;\n clickStart = null;\n if (dx * dx + dy * dy > 25) return;\n const hit = this.pickAtClientPoint(e.clientX, e.clientY);\n if (hit?.kind === \"room\") {\n this.emitRoomClickEvent(hit.id as number, e.clientX, e.clientY);\n return;\n }\n if (hit?.kind === \"areaExit\") {\n const targetRoomId = (hit.payload as {targetRoomId: number} | undefined)?.targetRoomId\n ?? (hit.id as number);\n this.emitAreaExitClickEvent(targetRoomId, e.clientX, e.clientY);\n return;\n }\n this.emitMapClickEvent();\n });\n\n this.listen(container, 'contextmenu', (e: MouseEvent) => {\n const hit = this.pickAtClientPoint(e.clientX, e.clientY);\n if (hit?.kind === \"room\") {\n e.preventDefault();\n this.emitRoomContextEvent(hit.id as number, e.clientX, e.clientY);\n }\n });\n\n // Long-press support for touch\n let longPressTimeout: number | undefined;\n let longPressStart: { clientX: number; clientY: number } | undefined;\n let dragSuppressed = false;\n\n const clearLongPress = () => {\n if (longPressTimeout !== undefined) {\n window.clearTimeout(longPressTimeout);\n longPressTimeout = undefined;\n }\n longPressStart = undefined;\n dragSuppressed = false;\n };\n\n this.listen(container, 'touchstart', (e: TouchEvent) => {\n clearLongPress();\n if (e.touches.length > 1) return;\n const touch = e.touches[0];\n if (!touch) return;\n const hit = this.pickAtClientPoint(touch.clientX, touch.clientY);\n if (hit?.kind !== \"room\") return;\n longPressStart = { clientX: touch.clientX, clientY: touch.clientY };\n longPressTimeout = window.setTimeout(() => {\n if (longPressStart) {\n const at = this.pickAtClientPoint(longPressStart.clientX, longPressStart.clientY);\n if (at?.kind === \"room\") {\n this.emitRoomContextEvent(at.id as number, longPressStart.clientX, longPressStart.clientY);\n }\n }\n clearLongPress();\n }, 500);\n }, { passive: true });\n\n this.listen(container, 'touchend', () => clearLongPress(), { passive: true });\n this.listen(container, 'touchcancel', () => clearLongPress(), { passive: true });\n\n this.listen(container, 'touchmove', (e: TouchEvent) => {\n if (!longPressStart) return;\n const touch = e.touches[0];\n if (!touch) {\n clearLongPress();\n return;\n }\n const dx = touch.clientX - longPressStart.clientX;\n const dy = touch.clientY - longPressStart.clientY;\n if (dx * dx + dy * dy > 100) {\n clearLongPress();\n }\n }, { passive: true });\n }\n\n // --- Hit testing helpers ---\n\n private pickAtClientPoint(clientX: number, clientY: number): HitResult | null {\n const mapPoint = this.hitTest.clientToMapPoint(clientX, clientY);\n if (!mapPoint) return null;\n return this.hitTest.pickAtPoint(mapPoint.x, mapPoint.y);\n }\n\n // --- Event emitters ---\n\n private emitRoomClickEvent(roomId: number, clientX: number, clientY: number) {\n const bounds = this.container.getBoundingClientRect();\n this.events.emit('roomclick', {\n roomId,\n position: { x: clientX - bounds.left, y: clientY - bounds.top },\n });\n }\n\n private emitRoomContextEvent(roomId: number, clientX: number, clientY: number) {\n const bounds = this.container.getBoundingClientRect();\n this.events.emit('roomcontextmenu', {\n roomId,\n position: { x: clientX - bounds.left, y: clientY - bounds.top },\n });\n }\n\n private emitAreaExitClickEvent(targetRoomId: number, clientX: number, clientY: number) {\n const bounds = this.container.getBoundingClientRect();\n this.events.emit('areaexitclick', {\n targetRoomId,\n position: { x: clientX - bounds.left, y: clientY - bounds.top },\n });\n }\n\n private emitMapClickEvent() {\n this.events.emit('mapclick', undefined);\n }\n}\n","import type {IMapReader} from \"./reader/MapReader\";\nimport type {Settings} from \"./types/Settings\";\nimport {movePoint, movePointCircle, movePointRoundedRect, PlanarDirection, planarDirections, oppositeDirections} from \"./directions\";\nimport {longToShort, regularExits} from \"./reader/Exit\";\n\ntype ConnectionType = 'regular' | 'special' | 'inner' | 'none';\n\ninterface Connection {\n type: ConnectionType;\n fromDir?: MapData.direction;\n toDir?: MapData.direction;\n customLineKey?: string;\n fromRoom: MapData.Room;\n toRoom: MapData.Room;\n}\n\nconst innerExits: MapData.direction[] = [\"up\", \"down\", \"in\", \"out\"];\n\nexport interface PathSegment {\n points: number[];\n}\n\nexport interface PathInnerMarker {\n room: MapData.Room;\n direction: MapData.direction;\n}\n\nexport interface PathCustomLine {\n points: number[];\n}\n\nexport interface PathResult {\n segments: PathSegment[];\n innerMarkers: PathInnerMarker[];\n customLines: PathCustomLine[];\n}\n\nfunction getRoomEdgePoint(settings: Settings, x: number, y: number, direction: MapData.direction, distance: number) {\n if (settings.roomShape === \"circle\") {\n return movePointCircle(x, y, direction, distance);\n } else if (settings.roomShape === \"roundedRectangle\") {\n return movePointRoundedRect(x, y, direction, distance, settings.roomSize * 0.2);\n }\n return movePoint(x, y, direction, distance);\n}\n\nfunction findConnection(mapReader: IMapReader, fromRoom: MapData.Room, toRoom: MapData.Room): Connection {\n // Check regular exits from fromRoom\n for (const [dir, targetId] of Object.entries(fromRoom.exits)) {\n if (targetId === toRoom.id) {\n const direction = dir as MapData.direction;\n\n if (innerExits.includes(direction)) {\n const shortDir = longToShort[direction];\n const customLineKey = fromRoom.customLines[shortDir] ? shortDir :\n fromRoom.customLines[direction] ? direction : undefined;\n return {type: 'inner', fromDir: direction, customLineKey, fromRoom, toRoom};\n }\n\n const shortDir = longToShort[direction];\n if (fromRoom.customLines[shortDir]) {\n return {type: 'special', fromDir: direction, customLineKey: shortDir, fromRoom, toRoom};\n }\n if (fromRoom.customLines[direction]) {\n return {type: 'special', fromDir: direction, customLineKey: direction, fromRoom, toRoom};\n }\n\n const reverseDir = findExitDirection(toRoom, fromRoom.id);\n return {type: 'regular', fromDir: direction, toDir: reverseDir, fromRoom, toRoom};\n }\n }\n\n // Check special exits from fromRoom\n for (const [exitName, targetId] of Object.entries(fromRoom.specialExits)) {\n if (targetId === toRoom.id) {\n if (fromRoom.customLines[exitName]) {\n return {type: 'special', customLineKey: exitName, fromRoom, toRoom};\n }\n return {type: 'inner', fromRoom, toRoom};\n }\n }\n\n // Check reverse (toRoom → fromRoom)\n for (const [dir, targetId] of Object.entries(toRoom.exits)) {\n if (targetId === fromRoom.id) {\n const direction = dir as MapData.direction;\n\n if (innerExits.includes(direction)) {\n const shortDir = longToShort[direction];\n const customLineKey = toRoom.customLines[shortDir] ? shortDir :\n toRoom.customLines[direction] ? direction : undefined;\n return {type: 'inner', toDir: direction, customLineKey, fromRoom, toRoom};\n }\n\n const shortDir = longToShort[direction];\n if (toRoom.customLines[shortDir]) {\n return {type: 'special', toDir: direction, customLineKey: shortDir, fromRoom, toRoom};\n }\n if (toRoom.customLines[direction]) {\n return {type: 'special', toDir: direction, customLineKey: direction, fromRoom, toRoom};\n }\n\n return {type: 'regular', toDir: direction, fromRoom, toRoom};\n }\n }\n\n // Check special exits from toRoom\n for (const [exitName, targetId] of Object.entries(toRoom.specialExits)) {\n if (targetId === fromRoom.id) {\n if (toRoom.customLines[exitName]) {\n return {type: 'special', customLineKey: exitName, fromRoom, toRoom};\n }\n return {type: 'inner', fromRoom, toRoom};\n }\n }\n\n return {type: 'none', fromRoom, toRoom};\n}\n\nfunction findExitDirection(room: MapData.Room, targetId: number): MapData.direction | undefined {\n for (const [dir, id] of Object.entries(room.exits)) {\n if (id === targetId) {\n return dir as MapData.direction;\n }\n }\n return undefined;\n}\n\nfunction isRoomVisible(room: MapData.Room | undefined, currentArea: number, currentZIndex: number) {\n if (!room) return false;\n return room.area === currentArea && room.z === currentZIndex;\n}\n\nfunction getExitToRoom(from: MapData.Room, to: MapData.Room): { direction: MapData.direction } | undefined {\n for (const [dir, targetId] of Object.entries(from.exits)) {\n if (targetId === to.id) {\n return {direction: dir as MapData.direction};\n }\n }\n for (const [_, targetId] of Object.entries(from.specialExits)) {\n if (targetId === to.id) {\n return undefined;\n }\n }\n return undefined;\n}\n\nfunction getDirectionTowards(from: MapData.Room, to: MapData.Room): PlanarDirection | undefined {\n for (const direction of planarDirections) {\n if (from.exits[direction] === to.id) return direction;\n }\n for (const direction of planarDirections) {\n if (to.exits[direction] === from.id) return oppositeDirections[direction];\n }\n return undefined;\n}\n\nfunction addRegularConnectionPoints(settings: Settings, connection: Connection, points: number[]) {\n const {fromRoom, toRoom, fromDir, toDir} = connection;\n\n if (points.length === 0) {\n points.push(fromRoom.x, fromRoom.y);\n }\n\n if (fromDir && regularExits.includes(fromDir)) {\n const fromEdge = getRoomEdgePoint(settings, fromRoom.x, fromRoom.y, fromDir, settings.roomSize / 2);\n points.push(fromEdge.x, fromEdge.y);\n }\n\n if (toDir && regularExits.includes(toDir)) {\n const toEdge = getRoomEdgePoint(settings, toRoom.x, toRoom.y, toDir, settings.roomSize / 2);\n points.push(toEdge.x, toEdge.y);\n }\n\n points.push(toRoom.x, toRoom.y);\n}\n\nfunction addSpecialConnectionPoints(connection: Connection, points: number[]) {\n const {fromRoom, toRoom, customLineKey} = connection;\n\n let room: MapData.Room = fromRoom;\n let customLine: MapData.Line | undefined;\n\n if (customLineKey) {\n customLine = fromRoom.customLines[customLineKey];\n if (!customLine) {\n customLine = toRoom.customLines[customLineKey];\n room = toRoom;\n }\n }\n\n if (points.length === 0) {\n points.push(room.x, room.y);\n }\n\n if (customLine) {\n customLine.points.forEach(point => {\n points.push(point.x, -point.y);\n });\n }\n\n points.push(toRoom.x, toRoom.y);\n}\n\nexport function computePathData(\n mapReader: IMapReader,\n settings: Settings,\n locations: number[],\n currentArea: number,\n currentZIndex: number,\n): PathResult {\n const segments: PathSegment[] = [];\n const innerMarkers: PathInnerMarker[] = [];\n const customLines: PathCustomLine[] = [];\n\n const rooms = locations\n .map(location => mapReader.getRoom(location))\n .filter((room): room is MapData.Room => room !== undefined);\n\n let currentSegmentPoints: number[] = [];\n\n const flushSegment = () => {\n if (currentSegmentPoints.length >= 4) {\n segments.push({points: [...currentSegmentPoints]});\n }\n currentSegmentPoints = [];\n };\n\n const processInnerConnection = (connection: Connection) => {\n const {fromRoom, toRoom, fromDir, toDir, customLineKey} = connection;\n\n if (customLineKey) {\n let room = fromRoom;\n let customLine = fromRoom.customLines[customLineKey];\n if (!customLine) {\n customLine = toRoom.customLines[customLineKey];\n room = toRoom;\n }\n if (customLine) {\n const points: number[] = [room.x, room.y];\n customLine.points.forEach(point => {\n points.push(point.x, -point.y);\n });\n customLines.push({points});\n }\n }\n\n if (fromDir && innerExits.includes(fromDir)) {\n innerMarkers.push({room: fromRoom, direction: fromDir});\n }\n if (toDir && innerExits.includes(toDir)) {\n innerMarkers.push({room: toRoom, direction: toDir});\n }\n };\n\n for (let i = 0; i < rooms.length - 1; i++) {\n const fromRoom = rooms[i];\n const toRoom = rooms[i + 1];\n\n const fromVisible = isRoomVisible(fromRoom, currentArea, currentZIndex);\n const toVisible = isRoomVisible(toRoom, currentArea, currentZIndex);\n\n if (!fromVisible && !toVisible) {\n flushSegment();\n continue;\n }\n\n if (fromVisible && toVisible) {\n const connection = findConnection(mapReader, fromRoom, toRoom);\n\n switch (connection.type) {\n case 'regular':\n addRegularConnectionPoints(settings, connection, currentSegmentPoints);\n break;\n case 'special':\n addSpecialConnectionPoints(connection, currentSegmentPoints);\n break;\n case 'inner':\n flushSegment();\n processInnerConnection(connection);\n break;\n case 'none':\n if (currentSegmentPoints.length === 0) {\n currentSegmentPoints.push(fromRoom.x, fromRoom.y);\n }\n currentSegmentPoints.push(toRoom.x, toRoom.y);\n break;\n }\n } else {\n const visibleRoom = fromVisible ? fromRoom : toRoom;\n const invisibleRoom = fromVisible ? toRoom : fromRoom;\n const exitInfo = getExitToRoom(visibleRoom, invisibleRoom);\n\n if (exitInfo) {\n if (innerExits.includes(exitInfo.direction)) {\n flushSegment();\n innerMarkers.push({room: visibleRoom, direction: exitInfo.direction});\n } else if (regularExits.includes(exitInfo.direction)) {\n if (currentSegmentPoints.length === 0) {\n currentSegmentPoints.push(visibleRoom.x, visibleRoom.y);\n }\n const edgePoint = getRoomEdgePoint(settings, visibleRoom.x, visibleRoom.y, exitInfo.direction, settings.roomSize / 2);\n const stubEnd = movePoint(visibleRoom.x, visibleRoom.y, exitInfo.direction, settings.roomSize);\n currentSegmentPoints.push(edgePoint.x, edgePoint.y, stubEnd.x, stubEnd.y);\n flushSegment();\n }\n } else {\n const dir = getDirectionTowards(visibleRoom, invisibleRoom);\n if (dir) {\n if (currentSegmentPoints.length === 0) {\n currentSegmentPoints.push(visibleRoom.x, visibleRoom.y);\n }\n const edgePoint = getRoomEdgePoint(settings, visibleRoom.x, visibleRoom.y, dir, settings.roomSize / 2);\n const stubEnd = movePoint(visibleRoom.x, visibleRoom.y, dir, settings.roomSize);\n currentSegmentPoints.push(edgePoint.x, edgePoint.y, stubEnd.x, stubEnd.y);\n flushSegment();\n }\n }\n }\n }\n\n flushSegment();\n\n return {segments, innerMarkers, customLines};\n}\n","import type {Settings} from \"../types/Settings\";\nimport type {IMapReader} from \"../reader/MapReader\";\nimport {computePathData} from \"../PathData\";\nimport {computeInnerExitTrianglesForDirection} from \"./InnerExitStyle\";\n\n// --- Highlight ---\n\nexport type HighlightData = {\n shape: 'circle' | 'rect';\n cx: number; cy: number;\n /** For circle: radius. For rect: half-size. */\n size: number;\n cornerRadius: number;\n /**\n * One or more colours. A single colour draws the classic ring/marker; two\n * or more split the highlight into that many equal pie wedges (one colour\n * each).\n */\n colors: string[];\n strokeAlpha: number;\n strokeWidth: number;\n fillAlpha: number;\n dash?: number[];\n dashEnabled: boolean;\n};\n\n/**\n * Resolve the concrete highlight outline shape. `'match'` follows the\n * highlight's {@link HighlightStyle.matchRoomShape} flag against the current\n * roomShape (circle rooms fall back to a circle highlight); any other value\n * forces that shape explicitly.\n */\nfunction resolveHighlightShapeKind(\n hl: Settings['highlight'],\n roomShape: Settings['roomShape'],\n): 'rectangle' | 'roundedRectangle' | 'circle' {\n const sel = hl.shape ?? 'match';\n if (sel !== 'match') return sel;\n const matchRoom = hl.matchRoomShape ?? true;\n if (matchRoom && roomShape !== \"circle\") {\n return roomShape === \"roundedRectangle\" ? 'roundedRectangle' : 'rectangle';\n }\n return 'circle';\n}\n\nexport function computeHighlight(room: MapData.Room, color: string | string[], settings: Settings): HighlightData {\n const hl = settings.highlight;\n const rs = settings.roomSize;\n const factor = hl.sizeFactor;\n const kind = resolveHighlightShapeKind(hl, settings.roomShape);\n const colors = Array.isArray(color) ? (color.length > 0 ? [...color] : ['#ffffff']) : [color];\n return {\n shape: kind === 'circle' ? 'circle' : 'rect',\n cx: room.x,\n cy: room.y,\n size: rs / 2 * factor,\n cornerRadius: kind === 'roundedRectangle' ? rs * factor * 0.2 : 0,\n colors,\n strokeAlpha: hl.strokeAlpha,\n strokeWidth: hl.strokeWidth,\n fillAlpha: hl.fillAlpha,\n dash: hl.dash,\n dashEnabled: hl.dashEnabled,\n };\n}\n\n// --- Position Marker ---\n\nexport type PositionMarkerData = {\n shape: 'circle' | 'rect';\n cx: number; cy: number;\n size: number;\n cornerRadius: number;\n strokeColor: string;\n strokeWidth: number;\n strokeAlpha: number;\n fillColor: string;\n fillAlpha: number;\n dash?: number[];\n dashEnabled: boolean;\n};\n\nexport function computePositionMarker(room: MapData.Room, settings: Settings): PositionMarkerData {\n const pm = settings.playerMarker;\n const size = settings.roomSize * pm.sizeFactor;\n const useRoomShape = pm.matchRoomShape && settings.roomShape !== \"circle\";\n return {\n shape: useRoomShape ? 'rect' : 'circle',\n cx: room.x,\n cy: room.y,\n size: size / 2,\n cornerRadius: useRoomShape && settings.roomShape === \"roundedRectangle\" ? size * 0.2 : 0,\n strokeColor: pm.strokeColor,\n strokeWidth: pm.strokeWidth,\n strokeAlpha: pm.strokeAlpha,\n fillColor: pm.fillColor,\n fillAlpha: pm.fillAlpha,\n dash: pm.dash,\n dashEnabled: pm.dashEnabled,\n };\n}\n\n// --- Path Overlay ---\n\nexport type PathOverlaySegment = {\n points: number[];\n};\n\nexport type PathOverlayTriangle = {\n vertices: number[];\n};\n\nexport type PathOverlayData = {\n segments: PathOverlaySegment[];\n triangles: PathOverlayTriangle[];\n color: string;\n outlineWidth: number;\n lineWidth: number;\n};\n\nexport function computePathOverlay(\n mapReader: IMapReader,\n settings: Settings,\n locations: number[],\n color: string,\n areaId: number,\n zIndex: number,\n): PathOverlayData {\n const result = computePathData(mapReader, settings, locations, areaId, zIndex);\n const lw = settings.lineWidth;\n\n const segments: PathOverlaySegment[] = [];\n for (const seg of result.segments) {\n if (seg.points.length >= 4) segments.push({points: seg.points});\n }\n for (const cl of result.customLines) {\n if (cl.points.length >= 4) segments.push({points: cl.points});\n }\n\n const triangles: PathOverlayTriangle[] = [];\n for (const marker of result.innerMarkers) {\n for (const tri of computeInnerExitTrianglesForDirection(marker.room, marker.direction, settings)) {\n triangles.push({vertices: tri.vertices});\n }\n }\n\n return {segments, triangles, color, outlineWidth: lw * 8, lineWidth: lw * 4};\n}\n","/**\n * Pure shape builders for the built-in overlays exporters can stamp on top of\n * a scene: highlights, the position marker, and path overlays.\n *\n * Mirrors the geometry the {@link OverlayRenderer} produces against a\n * {@link DrawingBackend} — output as engine-agnostic {@link Shape}s so the\n * exporter pipeline can drive {@link buildDrawCommands} + per-engine\n * renderers without touching a backend.\n */\n\nimport type {\n HighlightData,\n PathOverlayData,\n PositionMarkerData,\n} from \"../OverlayStyle\";\nimport type {Shape} from \"../Shape\";\nimport {hexToRgba} from \"../../utils/color\";\n\n/**\n * Build a highlight ring around a room. Circle rooms get a single dashed\n * stroke; rectangular variants get four independent dashed lines (wrapped in\n * a group) so the corner dashes line up cleanly — each side's dash pattern\n * starts fresh at the corner instead of wrapping continuously around the\n * perimeter (same approach as the backend renderer).\n *\n * When the highlight carries more than one colour the ring is split into that\n * many equal pie wedges via {@link highlightWedgesToShape}.\n */\nexport function highlightToShape(data: HighlightData): Shape {\n if (data.colors.length > 1) {\n return highlightWedgesToShape(data);\n }\n\n const color = data.colors[0];\n const stroke = hexToRgba(color, data.strokeAlpha);\n const fill = data.fillAlpha > 0 ? hexToRgba(color, data.fillAlpha) : undefined;\n\n if (data.shape === \"circle\") {\n return {\n type: \"circle\",\n cx: data.cx, cy: data.cy,\n radius: data.size,\n paint: {\n fill,\n stroke,\n strokeWidth: data.strokeWidth,\n dash: data.dash,\n dashEnabled: data.dashEnabled,\n },\n layer: \"overlay\",\n };\n }\n\n // Rounded highlights can't be drawn as four corner-aligned straight lines —\n // the rounded corners would be lost. Emit a single rounded rect so the\n // stroke (and any fill) follow the corner radius.\n if (data.cornerRadius > 0) {\n return {\n type: \"rect\",\n x: data.cx - data.size,\n y: data.cy - data.size,\n width: data.size * 2,\n height: data.size * 2,\n cornerRadius: data.cornerRadius,\n paint: {\n fill,\n stroke,\n strokeWidth: data.strokeWidth,\n dash: data.dash,\n dashEnabled: data.dashEnabled,\n },\n layer: \"overlay\",\n };\n }\n\n // For rectangular highlights we draw four independent line segments so the\n // corner dashes line up cleanly. If the highlight has a fill, render an\n // underlying filled rect (with no stroke) so the dashed sides still sit on top.\n const x1 = data.cx - data.size;\n const y1 = data.cy - data.size;\n const x2 = data.cx + data.size;\n const y2 = data.cy + data.size;\n const sides: number[][] = [\n [x1, y1, x2, y1],\n [x2, y1, x2, y2],\n [x2, y2, x1, y2],\n [x1, y2, x1, y1],\n ];\n const children: Shape[] = [];\n if (fill) {\n children.push({\n type: \"rect\",\n x: x1, y: y1,\n width: data.size * 2,\n height: data.size * 2,\n cornerRadius: data.cornerRadius,\n paint: { fill },\n layer: \"overlay\",\n });\n }\n for (const points of sides) {\n children.push({\n type: \"line\",\n points,\n paint: {\n stroke,\n strokeWidth: data.strokeWidth,\n dash: data.dash,\n dashEnabled: data.dashEnabled,\n },\n lineCap: \"butt\",\n layer: \"overlay\",\n });\n }\n return {\n type: \"group\",\n x: 0, y: 0,\n children,\n layer: \"overlay\",\n };\n}\n\n/**\n * Split a highlight into N equal pie wedges — one per colour, starting at the\n * top and going clockwise. Each wedge gets an arc of the perimeter stroked in\n * its colour; when the highlight has a fill, each wedge is also a filled pie\n * slice. The perimeter follows the highlight boundary: a circle for circular\n * highlights, the square edges (corners included) for rectangular ones.\n */\nfunction highlightWedgesToShape(data: HighlightData): Shape {\n const n = data.colors.length;\n const step = (Math.PI * 2) / n;\n const base = -Math.PI / 2;\n const children: Shape[] = [];\n\n for (let i = 0; i < n; i++) {\n const color = data.colors[i];\n const stroke = hexToRgba(color, data.strokeAlpha);\n const fill = data.fillAlpha > 0 ? hexToRgba(color, data.fillAlpha) : undefined;\n const a0 = base + i * step;\n const a1 = a0 + step;\n const boundary = highlightBoundaryPath(data, a0, a1);\n\n if (fill) {\n const vertices: number[] = [data.cx, data.cy];\n for (const [x, y] of boundary) vertices.push(x, y);\n children.push({\n type: \"polygon\",\n vertices,\n paint: { fill },\n layer: \"overlay\",\n });\n }\n\n const points: number[] = [];\n for (const [x, y] of boundary) points.push(x, y);\n children.push({\n type: \"line\",\n points,\n paint: {\n stroke,\n strokeWidth: data.strokeWidth,\n dash: data.dash,\n dashEnabled: data.dashEnabled,\n },\n lineCap: \"butt\",\n layer: \"overlay\",\n });\n }\n\n return {\n type: \"group\",\n x: 0, y: 0,\n children,\n layer: \"overlay\",\n };\n}\n\n/**\n * Point on the highlight boundary for a ray cast from the centre at `angle`:\n * the circle edge, the square edge, or — when the highlight has a corner radius\n * — the rounded-rect perimeter (flat edges with quarter-circle corners).\n */\nfunction highlightBoundaryPoint(data: HighlightData, angle: number): [number, number] {\n const c = Math.cos(angle);\n const s = Math.sin(angle);\n if (data.shape === \"circle\") {\n return [data.cx + data.size * c, data.cy + data.size * s];\n }\n\n const half = data.size;\n const m = Math.max(Math.abs(c), Math.abs(s));\n // Where the ray meets the sharp square (relative to the centre).\n const px = (half / m) * c;\n const py = (half / m) * s;\n\n const r = Math.min(data.cornerRadius, half);\n const inset = half - r;\n // Past the straight section on both axes → inside a rounded corner; re-solve\n // the ray against that corner's quarter circle (centre at ±inset, radius r).\n if (r > 0 && Math.abs(px) > inset && Math.abs(py) > inset) {\n const ccx = Math.sign(px) * inset;\n const ccy = Math.sign(py) * inset;\n const dC = ccx * c + ccy * s;\n const cc = ccx * ccx + ccy * ccy;\n const t = dC + Math.sqrt(Math.max(0, dC * dC - (cc - r * r)));\n return [data.cx + t * c, data.cy + t * s];\n }\n return [data.cx + px, data.cy + py];\n}\n\n/**\n * Sample the highlight boundary from angle a0 to a1. Circles and rounded rects\n * are subdivided into short steps so the curves stay smooth; sharp squares only\n * insert the corner angles that fall inside the range so the polyline hugs the\n * corners instead of cutting across them.\n */\nfunction highlightBoundaryPath(data: HighlightData, a0: number, a1: number): Array<[number, number]> {\n const angles: number[] = [a0];\n const rounded = data.shape !== \"circle\" && data.cornerRadius > 0;\n if (data.shape === \"circle\" || rounded) {\n // Finer steps for rounded rects so the small corner arcs read as curves.\n const step = rounded ? Math.PI / 36 : Math.PI / 18;\n const segments = Math.max(1, Math.ceil((a1 - a0) / step));\n for (let k = 1; k < segments; k++) {\n angles.push(a0 + ((a1 - a0) * k) / segments);\n }\n } else {\n // Square corners sit at 45° + k·90°; include those strictly inside (a0, a1).\n const quarter = Math.PI / 2;\n const first = Math.PI / 4;\n const start = Math.ceil((a0 - first) / quarter);\n const end = Math.floor((a1 - first) / quarter);\n for (let k = start; k <= end; k++) {\n const corner = first + k * quarter;\n if (corner > a0 + 1e-9 && corner < a1 - 1e-9) angles.push(corner);\n }\n }\n angles.push(a1);\n angles.sort((x, y) => x - y);\n return angles.map((a) => highlightBoundaryPoint(data, a));\n}\n\n/** Build the player-position marker. */\nexport function positionMarkerToShape(data: PositionMarkerData): Shape {\n const stroke = hexToRgba(data.strokeColor, data.strokeAlpha);\n const fill = data.fillAlpha > 0 ? hexToRgba(data.fillColor, data.fillAlpha) : undefined;\n\n if (data.shape === \"circle\") {\n return {\n type: \"circle\",\n cx: data.cx, cy: data.cy,\n radius: data.size,\n paint: {\n fill,\n stroke,\n strokeWidth: data.strokeWidth,\n dash: data.dash,\n dashEnabled: data.dashEnabled,\n },\n layer: \"overlay\",\n };\n }\n\n return {\n type: \"rect\",\n x: data.cx - data.size,\n y: data.cy - data.size,\n width: data.size * 2,\n height: data.size * 2,\n cornerRadius: data.cornerRadius,\n paint: {\n fill,\n stroke,\n strokeWidth: data.strokeWidth,\n dash: data.dash,\n dashEnabled: data.dashEnabled,\n },\n layer: \"overlay\",\n };\n}\n\n/** Build a path overlay: outline + inner colour line per segment, plus directional triangles. */\nexport function pathToShapes(data: PathOverlayData): Shape[] {\n const shapes: Shape[] = [];\n\n for (const seg of data.segments) {\n shapes.push({\n type: \"line\",\n points: seg.points,\n paint: {\n stroke: \"black\",\n strokeWidth: data.outlineWidth,\n alpha: 0.8,\n },\n lineCap: \"round\",\n lineJoin: \"round\",\n layer: \"overlay\",\n });\n shapes.push({\n type: \"line\",\n points: seg.points,\n paint: {\n stroke: data.color,\n strokeWidth: data.lineWidth,\n alpha: 0.8,\n },\n lineCap: \"round\",\n lineJoin: \"round\",\n layer: \"overlay\",\n });\n }\n\n for (const tri of data.triangles) {\n shapes.push({\n type: \"polygon\",\n vertices: tri.vertices,\n paint: {\n fill: data.color,\n stroke: \"black\",\n strokeWidth: data.outlineWidth / 4,\n },\n layer: \"overlay\",\n });\n }\n\n return shapes;\n}\n","/**\n * HitTester — point→shape lookup built from {@link Shape} hit annotations.\n *\n * Shapes produced by {@link ScenePipeline} carry an optional {@link HitInfo}\n * annotation on shapes that represent pickable model entities (rooms, exits,\n * labels, …). HitTester walks that tree, computes world-space geometry and\n * bounding boxes, and builds a spatial index so picks run in sub-linear time.\n *\n * Coordinates contract:\n * - Shapes live in **world space** (flat map coordinates).\n * - `coordTransform` maps world → rendered space (identity for flat styles;\n * iso projection for IsometricStyle). Each vertex is transformed\n * individually so shears (iso) preserve segment shape.\n * - `layerOffset` adds a per-layer scene-space shift the projection omits\n * (e.g. Isometric lowers the `link` layer by the cube depth so connectors\n * meet the cube base) — applied after `coordTransform` so hit zones land on\n * the drawn geometry, not a cube-height above it.\n * - `pick` / `pickAll` / `pickInRect` / `findRoomAtPoint` expect points in\n * **rendered space** — the same space that `Camera.clientToMapPoint`\n * returns.\n *\n * Distance metric: per shape kind, pick distance is computed against the\n * actual geometry (segment distance for lines/polylines, edge distance for\n * rects/polygons with point-inside short-circuit, radius-distance for\n * circles), so long thin shapes (exits, stubs) are only hit near the line\n * itself — not anywhere within their AABB.\n */\n\nimport type {HitInfo, Shape, Bbox} from \"../scene/Shape\";\n\nexport type CoordTransform = (x: number, y: number) => {x: number; y: number};\n\n/**\n * Extra scene-space offset applied to a layer's geometry, mirroring a\n * render-time per-layer shift the {@link CoordTransform} does not capture\n * (e.g. Isometric lowers the `link` layer by the cube depth).\n */\nexport type LayerOffset = (layer: Shape[\"layer\"]) => {x: number; y: number};\n\n/** Result returned by {@link HitTester.pick} et al. */\nexport interface HitResult {\n kind: string;\n id?: number | string;\n payload?: unknown;\n /** Distance from the query point to the shape (rendered space units). */\n distance: number;\n /** Effective priority used when ranking (per-kind default unless overridden). */\n priority: number;\n /** Center of the shape's rendered bbox — useful for hover effects. */\n centerX: number;\n centerY: number;\n}\n\n/** One entry returned by {@link HitTester.debugEntries} for visualisation. */\nexport interface HitDebugEntry {\n kind: string;\n geoms: ReadonlyArray<HitGeom>;\n /** Max distance from shape edge that still registers as a hit (rendered-space units). */\n marginRadius: number;\n /** Rendered-space bbox of the hit geometry. */\n minX: number; maxX: number; minY: number; maxY: number;\n}\n\n/** Per-kind defaults used when {@link HitInfo.priority} is omitted. */\nconst DEFAULT_PRIORITY: Record<string, number> = {\n areaExit: 110,\n room: 100,\n label: 80,\n specialExit: 60,\n exit: 40,\n stub: 20,\n};\n\n/** Per-kind defaults used when {@link HitInfo.margin} is omitted. */\nconst DEFAULT_MARGIN: Record<string, number> = {\n room: 0.3,\n areaExit: 1.0,\n label: 1.0,\n specialExit: 0.5,\n exit: 0.35,\n stub: 0.3,\n};\n\nexport type HitGeom =\n | {type: \"polyline\"; pts: number[]; closed: boolean}\n | {type: \"circle\"; cx: number; cy: number; r: number};\n\ntype HitEntry = {\n info: HitInfo;\n margin: number;\n priority: number;\n rMinX: number;\n rMaxX: number;\n rMinY: number;\n rMaxY: number;\n cx: number;\n cy: number;\n geoms: HitGeom[];\n};\n\nconst identity: CoordTransform = (x, y) => ({x, y});\n\n/**\n * Spatial index for hittable shapes. Rebuilt cheaply after each scene build.\n */\nexport class HitTester {\n private entries: HitEntry[] = [];\n private bucketSize = 5;\n private roomSize = 1;\n private spatialIndex = new Map<number, HitEntry[]>();\n private transform: CoordTransform = identity;\n private layerOffset: LayerOffset | undefined;\n\n /**\n * Rebuild from a fresh set of world-space shapes.\n *\n * @param shapes Top-level shape list from {@link ScenePipeline}.\n * @param roomSize Current room size (world units) — used as base pick margin.\n * @param coordTransform World→rendered projection; omit for flat (identity).\n * @param layerOffset Per-layer scene-space shift the projection omits\n * (e.g. Isometric link-layer depth); omit for none.\n */\n build(\n shapes: Shape[],\n roomSize: number,\n coordTransform?: CoordTransform,\n layerOffset?: LayerOffset,\n ): void {\n this.clear();\n this.roomSize = roomSize;\n this.bucketSize = Math.max(roomSize * 10, 5);\n this.transform = coordTransform ?? identity;\n this.layerOffset = layerOffset;\n this.collectHitShapes(shapes, 0, 0);\n }\n\n clear(): void {\n this.entries = [];\n this.spatialIndex.clear();\n }\n\n /**\n * Find the best-matching hittable shape at a rendered-space point.\n * Best = highest priority, then closest. Returns `null` when no shape\n * is within its margin of the point.\n */\n pick(x: number, y: number): HitResult | null {\n let best: HitEntry | null = null;\n let bestDist = Infinity;\n let bestCenterDist = Infinity;\n let bestPriority = -Infinity;\n this.forEachCandidate(x, y, (entry, dist) => {\n // Tertiary tiebreaker: when two shapes both contain the point\n // (distance 0), prefer the one whose bbox center is closer.\n const cdx = x - entry.cx;\n const cdy = y - entry.cy;\n const cdist = cdx * cdx + cdy * cdy;\n if (\n entry.priority > bestPriority ||\n (entry.priority === bestPriority && dist < bestDist) ||\n (entry.priority === bestPriority && dist === bestDist && cdist < bestCenterDist)\n ) {\n best = entry;\n bestDist = dist;\n bestCenterDist = cdist;\n bestPriority = entry.priority;\n }\n });\n return best ? this.toResult(best, bestDist) : null;\n }\n\n /**\n * Return every hittable shape at a rendered-space point, sorted by\n * priority desc then distance asc. Used for Alt+click cycling and\n * disambiguation menus.\n */\n pickAll(x: number, y: number): HitResult[] {\n const results: Array<{entry: HitEntry; dist: number}> = [];\n this.forEachCandidate(x, y, (entry, dist) => {\n results.push({entry, dist});\n });\n results.sort((a, b) => b.entry.priority - a.entry.priority || a.dist - b.dist);\n return results.map(r => this.toResult(r.entry, r.dist));\n }\n\n /**\n * Return every hittable shape whose rendered-space center falls inside\n * the rect, optionally filtered by kind. Used for marquee selection.\n */\n pickInRect(\n minX: number,\n minY: number,\n maxX: number,\n maxY: number,\n kinds?: string[],\n ): HitResult[] {\n const lx = Math.min(minX, maxX);\n const rx = Math.max(minX, maxX);\n const ty = Math.min(minY, maxY);\n const by = Math.max(minY, maxY);\n const out: HitResult[] = [];\n for (const entry of this.entries) {\n if (kinds && !kinds.includes(entry.info.kind)) continue;\n if (entry.cx < lx || entry.cx > rx || entry.cy < ty || entry.cy > by) continue;\n out.push(this.toResult(entry, 0));\n }\n out.sort((a, b) => b.priority - a.priority);\n return out;\n }\n\n /**\n * Convenience wrapper: return the room whose hit shape is nearest to\n * the given rendered-space point, or `null` when none is within range.\n */\n findRoomAtPoint(x: number, y: number): MapData.Room | null {\n const result = this.pick(x, y);\n if (!result || result.kind !== \"room\") return null;\n return (result.payload as MapData.Room) ?? null;\n }\n\n /**\n * Returns hit geometry for every entry, for debug visualisation.\n * Coordinates are in **rendered space** (world space for flat styles).\n */\n debugEntries(): HitDebugEntry[] {\n return this.entries.map(e => ({\n kind: e.info.kind,\n geoms: e.geoms,\n marginRadius: e.margin * this.roomSize,\n minX: e.rMinX,\n maxX: e.rMaxX,\n minY: e.rMinY,\n maxY: e.rMaxY,\n }));\n }\n\n // ── Private ───────────────────────────────────────────────────────────────\n\n /** Walk the 9 buckets around the point and emit candidates within margin. */\n private forEachCandidate(\n x: number,\n y: number,\n emit: (entry: HitEntry, dist: number) => void,\n ): void {\n const bx = Math.floor(x / this.bucketSize);\n const by = Math.floor(y / this.bucketSize);\n const seen = new Set<HitEntry>();\n for (let dx = -1; dx <= 1; dx++) {\n for (let dy = -1; dy <= 1; dy++) {\n const bucket = this.spatialIndex.get(bucketKey(bx + dx, by + dy));\n if (!bucket) continue;\n for (const entry of bucket) {\n if (seen.has(entry)) continue;\n seen.add(entry);\n const r = entry.margin * this.roomSize;\n if (\n x < entry.rMinX - r || x > entry.rMaxX + r ||\n y < entry.rMinY - r || y > entry.rMaxY + r\n ) continue;\n const dist = entryDistance(entry, x, y);\n if (dist > r) continue;\n emit(entry, dist);\n }\n }\n }\n }\n\n private toResult(entry: HitEntry, distance: number): HitResult {\n return {\n kind: entry.info.kind,\n id: entry.info.id,\n payload: entry.info.payload,\n distance,\n priority: entry.priority,\n centerX: entry.cx,\n centerY: entry.cy,\n };\n }\n\n private collectHitShapes(shapes: Shape[], offsetX: number, offsetY: number): void {\n for (const shape of shapes) {\n if (shape.hit) {\n const geoms: HitGeom[] = [];\n const bbox = makeEmptyBbox();\n collectGeometryForEntry(shape, offsetX, offsetY, this.entryTransform(shape.layer), geoms, bbox);\n if (geoms.length > 0 && bbox.minX <= bbox.maxX) {\n this.indexEntry(bbox, geoms, shape.hit);\n }\n }\n if (shape.type === \"group\") {\n this.collectHitShapes(shape.children, offsetX + shape.x, offsetY + shape.y);\n }\n }\n }\n\n /**\n * Transform for one entry: the base projection plus any per-layer scene\n * offset the projection omits (e.g. Isometric link-layer depth), so hit\n * geometry lands where the shape is actually drawn.\n */\n private entryTransform(layer: Shape[\"layer\"]): CoordTransform {\n const off = this.layerOffset?.(layer);\n if (!off || (off.x === 0 && off.y === 0)) return this.transform;\n const base = this.transform;\n return (x, y) => {\n const p = base(x, y);\n return {x: p.x + off.x, y: p.y + off.y};\n };\n }\n\n private indexEntry(bbox: Bbox, geoms: HitGeom[], info: HitInfo): void {\n const margin = info.margin ?? DEFAULT_MARGIN[info.kind] ?? 1.0;\n const priority = info.priority ?? DEFAULT_PRIORITY[info.kind] ?? 50;\n\n const cx = (bbox.minX + bbox.maxX) / 2;\n const cy = (bbox.minY + bbox.maxY) / 2;\n const entry: HitEntry = {\n info,\n margin,\n priority,\n rMinX: bbox.minX,\n rMaxX: bbox.maxX,\n rMinY: bbox.minY,\n rMaxY: bbox.maxY,\n cx,\n cy,\n geoms,\n };\n this.entries.push(entry);\n\n const size = this.bucketSize;\n const bMinX = Math.floor(bbox.minX / size);\n const bMaxX = Math.floor(bbox.maxX / size);\n const bMinY = Math.floor(bbox.minY / size);\n const bMaxY = Math.floor(bbox.maxY / size);\n for (let bx = bMinX; bx <= bMaxX; bx++) {\n for (let by = bMinY; by <= bMaxY; by++) {\n const key = bucketKey(bx, by);\n let bucket = this.spatialIndex.get(key);\n if (!bucket) {\n bucket = [];\n this.spatialIndex.set(key, bucket);\n }\n bucket.push(entry);\n }\n }\n }\n}\n\n// ── Geometry collection ──────────────────────────────────────────────────────\n\nfunction makeEmptyBbox(): Bbox {\n return {minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity};\n}\n\nfunction expandBbox(bbox: Bbox, x: number, y: number): void {\n if (x < bbox.minX) bbox.minX = x;\n if (x > bbox.maxX) bbox.maxX = x;\n if (y < bbox.minY) bbox.minY = y;\n if (y > bbox.maxY) bbox.maxY = y;\n}\n\n/**\n * Entry point for geometry collection: the hit-annotated `shape` itself is\n * always included, regardless of whether it would otherwise be skipped as\n * a hit-annotated descendant.\n */\nfunction collectGeometryForEntry(\n shape: Shape,\n offsetX: number,\n offsetY: number,\n transform: CoordTransform,\n out: HitGeom[],\n bbox: Bbox,\n): void {\n if (shape.type === \"group\") {\n // Top-level: walk children directly, skipping any with their own hit.\n for (const child of shape.children) {\n if (child.hit) continue;\n collectGeometry(child, offsetX + shape.x, offsetY + shape.y, transform, out, bbox);\n }\n return;\n }\n collectGeometry(shape, offsetX, offsetY, transform, out, bbox);\n}\n\n/**\n * Recursive geometry walk for a non-hit-annotated subtree. Hit-annotated\n * descendants are skipped — they get their own entries via `collectHitShapes`.\n */\nfunction collectGeometry(\n shape: Shape,\n offsetX: number,\n offsetY: number,\n transform: CoordTransform,\n out: HitGeom[],\n bbox: Bbox,\n): void {\n switch (shape.type) {\n case \"rect\":\n case \"image\": {\n const c1 = transform(offsetX + shape.x, offsetY + shape.y);\n const c2 = transform(offsetX + shape.x + shape.width, offsetY + shape.y);\n const c3 = transform(offsetX + shape.x + shape.width, offsetY + shape.y + shape.height);\n const c4 = transform(offsetX + shape.x, offsetY + shape.y + shape.height);\n const verts = [c1.x, c1.y, c2.x, c2.y, c3.x, c3.y, c4.x, c4.y];\n out.push({type: \"polyline\", pts: verts, closed: true});\n for (let i = 0; i < verts.length; i += 2) expandBbox(bbox, verts[i], verts[i + 1]);\n return;\n }\n case \"text\": {\n const w = shape.width ?? 0;\n const h = shape.height ?? 0;\n if (w === 0 || h === 0) {\n const c = transform(offsetX + shape.x, offsetY + shape.y);\n expandBbox(bbox, c.x, c.y);\n return;\n }\n const c1 = transform(offsetX + shape.x, offsetY + shape.y);\n const c2 = transform(offsetX + shape.x + w, offsetY + shape.y);\n const c3 = transform(offsetX + shape.x + w, offsetY + shape.y + h);\n const c4 = transform(offsetX + shape.x, offsetY + shape.y + h);\n const verts = [c1.x, c1.y, c2.x, c2.y, c3.x, c3.y, c4.x, c4.y];\n out.push({type: \"polyline\", pts: verts, closed: true});\n for (let i = 0; i < verts.length; i += 2) expandBbox(bbox, verts[i], verts[i + 1]);\n return;\n }\n case \"circle\": {\n const c = transform(offsetX + shape.cx, offsetY + shape.cy);\n out.push({type: \"circle\", cx: c.x, cy: c.y, r: shape.radius});\n expandBbox(bbox, c.x - shape.radius, c.y - shape.radius);\n expandBbox(bbox, c.x + shape.radius, c.y + shape.radius);\n return;\n }\n case \"line\": {\n const pts: number[] = [];\n for (let i = 0; i < shape.points.length; i += 2) {\n const p = transform(offsetX + shape.points[i], offsetY + shape.points[i + 1]);\n pts.push(p.x, p.y);\n expandBbox(bbox, p.x, p.y);\n }\n if (pts.length >= 2) out.push({type: \"polyline\", pts, closed: false});\n return;\n }\n case \"polygon\": {\n const verts: number[] = [];\n for (let i = 0; i < shape.vertices.length; i += 2) {\n const p = transform(offsetX + shape.vertices[i], offsetY + shape.vertices[i + 1]);\n verts.push(p.x, p.y);\n expandBbox(bbox, p.x, p.y);\n }\n if (verts.length >= 2) out.push({type: \"polyline\", pts: verts, closed: true});\n return;\n }\n case \"group\": {\n for (const child of shape.children) {\n if (child.hit) continue;\n collectGeometry(child, offsetX + shape.x, offsetY + shape.y, transform, out, bbox);\n }\n return;\n }\n }\n}\n\n// ── Distance helpers ─────────────────────────────────────────────────────────\n\nfunction entryDistance(entry: HitEntry, x: number, y: number): number {\n let best = Infinity;\n for (const g of entry.geoms) {\n const d = geomDistance(g, x, y);\n if (d < best) best = d;\n if (best === 0) return 0;\n }\n return best;\n}\n\nfunction geomDistance(g: HitGeom, x: number, y: number): number {\n if (g.type === \"circle\") {\n const dx = x - g.cx, dy = y - g.cy;\n return Math.max(0, Math.hypot(dx, dy) - g.r);\n }\n if (g.closed && pointInPolygon(g.pts, x, y)) return 0;\n return minDistanceToPolyline(g.pts, x, y, g.closed);\n}\n\nfunction minDistanceToPolyline(pts: number[], px: number, py: number, closed: boolean): number {\n const n = pts.length / 2;\n if (n < 2) {\n if (n === 1) return Math.hypot(px - pts[0], py - pts[1]);\n return Infinity;\n }\n let best = Infinity;\n const segs = closed ? n : n - 1;\n for (let i = 0; i < segs; i++) {\n const ai = i * 2;\n const bi = ((i + 1) % n) * 2;\n const d = pointToSegment(px, py, pts[ai], pts[ai + 1], pts[bi], pts[bi + 1]);\n if (d < best) best = d;\n }\n return best;\n}\n\nfunction pointToSegment(\n px: number, py: number,\n ax: number, ay: number,\n bx: number, by: number,\n): number {\n const dx = bx - ax, dy = by - ay;\n const lenSq = dx * dx + dy * dy;\n let t = lenSq === 0 ? 0 : ((px - ax) * dx + (py - ay) * dy) / lenSq;\n if (t < 0) t = 0;\n else if (t > 1) t = 1;\n const cx = ax + t * dx;\n const cy = ay + t * dy;\n return Math.hypot(px - cx, py - cy);\n}\n\nfunction pointInPolygon(verts: number[], x: number, y: number): boolean {\n let inside = false;\n const n = verts.length / 2;\n for (let i = 0, j = n - 1; i < n; j = i++) {\n const xi = verts[i * 2], yi = verts[i * 2 + 1];\n const xj = verts[j * 2], yj = verts[j * 2 + 1];\n // Avoid divide-by-zero on horizontal edges; tiny epsilon is harmless.\n const denom = (yj - yi) || 1e-12;\n const intersect = ((yi > y) !== (yj > y)) &&\n (x < ((xj - xi) * (y - yi)) / denom + xi);\n if (intersect) inside = !inside;\n }\n return inside;\n}\n\nfunction bucketKey(bx: number, by: number): number {\n return bx * 1000003 + by;\n}\n\n// ── Bbox computation (preserved for callers / tests) ─────────────────────────\n\n/**\n * Compute the axis-aligned bounding box of a shape in world space.\n * `offsetX/Y` is the cumulative parent group origin.\n *\n * Note: this does NOT apply the HitTester's coord transform. Callers that\n * need rendered-space extents should transform the four corners themselves.\n * Kept exported for backends that need a quick world-space AABB (e.g. cull\n * entry construction, scene bounds), and for tests.\n */\nexport function computeShapeBbox(shape: Shape, offsetX: number, offsetY: number): Bbox {\n switch (shape.type) {\n case \"rect\":\n return {\n minX: offsetX + shape.x,\n minY: offsetY + shape.y,\n maxX: offsetX + shape.x + shape.width,\n maxY: offsetY + shape.y + shape.height,\n };\n case \"circle\":\n return {\n minX: offsetX + shape.cx - shape.radius,\n minY: offsetY + shape.cy - shape.radius,\n maxX: offsetX + shape.cx + shape.radius,\n maxY: offsetY + shape.cy + shape.radius,\n };\n case \"line\": {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (let i = 0; i < shape.points.length; i += 2) {\n const x = offsetX + shape.points[i];\n const y = offsetY + shape.points[i + 1];\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n return {minX, minY, maxX, maxY};\n }\n case \"polygon\": {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (let i = 0; i < shape.vertices.length; i += 2) {\n const x = offsetX + shape.vertices[i];\n const y = offsetY + shape.vertices[i + 1];\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n return {minX, minY, maxX, maxY};\n }\n case \"text\":\n return {\n minX: offsetX + shape.x,\n minY: offsetY + shape.y,\n maxX: offsetX + shape.x + (shape.width ?? 0),\n maxY: offsetY + shape.y + (shape.height ?? 0),\n };\n case \"image\":\n return {\n minX: offsetX + shape.x,\n minY: offsetY + shape.y,\n maxX: offsetX + shape.x + shape.width,\n maxY: offsetY + shape.y + shape.height,\n };\n case \"group\": {\n if (shape.children.length === 0) {\n const px = offsetX + shape.x;\n const py = offsetY + shape.y;\n return {minX: px, minY: py, maxX: px, maxY: py};\n }\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const child of shape.children) {\n const b = computeShapeBbox(child, offsetX + shape.x, offsetY + shape.y);\n if (b.minX < minX) minX = b.minX;\n if (b.minY < minY) minY = b.minY;\n if (b.maxX > maxX) maxX = b.maxX;\n if (b.maxY > maxY) maxY = b.maxY;\n }\n return {minX, minY, maxX, maxY};\n }\n }\n}\n","/**\n * Style — engine-agnostic geometry transformer.\n *\n * A {@link Style} takes a {@link Shape} from the scene pipeline and returns\n * zero or more transformed Shapes. Transforms run **before** the shapes hit\n * culling, hit-testing, and the {@link buildDrawCommands}, so styles never\n * need a backend handle and never know about Konva or SVG.\n *\n * Most styles return one shape (recoloured / re-projected). Some split:\n * - Sketchy may emit multiple wobbled segments for a long line.\n * - Neon emits a wide-translucent glow shape plus the main shape.\n * - Isometric projects coordinates and may emit cube side-face shapes.\n *\n * Compose styles with {@link compose}; the leftmost style runs first.\n */\n\nimport type {Shape} from \"../scene/Shape\";\n\n/** Per-frame context passed to style transforms. */\nexport interface StyleContext {\n /** Camera scale (BASE_SCALE * zoom). */\n scale: number;\n /** Active room size in world units (for jitter / depth tuning). */\n roomSize: number;\n}\n\nexport interface Style {\n /**\n * Transform one shape into one or more shapes. Implementations should be\n * pure: same input + context → same output. Group children are walked by\n * the caller (the pipeline / a wrapper); transforms see leaf shapes and\n * group shapes as-is.\n */\n transform(shape: Shape, ctx: StyleContext): Shape | Shape[];\n\n /**\n * Optional world-space → scene-space coordinate map for styles that warp\n * coordinates (e.g. Isometric). When set, {@link HitTester} and\n * {@link Camera} use the inverse to translate clicks back to map space.\n */\n worldToScene?(x: number, y: number): {x: number; y: number};\n\n /** Inverse of {@link worldToScene}. */\n sceneToWorld?(x: number, y: number): {x: number; y: number};\n\n /**\n * Optional extra scene-space offset that {@link transform} applies to a\n * given layer's groups but {@link worldToScene} does not capture. Isometric\n * lowers the `link` layer by the cube depth so connectors meet the cube\n * base; without mirroring that here, {@link HitTester} would place exit hit\n * zones a cube-height above the drawn connectors. Returns `{x:0, y:0}` for\n * layers it does not shift.\n */\n sceneLayerOffset?(layer: Shape[\"layer\"]): {x: number; y: number};\n}\n\n/** Identity style — passes shapes through unchanged. */\nexport const identityStyle: Style = {\n transform: (shape) => shape,\n};\n\n/**\n * Compose a chain of styles into a single style. Shapes flow **left → right**:\n * the leftmost style transforms first, the rightmost transforms last and gets\n * the final say on the emitted shapes.\n *\n * Order matters in two ways:\n *\n * 1. **Shape-changing styles must come before styles that depend on the new\n * geometry.** {@link sketchyShapeStyle} converts rect/circle into polygon,\n * and {@link isometricShapeStyle} only extrudes rect/circle into cubes — so\n * Iso must run before Sketchy or the cubes never form.\n * 2. **Paint-rewriting styles overwrite earlier paint choices.** Put the style\n * whose colour palette you want on screen *last*: in\n * `compose(Sketchy(...), Parchment)` Parchment recolours Sketchy's pencil\n * fills/strokes into the parchment palette; reverse the order and Sketchy\n * repaints over Parchment's choices.\n *\n * Example — isometric parchment cubes with hand-drawn wobble:\n * ```ts\n * compose(\n * Isometric({depth, rotation}), // rects → cube-face polygons\n * Sketchy({jitter, color: '#4a3728'}), // wobble each polygon\n * Parchment, // recolour to parchment palette\n * );\n * ```\n */\nexport function compose(...styles: Style[]): Style {\n if (styles.length === 0) return identityStyle;\n if (styles.length === 1) return styles[0];\n\n return {\n transform(shape, ctx) {\n let acc: Shape[] = [shape];\n for (const style of styles) {\n const next: Shape[] = [];\n for (const s of acc) {\n const out = style.transform(s, ctx);\n if (Array.isArray(out)) next.push(...out);\n else next.push(out);\n }\n acc = next;\n }\n return acc;\n },\n\n worldToScene(x, y) {\n let p = {x, y};\n for (const style of styles) {\n if (style.worldToScene) p = style.worldToScene(p.x, p.y);\n }\n return p;\n },\n\n sceneToWorld(x, y) {\n let p = {x, y};\n for (let i = styles.length - 1; i >= 0; i--) {\n const style = styles[i];\n if (style.sceneToWorld) p = style.sceneToWorld(p.x, p.y);\n }\n return p;\n },\n\n sceneLayerOffset(layer) {\n let x = 0, y = 0;\n for (const style of styles) {\n if (!style.sceneLayerOffset) continue;\n const o = style.sceneLayerOffset(layer);\n x += o.x;\n y += o.y;\n }\n return {x, y};\n },\n };\n}\n","/**\n * Recursively apply a {@link Style} to a list of {@link Shape}s, walking into\n * group children so paint / coordinate rewrites reach every leaf.\n *\n * Each leaf is passed through `style.transform`; groups are transformed too\n * (after their children have been mapped) so styles like Isometric can rewrite\n * the group itself when needed. Result is flattened into a new shape list.\n */\n\nimport type {Shape} from \"../scene/Shape\";\nimport type {Style, StyleContext} from \"./Style\";\n\nexport function applyStyleToShapes(\n shapes: Shape[],\n style: Style,\n ctx: StyleContext,\n): Shape[] {\n const out: Shape[] = [];\n for (const shape of shapes) {\n const transformed = applyStyle(shape, style, ctx);\n if (Array.isArray(transformed)) out.push(...transformed);\n else out.push(transformed);\n }\n return out;\n}\n\nfunction applyStyle(shape: Shape, style: Style, ctx: StyleContext): Shape | Shape[] {\n if (shape.type === \"group\") {\n const children: Shape[] = [];\n for (const child of shape.children) {\n const out = applyStyle(child, style, ctx);\n if (Array.isArray(out)) children.push(...out);\n else children.push(out);\n }\n // Transform the (recursively styled) group so styles that need to\n // rewrite the container itself (e.g. Isometric coordinate warping)\n // see the post-children form.\n return style.transform({...shape, children}, ctx);\n }\n return style.transform(shape, ctx);\n}\n","import type {SceneShapesByLayer} from \"../ScenePipeline\";\nimport type {Shape} from \"../scene/Shape\";\nimport type {MapState} from \"../MapState\";\nimport type {SceneOverlay} from \"../overlay/SceneOverlay\";\nimport type {ViewportBounds} from \"../types/Settings\";\nimport type {SvgOverlays} from \"../SvgTypes\";\nimport {computeHighlight, computePathOverlay, computePositionMarker} from \"../scene/OverlayStyle\";\nimport {\n highlightToShape,\n pathToShapes,\n positionMarkerToShape,\n} from \"../scene/elements/OverlayLayout\";\n\nexport interface SceneFlushContext {\n state: MapState;\n viewportBounds: ViewportBounds;\n sceneOverlays: Iterable<SceneOverlay>;\n overlays?: SvgOverlays;\n}\n\n/**\n * Walk the canonical scene-layer order, calling `flush` once per layer.\n * Order: grid → link → room → built-in overlays → scene overlays → topLabel.\n *\n * Single source of truth for what static exporters must render — adding a new\n * exporter no longer means re-typing the sequence and risking a silent omission.\n */\nexport function flushSceneShapes(\n sceneShapes: SceneShapesByLayer,\n context: SceneFlushContext,\n flush: (shapes: Shape[], sceneSpace?: boolean) => void,\n): void {\n flush(sceneShapes.grid);\n flush(sceneShapes.link);\n flush(sceneShapes.room);\n flush(buildBuiltInOverlayShapes(context.state, context.overlays));\n for (const overlay of context.sceneOverlays) {\n const out = overlay.render(context.state, context.viewportBounds);\n if (!out) continue;\n // Scene-space overlays return geometry already in rendered space;\n // signal the exporter to skip the Style transform so warping styles\n // (Isometric) don't project it twice.\n flush(Array.isArray(out) ? out : [out], overlay.sceneSpace);\n }\n flush(sceneShapes.topLabel);\n}\n\nexport function buildBuiltInOverlayShapes(\n state: MapState,\n overlaysInput?: SvgOverlays,\n): Shape[] {\n const overlays = state.getOverlaysForArea(overlaysInput);\n if (!overlays) return [];\n const settings = state.settings;\n const out: Shape[] = [];\n\n if (overlays.paths) {\n for (const path of overlays.paths) {\n const data = computePathOverlay(\n state.mapReader, settings, path.locations, path.color,\n state.currentArea!, state.currentZIndex!,\n );\n out.push(...pathToShapes(data));\n }\n }\n if (overlays.highlights) {\n for (const hl of overlays.highlights) {\n const room = state.mapReader.getRoom(hl.roomId);\n if (!room) continue;\n out.push(highlightToShape(computeHighlight(room, hl.color, settings)));\n }\n }\n if (overlays.position) {\n const room = state.mapReader.getRoom(overlays.position.roomId);\n if (room) {\n out.push(positionMarkerToShape(computePositionMarker(room, settings)));\n }\n }\n return out;\n}\n"],"mappings":"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"}