mudlet-map-renderer 1.2.2 → 2.0.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.
- package/dist/ScenePipeline.d.ts +1 -1
- package/dist/camera/Camera.d.ts +1 -1
- package/dist/draw/DrawCommand.d.ts +4 -4
- package/dist/index.d.ts +4 -3
- package/dist/index.mjs +836 -676
- package/dist/index.mjs.map +1 -1
- package/dist/render/RecordingLayer.d.ts +4 -3
- package/dist/render/canvasGradient.d.ts +2 -0
- package/dist/rendering/KonvaRenderBackend.d.ts +8 -6
- package/dist/scene/Shape.d.ts +49 -2
- package/dist/style/index.d.ts +8 -1
- package/dist/style/shape/GradientRoomsStyle.d.ts +23 -0
- package/dist/style/shape/index.d.ts +2 -0
- package/dist/style/shape/paintMap.d.ts +6 -6
- package/package.json +1 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/types/Settings.ts","../src/utils/color.ts","../src/TypedEventEmitter.ts","../src/lens/RoomLens.ts","../src/MapState.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/ScenePipeline.ts","../src/scene/elements/GridLayout.ts","../src/export/clipSceneToViewport.ts","../src/rendering/SceneManager.ts","../src/InteractionHandler.ts","../src/render/RecordingLayer.ts","../src/render/shapeToRecording.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/rendering/KonvaRenderBackend.ts","../src/style/shape/paintMap.ts","../src/style/shape/ParchmentStyle.ts","../src/style/shape/BlueprintStyle.ts","../src/style/shape/NeonStyle.ts","../src/style/shape/wobble.ts","../src/style/shape/SketchyStyle.ts","../src/style/shape/IsometricStyle.ts","../src/style/shape/ConstructionStyle.ts","../src/style/shape/SciFiStyle.ts","../src/style/index.ts","../src/rendering/MapRenderer.ts","../src/draw/DrawCommandBuilder.ts","../src/render/SvgRenderer.ts","../src/render/CanvasRenderer.ts","../src/export/flushSceneShapes.ts","../src/export/sceneBounds.ts","../src/export/SvgExporter.ts","../src/export/PngExporter.ts","../src/export/canvasToBytes.ts","../src/export/CanvasExporter.ts","../src/scene/AmbientLightStyle.ts","../src/overlay/AmbientLightOverlay.ts","../node_modules/node-dijkstra/libs/PriorityQueue.js","../node_modules/node-dijkstra/libs/removeDeepFromMap.js","../node_modules/node-dijkstra/libs/toDeepMap.js","../node_modules/node-dijkstra/libs/validateDeep.js","../node_modules/node-dijkstra/libs/Graph.js","../src/MapGraph.ts","../src/PathFinder.ts","../src/lens/composeLenses.ts","../src/lens/ExplorationLens.ts","../src/AreaMapRenderer.ts"],"sourcesContent":["const defaultRoomSize = 0.6;\nconst defaultLineWidth = 0.025;\nconst lineColor = 'rgb(225, 255, 225)';\n\nexport type LabelRenderMode = \"image\" | \"data\" | \"none\";\n\nexport type CullingMode = \"none\" | \"basic\" | \"indexed\";\n\nexport type RoomShape = \"rectangle\" | \"circle\" | \"roundedRectangle\";\n\nexport type RoomContextMenuEventDetail = {\n roomId: number;\n position: { x: number; y: number };\n};\n\nexport type RoomClickEventDetail = {\n roomId: number;\n position: { x: number; y: number };\n};\n\nexport type ZoomChangeEventDetail = {\n zoom: number;\n};\n\nexport type AreaExitClickEventDetail = {\n targetRoomId: number;\n position: { x: number; y: number };\n};\n\nexport type ViewportBounds = {\n minX: number;\n maxX: number;\n minY: number;\n maxY: number;\n};\n\nexport type PanEventDetail = ViewportBounds;\n\nexport type RendererEventMap = {\n roomclick: RoomClickEventDetail;\n roomcontextmenu: RoomContextMenuEventDetail;\n areaexitclick: AreaExitClickEventDetail;\n mapclick: undefined;\n pan: PanEventDetail;\n zoom: ZoomChangeEventDetail;\n};\n\n/**\n * Style configuration for the player position marker.\n * The player marker is a circle that indicates the current player position on the map.\n */\nexport type PlayerMarkerStyle = {\n /**\n * Hex color for the marker's stroke/border (e.g., \"#00e5b2\" for cyan-green).\n */\n strokeColor: string;\n\n /**\n * Opacity for the stroke/border (0.0 = fully transparent, 1.0 = fully opaque).\n */\n strokeAlpha: number;\n\n /**\n * Hex color for the marker's fill (e.g., \"#00e5b2\" for cyan-green).\n */\n fillColor: string;\n\n /**\n * Opacity for the fill (0.0 = fully transparent, 1.0 = fully opaque).\n * Setting this to 0 creates a hollow circle effect.\n */\n fillAlpha: number;\n\n /**\n * Width of the marker's stroke/border in map units (typically 0.01-0.3).\n */\n strokeWidth: number;\n\n /**\n * Size multiplier relative to the room size.\n * - 1.0 = marker radius equals room radius (matches room size)\n * - Values > 1.0 make the marker larger than rooms\n * - Values < 1.0 make the marker smaller than rooms\n *\n * Note: Room circles have radius = roomSize / 2, so sizeFactor is applied to that radius.\n */\n sizeFactor: number;\n\n /**\n * Dash pattern for the stroke as an array of [dash length, gap length].\n * Example: [0.05, 0.05] creates evenly spaced dashes.\n * Only applied when dashEnabled is true.\n */\n dash?: number[];\n\n /**\n * Whether to apply the dash pattern to the stroke.\n * When false, the stroke is solid regardless of the dash property.\n */\n dashEnabled: boolean;\n\n /**\n * When true, the marker shape matches the current roomShape setting\n * (rectangle, circle, or roundedRectangle) instead of always being a circle.\n */\n matchRoomShape: boolean;\n};\n\n/**\n * Style configuration for room highlights.\n * Highlights are rings drawn around rooms registered via {@link MapRenderer.renderHighlight}.\n * The highlight's color is supplied per-call; this style controls everything else.\n */\nexport type HighlightStyle = {\n /**\n * Opacity for the highlight's stroke/ring (0.0 = fully transparent, 1.0 = fully opaque).\n */\n strokeAlpha: number;\n\n /**\n * Opacity for the fill (0.0 = fully transparent / hollow, 1.0 = fully opaque).\n * The fill uses the per-highlight color. Defaults to 0 to preserve the hollow ring look.\n */\n fillAlpha: number;\n\n /**\n * Width of the highlight stroke in map units (typically 0.01-0.3).\n */\n strokeWidth: number;\n\n /**\n * Size multiplier relative to the room size.\n * - 1.0 = highlight matches room size\n * - Values > 1.0 produce a ring outside the room\n * - Values < 1.0 produce a smaller marker inside the room\n */\n sizeFactor: number;\n\n /**\n * Dash pattern for the stroke as an array of [dash length, gap length].\n * Only applied when dashEnabled is true.\n */\n dash?: number[];\n\n /**\n * Whether to apply the dash pattern to the stroke.\n * When false, the stroke is solid regardless of the dash property.\n */\n dashEnabled: boolean;\n\n /**\n * When true, the highlight shape matches the current roomShape setting\n * (rectangle, circle, or roundedRectangle). When false, the highlight is always a circle.\n */\n matchRoomShape: boolean;\n};\n\n\n/**\n * Settings for map rendering.\n * All properties can be modified at runtime to change the map's appearance and behavior.\n * Create with {@link createSettings} and pass to the renderer constructor.\n */\nexport type Settings = {\n /** Size of each room in map units (width/height for rectangles, diameter for circles). Default: 0.6 */\n roomSize: number;\n /** Width of lines (exit connections, room borders) in map units. Default: 0.025 */\n lineWidth: number;\n /** Color of exit connection lines as RGB string. Default: 'rgb(225, 255, 225)' */\n lineColor: string;\n /** Background color of the map container. Default: '#000000' */\n backgroundColor: string;\n /** When true, map instantly jumps to new position on room change. Default: false */\n instantMapMove: boolean;\n /** When true, highlights the current room and its exits with an overlay. Default: true */\n highlightCurrentRoom: boolean;\n /** Legacy flag for enabling/disabling culling (prefer cullingMode). Default: true */\n cullingEnabled: boolean;\n /** How off-screen elements are culled: \"none\" | \"basic\" | \"indexed\". Default: \"indexed\" */\n cullingMode: CullingMode;\n /** Custom culling bounds in map coordinates, or null for viewport bounds. Default: null */\n cullingBounds: { x: number; y: number; width: number; height: number } | null;\n /** How to render room labels: \"image\" | \"data\". Default: \"image\" */\n labelRenderMode: LabelRenderMode;\n /** When true, room labels have transparent backgrounds. Default: false */\n transparentLabels: boolean;\n /** Shape used to render rooms: \"rectangle\" | \"circle\" | \"roundedRectangle\". Default: \"rectangle\" */\n roomShape: RoomShape;\n /** Style configuration for the player position marker. */\n playerMarker: PlayerMarkerStyle;\n /** Style configuration for room highlights (added via {@link MapRenderer.renderHighlight}). */\n highlight: HighlightStyle;\n /** Whether to render a background grid. Default: false */\n gridEnabled: boolean;\n /** Grid line spacing in map units. Default: 1 */\n gridSize: number;\n /** Color of grid lines as CSS color string. Default: 'rgba(255, 255, 255, 0.07)' */\n gridColor: string;\n /** Width of grid lines in map units. Default: 0.02 */\n gridLineWidth: number;\n /** Whether to draw borders (strokes) on rooms. Default: true */\n borders: boolean;\n /** When true, rooms use frame rendering: fill=backgroundColor, stroke=envColor. Default: false */\n frameMode: boolean;\n /** When true, rooms use colored rendering: fill=envColor darkened 30%, stroke=envColor. Default: false */\n coloredMode: boolean;\n /** When true, rooms display a 3D emboss effect (rectangle/roundedRectangle only). Default: false */\n emboss: boolean;\n /** When true, displays the area name as a header text on the map. Default: false */\n areaName: boolean;\n /** Font family for the area name header. Default: 'sans-serif' */\n fontFamily: string;\n /** When true, uses bounds from all z-levels for viewport sizing, not just the current level. Default: false */\n uniformLevelSize: boolean;\n /** When true, renders a small text label next to area-exit arrows showing the target area name.\n * Exits leading to the same target area are grouped — one label per area at the cluster centroid. Default: false */\n areaExitLabels: boolean;\n /** Font size (in map units) for area-exit labels. Padding, corner radius, and stroke\n * scale proportionally. Default: 0.3 */\n areaExitLabelFontSize: number;\n};\n\n/** Creates a new Settings object with default values. */\nexport function createSettings(): Settings {\n return {\n roomSize: defaultRoomSize,\n lineWidth: defaultLineWidth,\n lineColor: lineColor,\n backgroundColor: '#000000',\n instantMapMove: false,\n highlightCurrentRoom: true,\n cullingEnabled: true,\n cullingMode: \"indexed\",\n cullingBounds: null,\n labelRenderMode: \"image\",\n transparentLabels: false,\n roomShape: \"rectangle\",\n playerMarker: {\n strokeColor: \"#00e5b2\",\n strokeAlpha: 1.0,\n fillColor: \"#00e5b2\",\n fillAlpha: 0.0,\n strokeWidth: 0.1,\n sizeFactor: 1.7,\n dash: [0.05, 0.05],\n dashEnabled: true,\n matchRoomShape: false,\n },\n highlight: {\n strokeAlpha: 1.0,\n fillAlpha: 0.0,\n strokeWidth: 0.1,\n sizeFactor: 1.425,\n dash: [0.05, 0.05],\n dashEnabled: true,\n matchRoomShape: true,\n },\n gridEnabled: false,\n gridSize: 1,\n gridColor: 'rgba(200, 200, 200, 0.15)',\n gridLineWidth: 0.03,\n borders: true,\n frameMode: false,\n coloredMode: false,\n emboss: false,\n areaName: true,\n fontFamily: 'sans-serif',\n uniformLevelSize: false,\n areaExitLabels: false,\n areaExitLabelFontSize: 0.3,\n };\n}\n","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\nexport function darkenColor(color: string, factor: number): string {\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]);\n g = parseInt(rgbMatch[2]);\n b = parseInt(rgbMatch[3]);\n } else if (color.startsWith('#') && color.length >= 7) {\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 } else {\n return color;\n }\n r = Math.round(r * (1 - factor));\n g = Math.round(g * (1 - factor));\n b = Math.round(b * (1 - factor));\n return `rgb(${r}, ${g}, ${b})`;\n}\n\nexport function lightenColor(color: string, factor: number): string {\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]);\n g = parseInt(rgbMatch[2]);\n b = parseInt(rgbMatch[3]);\n } else if (color.startsWith('#') && color.length >= 7) {\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 } else {\n return color;\n }\n r = Math.min(255, Math.round(r + (255 - r) * factor));\n g = Math.min(255, Math.round(g + (255 - g) * factor));\n b = Math.min(255, Math.round(b + (255 - b) * factor));\n return `rgb(${r}, ${g}, ${b})`;\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 * 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 {IMapReader} from \"./reader/MapReader\";\nimport type {IArea} from \"./reader/Area\";\nimport type {IPlane} from \"./reader/Plane\";\nimport type {Settings} from \"./types/Settings\";\nimport type {SvgOverlays} from \"./SvgTypes\";\nimport {TypedEventEmitter} from \"./TypedEventEmitter\";\nimport type {RoomLens} from \"./lens/RoomLens\";\nimport {ALL_VISIBLE} from \"./lens/RoomLens\";\n\nexport type HighlightEntry = { color: string; area: number; z: number };\nexport type PathEntry = { locations: number[]; color: string };\n\nexport type MapStateEventMap = {\n area: { area: IArea; zIndex: number };\n position: { roomId: number | undefined; center: boolean; areaChanged: boolean };\n center: { roomId: number; instant: boolean };\n highlight: { roomId: number; color: string | undefined };\n path: undefined;\n clear: undefined;\n lens: { lens: RoomLens };\n};\n\n/**\n * Pure data + events layer for map state.\n * No Konva, no DOM — just tracks what area is displayed,\n * where the player is, which rooms are highlighted, and active paths.\n *\n * Rendering backends subscribe to events and sync their visual state.\n */\nexport class MapState {\n readonly mapReader: IMapReader;\n readonly settings: Settings;\n readonly events = new TypedEventEmitter<MapStateEventMap>();\n\n currentArea?: number;\n currentAreaInstance?: IArea;\n currentZIndex?: number;\n currentAreaVersion?: number;\n positionRoomId?: number;\n centerRoomId?: number;\n highlights: Map<number, HighlightEntry> = new Map();\n paths: PathEntry[] = [];\n lens: RoomLens = ALL_VISIBLE;\n\n constructor(mapReader: IMapReader, settings: Settings) {\n this.mapReader = mapReader;\n this.settings = settings;\n }\n\n setLens(lens: RoomLens) {\n this.lens = lens;\n this.events.emit('lens', {lens});\n }\n\n /**\n * Set the displayed area and z-level.\n * Returns true if the area/z actually changed (or area version bumped).\n */\n setArea(id: number, zIndex: number): boolean {\n const area = this.mapReader.getArea(id);\n if (!area) return false;\n\n this.currentArea = id;\n this.currentAreaInstance = area;\n this.currentZIndex = zIndex;\n this.currentAreaVersion = area.getVersion();\n\n this.events.emit('area', {area, zIndex});\n return true;\n }\n\n setPosition(roomId: number, center: boolean = true): boolean {\n const room = this.mapReader.getRoom(roomId);\n if (!room) return false;\n\n // Switch area/z if needed\n const area = this.mapReader.getArea(room.area);\n const areaVersion = area?.getVersion();\n const areaChanged =\n this.currentArea !== room.area ||\n this.currentZIndex !== room.z ||\n (areaVersion !== undefined && this.currentAreaVersion !== areaVersion) ||\n (area !== undefined && this.currentAreaInstance !== area);\n\n if (areaChanged) {\n this.setArea(room.area, room.z);\n }\n\n this.positionRoomId = roomId;\n this.events.emit('position', {roomId, center, areaChanged});\n return true;\n }\n\n /**\n * Update position marker without centering or switching area.\n * Used when you want to show where the player is without moving the viewport.\n */\n updatePositionMarker(roomId: number) {\n this.positionRoomId = roomId;\n this.events.emit('position', {roomId, center: false, areaChanged: false});\n }\n\n /**\n * Re-emit position for the current room (e.g. after settings change).\n */\n refreshPosition() {\n if (this.positionRoomId !== undefined) {\n this.events.emit('position', {roomId: this.positionRoomId, center: false, areaChanged: false});\n }\n }\n\n clearPosition() {\n this.positionRoomId = undefined;\n this.events.emit('position', {roomId: undefined, center: false, areaChanged: false});\n }\n\n setCenterRoom(roomId: number, instant?: boolean): boolean {\n const room = this.mapReader.getRoom(roomId);\n if (!room) return false;\n\n const areaChanged =\n this.currentArea !== room.area ||\n this.currentZIndex !== room.z;\n\n if (areaChanged || this.needsAreaRedraw(room)) {\n this.setArea(room.area, room.z);\n }\n\n this.centerRoomId = roomId;\n this.events.emit('center', {roomId, instant: instant ?? areaChanged});\n return true;\n }\n\n needsAreaRedraw(room: MapData.Room): boolean {\n const area = this.mapReader.getArea(room.area);\n const areaVersion = area?.getVersion();\n return (\n (areaVersion !== undefined && this.currentAreaVersion !== areaVersion) ||\n (area !== undefined && this.currentAreaInstance !== area)\n );\n }\n\n addHighlight(roomId: number, color: string): boolean {\n const room = this.mapReader.getRoom(roomId);\n if (!room) return false;\n this.highlights.set(roomId, {color, area: room.area, z: room.z});\n this.events.emit('highlight', {roomId, color});\n return true;\n }\n\n removeHighlight(roomId: number) {\n if (!this.highlights.has(roomId)) return;\n this.highlights.delete(roomId);\n this.events.emit('highlight', {roomId, color: undefined});\n }\n\n hasHighlight(roomId: number): boolean {\n return this.highlights.has(roomId);\n }\n\n clearHighlights() {\n this.highlights.clear();\n this.events.emit('clear', undefined);\n }\n\n addPath(locations: number[], color: string = '#66E64D') {\n this.paths.push({locations, color});\n this.events.emit('path', undefined);\n }\n\n clearPaths() {\n this.paths = [];\n this.events.emit('path', undefined);\n }\n\n /**\n * Build the overlay descriptor for the current area/z,\n * merging stateful overlays with any extra overlays passed in.\n */\n getOverlaysForArea(extra?: SvgOverlays): SvgOverlays {\n const overlays: SvgOverlays = {...extra};\n\n // Position\n if (this.positionRoomId !== undefined) {\n const room = this.mapReader.getRoom(this.positionRoomId);\n if (room && room.area === this.currentArea && room.z === this.currentZIndex) {\n overlays.position = {roomId: this.positionRoomId};\n }\n }\n\n // Highlights (only for current area/z)\n const highlights: Array<{ roomId: number; color: string }> = [...(extra?.highlights ?? [])];\n for (const [roomId, entry] of this.highlights) {\n if (entry.area === this.currentArea && entry.z === this.currentZIndex) {\n highlights.push({roomId, color: entry.color});\n }\n }\n if (highlights.length > 0) overlays.highlights = highlights;\n\n // Paths\n const paths: Array<{ locations: number[]; color: string }> = [...(extra?.paths ?? [])];\n paths.push(...this.paths);\n if (paths.length > 0) overlays.paths = paths;\n\n return overlays;\n }\n\n /**\n * Get the effective plane bounds (respects uniformLevelSize setting).\n */\n getEffectiveBounds(area: IArea, plane: IPlane) {\n return this.settings.uniformLevelSize ? area.getFullBounds() : plane.getBounds();\n }\n\n /**\n * Compute export bounds for a given area/plane, optionally centered on a room.\n */\n computeExportBounds(area: IArea, plane: IPlane, roomId: number | undefined, padding: number) {\n if (roomId !== undefined) {\n const room = this.mapReader.getRoom(roomId);\n if (!room) throw new Error(`Room ${roomId} not found`);\n return {x: room.x - padding, y: room.y - padding, w: padding * 2, h: padding * 2};\n }\n const b = this.getEffectiveBounds(area, plane);\n const areaName = this.settings.areaName ? area.getAreaName() : undefined;\n const nameOverhead = areaName ? 7 : 0;\n const nameLeftOffset = areaName ? 3.5 : 0;\n const minX = b.minX - nameLeftOffset;\n const minY = b.minY - nameOverhead;\n const nameRight = areaName ? (b.minX - 3.5 + areaName.length * 2.5 * 0.6) : -Infinity;\n const maxX = Math.max(b.maxX, nameRight);\n return {x: minX - padding, y: minY - padding, w: (maxX - minX) + padding * 2, h: (b.maxY - minY) + padding * 2};\n }\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 KonvaRenderer}, {@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\";\n\nexport type RoomColors = {\n fillColor: string;\n strokeColor: string;\n borderWidth: number;\n symbolColor: string;\n envColor: string;\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 const strokeColor = strokeOverride\n ? ((settings.frameMode || settings.coloredMode) ? brightEnvColor : strokeOverride)\n : ((settings.frameMode || settings.coloredMode) ? brightEnvColor : settings.lineColor);\n const borderWidth = settings.borders ? settings.lineWidth : 0;\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};\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} 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\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 {fillColor, strokeColor, borderWidth, symbolColor} = computeRoomColors(\n room, mapReader, settings, options.strokeOverride,\n );\n\n const rs = settings.roomSize;\n const children: Shape[] = [];\n\n const emboss = computeEmboss(fillColor, settings);\n // When emboss is active, skip the regular border — the emboss lines serve as the border.\n const drawBorder = emboss ? 0 : borderWidth;\n\n const multiRing = 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 },\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 },\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 {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 {movePoint, movePointCircle, movePointRoundedRect} from \"./directions\";\nimport {longToShort, regularExits} from \"./reader/Exit\";\n\ntype Bounds = { x: number; y: number; width: number; height: number };\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 DrawCommandBuilder}: 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\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): SceneBuildResult {\n this.linkShapes = [];\n this.roomShapes = [];\n this.topLabelShapes = [];\n this.labelShapeRefs = [];\n this.specialExitShapeRefs = [];\n this.stubShapeRefs = [];\n this.areaExitLabelShapeRefs = [];\n\n // Labels\n this.renderLabels(plane.getLabels(), area.getAreaId());\n\n // Link exits (two-way connections)\n const exitResult = this.renderLinkExits(area.getLinkExits(zIndex), zIndex, lens);\n\n // Visible rooms only — the lens (e.g. exploration) drops the rest.\n const visibleRooms = (plane.getRooms() ?? []).filter(r => lens.isVisible(r));\n\n // Rooms (with stubs, special exits, inner exits)\n const roomResult = this.renderRooms(visibleRooms, zIndex);\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, {flatPipeline: true});\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 from special exits (custom lines to other areas)\n this.exitRenderer.getSpecialExitAreaTargets(room).forEach(zone => {\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 // Area exit hit zones from inner exits (up/down/in/out to other areas)\n this.exitRenderer.getInnerExitAreaTargets(room).forEach(zone => {\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 // 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 // --- 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 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 {Settings, ViewportBounds} from \"../../types/Settings\";\nimport type {LineShape, Shape} from \"../Shape\";\n\nexport interface GridLayoutOptions {\n /** Inverse coordinate transform (rendered → Cartesian) for warped pipelines. */\n inverseTransform?: (x: number, y: number) => {x: number; y: number};\n}\n\n/**\n * Pure layout for the background grid. Returns the line shapes that span the\n * given viewport bounds with one extra grid step of buffer on every edge.\n *\n * Returns an empty array when grids are disabled. Caching is the caller's\n * responsibility (the previous renderer cached by snapped bounds).\n */\nexport function layoutGrid(\n viewportBounds: ViewportBounds,\n settings: Settings,\n options: GridLayoutOptions = {},\n): LineShape[] {\n if (!settings.gridEnabled) return [];\n\n const inv = options.inverseTransform ?? ((x, y) => ({x, y}));\n const {minX: vMinX, maxX: vMaxX, minY: vMinY, maxY: vMaxY} = viewportBounds;\n const c1 = inv(vMinX, vMinY);\n const c2 = inv(vMaxX, vMinY);\n const c3 = inv(vMaxX, vMaxY);\n const c4 = inv(vMinX, vMaxY);\n const cartMinX = Math.min(c1.x, c2.x, c3.x, c4.x);\n const cartMaxX = Math.max(c1.x, c2.x, c3.x, c4.x);\n const cartMinY = Math.min(c1.y, c2.y, c3.y, c4.y);\n const cartMaxY = Math.max(c1.y, c2.y, c3.y, c4.y);\n\n const buffer = settings.gridSize * 2;\n const left = Math.floor((cartMinX - buffer) / settings.gridSize) * settings.gridSize;\n const right = Math.ceil((cartMaxX + buffer) / settings.gridSize) * settings.gridSize;\n const top = Math.floor((cartMinY - buffer) / settings.gridSize) * settings.gridSize;\n const bottom = Math.ceil((cartMaxY + buffer) / settings.gridSize) * settings.gridSize;\n\n const lines: LineShape[] = [];\n const paint = {stroke: settings.gridColor, strokeWidth: settings.gridLineWidth};\n\n for (let x = left; x <= right; x += settings.gridSize) {\n lines.push({\n type: \"line\",\n points: [x, top, x, bottom],\n paint,\n grid: true,\n layer: \"grid\",\n });\n }\n for (let y = top; y <= bottom; y += settings.gridSize) {\n lines.push({\n type: \"line\",\n points: [left, y, right, y],\n paint,\n grid: true,\n layer: \"grid\",\n });\n }\n return lines;\n}\n\n/** Snapped bounds — for cache-key comparison by callers. */\nexport function gridSnappedBounds(\n viewportBounds: ViewportBounds,\n settings: Settings,\n options: GridLayoutOptions = {},\n): {left: number; right: number; top: number; bottom: number} | null {\n if (!settings.gridEnabled) return null;\n\n const inv = options.inverseTransform ?? ((x, y) => ({x, y}));\n const {minX: vMinX, maxX: vMaxX, minY: vMinY, maxY: vMaxY} = viewportBounds;\n const c1 = inv(vMinX, vMinY);\n const c2 = inv(vMaxX, vMinY);\n const c3 = inv(vMaxX, vMaxY);\n const c4 = inv(vMinX, vMaxY);\n const cartMinX = Math.min(c1.x, c2.x, c3.x, c4.x);\n const cartMaxX = Math.max(c1.x, c2.x, c3.x, c4.x);\n const cartMinY = Math.min(c1.y, c2.y, c3.y, c4.y);\n const cartMaxY = Math.max(c1.y, c2.y, c3.y, c4.y);\n\n const buffer = settings.gridSize * 2;\n return {\n left: Math.floor((cartMinX - buffer) / settings.gridSize) * settings.gridSize,\n right: Math.ceil((cartMaxX + buffer) / settings.gridSize) * settings.gridSize,\n top: Math.floor((cartMinY - buffer) / settings.gridSize) * settings.gridSize,\n bottom: Math.ceil((cartMaxY + buffer) / settings.gridSize) * settings.gridSize,\n };\n}\n","import type {SceneBuildResult, SceneShapesByLayer} from \"../ScenePipeline\";\nimport type {ViewportBounds, Settings} from \"../types/Settings\";\nimport type {Shape} from \"../scene/Shape\";\nimport {layoutGrid} from \"../scene/elements/GridLayout\";\n\n/**\n * Interactive-path optimised alternative to {@link clipSceneToViewport}.\n *\n * Returns a single `Map<Shape, boolean>` where `true` means the shape is\n * inside the viewport and `false` means it should be hidden. Shapes absent\n * from the map are \"unmanaged\" pass-throughs (noScaling labels, overlays) and\n * should be treated as visible.\n *\n * Allocation budget per frame: 1 Map instead of the 12 Sets + 2 filtered\n * arrays that {@link clipSceneToViewport} produces.\n */\nexport function buildCullingVisibilityMap(\n result: SceneBuildResult,\n viewportBounds: ViewportBounds,\n settings: Settings,\n transforms?: SceneTransforms,\n): Map<Shape, boolean> {\n if (!settings.cullingEnabled) return new Map();\n\n const {minX, maxX, minY, maxY} = viewportBounds;\n const half = settings.roomSize / 2;\n const fn = transforms?.forward;\n\n const inView = (bMinX: number, bMinY: number, bMaxX: number, bMaxY: number): boolean => {\n if (fn) {\n const tb = transformedBbox(bMinX, bMinY, bMaxX, bMaxY, fn);\n return tb.maxX >= minX && tb.minX <= maxX && tb.maxY >= minY && tb.minY <= maxY;\n }\n return bMaxX >= minX && bMinX <= maxX && bMaxY >= minY && bMinY <= maxY;\n };\n\n const visibility = new Map<Shape, boolean>();\n\n for (const {room, shape} of result.roomShapeRefs.values()) {\n visibility.set(shape, inView(room.x - half, room.y - half, room.x + half, room.y + half));\n }\n for (const {shape, bounds: b} of result.standaloneExitShapeRefs) {\n visibility.set(shape, inView(b.x, b.y, b.x + b.width, b.y + b.height));\n }\n for (const {shape, bounds: b} of result.labelShapeRefs) {\n visibility.set(shape, inView(b.x, b.y, b.x + b.width, b.y + b.height));\n }\n for (const {shape, bounds: b} of result.specialExitShapeRefs) {\n visibility.set(shape, inView(b.x, b.y, b.x + b.width, b.y + b.height));\n }\n for (const {shape, bounds: b} of result.stubShapeRefs) {\n visibility.set(shape, inView(b.x, b.y, b.x + b.width, b.y + b.height));\n }\n for (const {shape, bounds: b} of result.areaExitLabelShapeRefs) {\n visibility.set(shape, inView(b.x, b.y, b.x + b.width, b.y + b.height));\n }\n\n return visibility;\n}\n\ntype TransformFn = (x: number, y: number) => {x: number; y: number};\n\n/** Forward and inverse coordinate transforms for styles that warp space (e.g. Isometric). */\nexport type SceneTransforms = {\n /** World → scene (for culling bounding-box projection). */\n forward?: TransformFn;\n /** Scene → world (for grid-line Cartesian-bounds computation). */\n inverse?: TransformFn;\n};\n\nfunction transformedBbox(\n minX: number, minY: number, maxX: number, maxY: number,\n fn: TransformFn,\n): {minX: number; minY: number; maxX: number; maxY: number} {\n const c1 = fn(minX, minY);\n const c2 = fn(maxX, minY);\n const c3 = fn(maxX, maxY);\n const c4 = fn(minX, maxY);\n return {\n minX: Math.min(c1.x, c2.x, c3.x, c4.x),\n minY: Math.min(c1.y, c2.y, c3.y, c4.y),\n maxX: Math.max(c1.x, c2.x, c3.x, c4.x),\n maxY: Math.max(c1.y, c2.y, c3.y, c4.y),\n };\n}\n\n/**\n * Filter scene shapes to only those that intersect the given viewport, and\n * generate the grid for that viewport — the shared cull step used by export\n * paths (SVG, PNG).\n *\n * Uses {@link buildCullingVisibilityMap} for the predicate so culling logic\n * lives in one place. Shapes absent from the map are unmanaged pass-throughs\n * (noScaling labels, overlays) and are always included.\n *\n * Interactive path uses {@link buildCullingVisibilityMap} directly and skips\n * building these filtered arrays.\n *\n * When `settings.cullingEnabled` is false the original {@link SceneShapesByLayer}\n * is returned (with grid appended).\n *\n * `transforms.forward` projects world → rendered space for styles like\n * Isometric that warp coordinates (pass `style.worldToScene` when available).\n * `transforms.inverse` projects rendered → world space (pass `style.sceneToWorld`)\n * for correct grid-line Cartesian-bounds computation under warped styles.\n */\nexport function clipSceneToViewport(\n result: SceneBuildResult,\n viewportBounds: ViewportBounds,\n settings: Settings,\n transforms?: SceneTransforms,\n): SceneShapesByLayer {\n const grid = layoutGrid(viewportBounds, settings, {inverseTransform: transforms?.inverse});\n\n if (!settings.cullingEnabled) {\n return {...result.sceneShapes, grid};\n }\n\n const visibility = buildCullingVisibilityMap(result, viewportBounds, settings, transforms);\n return {\n grid,\n link: result.sceneShapes.link.filter(s => visibility.get(s) ?? true),\n room: result.sceneShapes.room.filter(s => visibility.get(s) ?? true),\n topLabel: result.sceneShapes.topLabel,\n };\n}\n","import type {IArea} from \"../reader/Area\";\nimport type {IPlane} from \"../reader/Plane\";\nimport type {Settings} from \"../types/Settings\";\nimport {ScenePipeline} from \"../ScenePipeline\";\nimport type {\n SceneBuildResult,\n SceneShapesByLayer,\n AreaExitHitZone,\n DrawnExitEntry,\n DrawnSpecialExitEntry,\n DrawnStubEntry,\n} from \"../ScenePipeline\";\nimport {clipSceneToViewport, buildCullingVisibilityMap} from \"../export/clipSceneToViewport\";\nimport type {Camera} from \"../camera/Camera\";\nimport type {CoordFn} from \"../coord/CoordFn\";\nimport {IDENTITY_TRANSFORM} from \"../coord/CoordFn\";\nimport type {SceneTransforms} from \"../export/clipSceneToViewport\";\nimport type {GroupShape, Shape} from \"../scene/Shape\";\nimport type {IMapReader} from \"../reader/MapReader\";\nimport type {RoomLens} from \"../lens/RoomLens\";\nimport type {ExitDrawData} from \"../ExitRenderer\";\n\nexport interface CullStats {\n visibleRooms: number;\n totalRooms: number;\n visibleExits: number;\n}\n\nexport interface CullOutput {\n shapes: SceneShapesByLayer;\n stats: CullStats;\n}\n\nconst EMPTY_SCENE: SceneShapesByLayer = {grid: [], link: [], room: [], topLabel: []};\n\n/**\n * Encapsulates the scene build + cull pipeline for the interactive renderer.\n *\n * Owns {@link ScenePipeline} and the last {@link SceneBuildResult}. Backends\n * call {@link rebuild} when the area/plane changes, then {@link cull} on each\n * viewport update to get the currently-visible shapes. Neither the pipeline\n * nor {@link clipSceneToViewport} need to be referenced outside this class.\n */\nexport class SceneManager {\n private pipeline: ScenePipeline;\n private lastBuildResult?: SceneBuildResult;\n private standaloneExitShapeSet: Set<Shape> = new Set();\n\n constructor(\n private readonly camera: Camera,\n private readonly settings: Settings,\n mapReader: IMapReader,\n ) {\n this.pipeline = new ScenePipeline(mapReader, settings);\n }\n\n get exitRenderer() {\n return this.pipeline.exitRenderer;\n }\n\n get lastResult(): SceneBuildResult | undefined {\n return this.lastBuildResult;\n }\n\n get drawnExits(): readonly DrawnExitEntry[] {\n return this.lastBuildResult?.drawnExits ?? [];\n }\n\n get drawnSpecialExits(): readonly DrawnSpecialExitEntry[] {\n return this.lastBuildResult?.drawnSpecialExits ?? [];\n }\n\n get drawnStubs(): readonly DrawnStubEntry[] {\n return this.lastBuildResult?.drawnStubs ?? [];\n }\n\n get areaExitHitZones(): readonly AreaExitHitZone[] {\n return this.lastBuildResult?.areaExitHitZones ?? [];\n }\n\n get hitShapes(): readonly Shape[] {\n return this.lastBuildResult?.hitShapes ?? [];\n }\n\n rebuild(area: IArea, plane: IPlane, zIndex: number, lens?: RoomLens): SceneBuildResult {\n this.lastBuildResult = this.pipeline.buildScene(area, plane, zIndex, lens);\n this.standaloneExitShapeSet = new Set(\n this.lastBuildResult.standaloneExitShapeRefs.map(r => r.shape),\n );\n return this.lastBuildResult;\n }\n\n buildExitShape(data: ExitDrawData): GroupShape {\n return this.pipeline.buildExitShape(data);\n }\n\n reset(): void {\n this.lastBuildResult = undefined;\n this.standaloneExitShapeSet = new Set();\n }\n\n resetPipeline(mapReader: IMapReader): void {\n this.pipeline = new ScenePipeline(mapReader, this.settings);\n this.reset();\n }\n\n /**\n * Lightweight cull for the interactive render path. Returns a\n * `Map<Shape, boolean>` where absent shapes are unmanaged pass-throughs\n * (always visible). Avoids the 12 Sets + 2 filtered arrays produced by\n * the full {@link cull} path.\n */\n cullInteractive(coordinateTransform: CoordFn = IDENTITY_TRANSFORM): Map<Shape, boolean> {\n if (!this.lastBuildResult) return new Map();\n const viewport = this.camera.getCullingViewport(this.settings.cullingBounds);\n const transforms: SceneTransforms | undefined = coordinateTransform !== IDENTITY_TRANSFORM\n ? {forward: coordinateTransform as (x: number, y: number) => {x: number; y: number}}\n : undefined;\n return buildCullingVisibilityMap(this.lastBuildResult, viewport, this.settings, transforms);\n }\n\n cull(coordinateTransform: CoordFn = IDENTITY_TRANSFORM): CullOutput {\n if (!this.lastBuildResult) {\n return {shapes: EMPTY_SCENE, stats: {visibleRooms: 0, totalRooms: 0, visibleExits: 0}};\n }\n\n const viewport = this.camera.getCullingViewport(this.settings.cullingBounds);\n const transforms: SceneTransforms | undefined = coordinateTransform !== IDENTITY_TRANSFORM\n ? {forward: coordinateTransform as (x: number, y: number) => {x: number; y: number}}\n : undefined;\n const shapes = clipSceneToViewport(this.lastBuildResult, viewport, this.settings, transforms);\n\n return {\n shapes,\n stats: {\n visibleRooms: shapes.room.length,\n totalRooms: this.lastBuildResult.roomShapeRefs.size,\n visibleExits: shapes.link.filter(s => this.standaloneExitShapeSet.has(s)).length,\n },\n };\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","/**\n * Konva-side replay infrastructure for {@link Shape}-derived draw commands.\n *\n * A {@link RecordingGroupNode} captures one positional group of low-level\n * draw commands (rect / circle / line / polygon / text / image, optionally\n * wrapped by an affine transform). It stays as plain data until either\n * - `materialize()` makes it into a single `Konva.Group` containing one\n * `Konva.Shape` whose `sceneFunc` replays the captured commands, or\n * - it is added to a {@link RecordingLayerNode} / {@link DrawCommandLayerNode}\n * that batches many groups into one shared `Konva.Shape` per layer.\n *\n * The single-`sceneFunc` per layer is the fast path used by the main scene\n * because it lets culling toggle individual entries (`DrawEntry.visible`)\n * without paying Konva's per-node overhead.\n */\n\nimport Konva from \"konva\";\nimport {BASE_SCALE} from \"../camera/Camera\";\n\n// --- Internal draw command types ---\n// These are the low-level Canvas2D-shaped commands captured per\n// RecordingGroupNode. They differ from src/draw/DrawCommand.ts (which is\n// camera-transformed render-space output for export pipelines): commands\n// here stay in group-local coordinates and are replayed under whatever\n// transform Konva has already applied to the layer.\n\ntype RectCommand = { type: 'rect'; x: number; y: number; w: number; h: number; fill?: string; stroke?: string; sw: number; cr: number; dash?: number[] };\ntype CircleCommand = { type: 'circle'; cx: number; cy: number; r: number; fill?: string; stroke?: string; sw: number; dash?: number[] };\ntype LineCommand = { type: 'line'; points: number[]; stroke?: string; sw: number; dash?: number[]; lineCap?: string; lineJoin?: string; alpha?: number };\ntype PolygonCommand = { type: 'polygon'; vertices: number[]; fill?: string; stroke?: string; sw: number };\ntype TextCommand = { type: 'text'; x: number; y: number; text: string; fontSize: number; fontFamily: string; fontStyle: string; fill: string; stroke?: string; sw: number; align: string; vAlign: string; w: number; h: number; baselineRatio?: number; transform?: [number, number, number, number, number, number] };\ntype ImageCommand = { type: 'image'; x: number; y: number; w: number; h: number; image: HTMLImageElement | any; transform?: [number, number, number, number, number, number] };\n\nexport type RecordingDrawCommand =\n | RectCommand\n | CircleCommand\n | LineCommand\n | PolygonCommand\n | TextCommand\n | ImageCommand;\n\n// --- Canvas2D replay ---\n\nfunction replayCommand(ctx: CanvasRenderingContext2D, cmd: RecordingDrawCommand) {\n switch (cmd.type) {\n case 'rect': {\n ctx.beginPath();\n if (cmd.cr > 0 && typeof ctx.roundRect === 'function') {\n ctx.roundRect(cmd.x, cmd.y, cmd.w, cmd.h, cmd.cr);\n } else {\n ctx.rect(cmd.x, cmd.y, cmd.w, cmd.h);\n }\n if (cmd.fill) {\n ctx.fillStyle = cmd.fill;\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n if (cmd.dash) ctx.setLineDash(cmd.dash); else ctx.setLineDash([]);\n ctx.stroke();\n }\n break;\n }\n case 'circle': {\n ctx.beginPath();\n ctx.arc(cmd.cx, cmd.cy, cmd.r, 0, Math.PI * 2);\n if (cmd.fill) {\n ctx.fillStyle = cmd.fill;\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n if (cmd.dash) ctx.setLineDash(cmd.dash); else ctx.setLineDash([]);\n ctx.stroke();\n }\n break;\n }\n case 'line': {\n if (cmd.points.length < 4) break;\n const savedAlpha = ctx.globalAlpha;\n if (cmd.alpha !== undefined) ctx.globalAlpha = cmd.alpha;\n ctx.beginPath();\n ctx.moveTo(cmd.points[0], cmd.points[1]);\n for (let i = 2; i < cmd.points.length; i += 2) {\n ctx.lineTo(cmd.points[i], cmd.points[i + 1]);\n }\n if (cmd.stroke) ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n if (cmd.dash) ctx.setLineDash(cmd.dash); else ctx.setLineDash([]);\n if (cmd.lineCap) ctx.lineCap = cmd.lineCap as CanvasLineCap;\n if (cmd.lineJoin) ctx.lineJoin = cmd.lineJoin as CanvasLineJoin;\n ctx.stroke();\n if (cmd.alpha !== undefined) ctx.globalAlpha = savedAlpha;\n break;\n }\n case 'polygon': {\n if (cmd.vertices.length < 4) break;\n ctx.beginPath();\n ctx.moveTo(cmd.vertices[0], cmd.vertices[1]);\n for (let i = 2; i < cmd.vertices.length; i += 2) {\n ctx.lineTo(cmd.vertices[i], cmd.vertices[i + 1]);\n }\n ctx.closePath();\n if (cmd.fill) {\n ctx.fillStyle = cmd.fill;\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n ctx.setLineDash([]);\n ctx.stroke();\n }\n break;\n }\n case 'text': {\n // Sub-pixel font sizes break Canvas2D text metrics on some\n // engines (notably node-canvas). Render at TEXT_SCALE × the\n // requested size and counter-scale so output stays pixel-correct.\n const TEXT_SCALE = 100;\n const scaledSize = cmd.fontSize * TEXT_SCALE;\n const font = `${cmd.fontStyle} ${scaledSize}px ${cmd.fontFamily}`;\n ctx.save();\n ctx.font = font;\n ctx.fillStyle = cmd.fill;\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw * TEXT_SCALE;\n ctx.lineJoin = 'round';\n }\n const hasBaselineRatio = cmd.baselineRatio !== undefined;\n if (cmd.transform) {\n ctx.transform(...cmd.transform);\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n ctx.textAlign = 'center';\n if (hasBaselineRatio) {\n ctx.textBaseline = 'alphabetic';\n const by = (cmd.h / 2 + cmd.baselineRatio! * cmd.fontSize) * TEXT_SCALE;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, cmd.w * TEXT_SCALE / 2, by);\n ctx.fillText(cmd.text, cmd.w * TEXT_SCALE / 2, by);\n } else {\n ctx.textBaseline = 'middle';\n const mx = cmd.w * TEXT_SCALE / 2;\n const my = cmd.h * TEXT_SCALE / 2;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, mx, my);\n ctx.fillText(cmd.text, mx, my);\n }\n } else if (cmd.w > 0 && cmd.h > 0) {\n ctx.textAlign = (cmd.align || 'left') as CanvasTextAlign;\n const tx = cmd.align === 'center' ? cmd.x + cmd.w / 2 : cmd.x;\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n if (cmd.vAlign === 'middle' && hasBaselineRatio) {\n ctx.textBaseline = 'alphabetic';\n const ty = cmd.y + cmd.h / 2 + cmd.baselineRatio! * cmd.fontSize;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n ctx.fillText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n } else {\n ctx.textBaseline = cmd.vAlign === 'middle' ? 'middle' : 'top';\n const ty = cmd.vAlign === 'middle' ? cmd.y + cmd.h / 2 : cmd.y;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n ctx.fillText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n }\n } else {\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, cmd.x * TEXT_SCALE, cmd.y * TEXT_SCALE);\n ctx.fillText(cmd.text, cmd.x * TEXT_SCALE, cmd.y * TEXT_SCALE);\n }\n ctx.restore();\n break;\n }\n case 'image': {\n if (!cmd.image) break;\n if (cmd.transform) {\n ctx.save();\n ctx.transform(...cmd.transform);\n ctx.drawImage(cmd.image, 0, 0, cmd.w, cmd.h);\n ctx.restore();\n } else {\n ctx.drawImage(cmd.image, cmd.x, cmd.y, cmd.w, cmd.h);\n }\n break;\n }\n }\n}\n\n// --- Recording group node ---\n\n/**\n * One positional group of recorded draw commands. Used as both the unit a\n * shape walker emits and the cull-tracking handle inside\n * {@link DrawCommandLayerNode}. Lazily materializes into a `Konva.Group` /\n * `Konva.Shape` pair when added directly to a Konva layer.\n */\nexport class RecordingGroupNode {\n x: number;\n y: number;\n _visible = true;\n noScaling = false;\n readonly commands: RecordingDrawCommand[] = [];\n /** Lazily created when this group is materialized for a Konva.Layer. */\n _konvaGroup?: Konva.Group;\n\n constructor(x: number, y: number) {\n this.x = x;\n this.y = y;\n }\n\n setVisible(visible: boolean) {\n this._visible = visible;\n this._konvaGroup?.visible(visible);\n }\n\n isVisible(): boolean {\n return this._visible;\n }\n\n destroy() {\n this._konvaGroup?.destroy();\n this._konvaGroup = undefined;\n this.commands.length = 0;\n }\n\n setPosition(x: number, y: number) {\n this.x = x;\n this.y = y;\n this._konvaGroup?.position({x, y});\n }\n\n getPosition() {\n return {x: this.x, y: this.y};\n }\n\n moveToTop() {\n this._konvaGroup?.moveToTop();\n }\n\n /**\n * Materialize this recording as a Konva.Group + Konva.Shape. Used by\n * {@link MaterializingLayerNode} for layers that want individual nodes\n * (overlay, position) rather than a single bulk replay.\n */\n materialize(): Konva.Group {\n if (this._konvaGroup) return this._konvaGroup;\n const group = new Konva.Group({\n x: this.x, y: this.y,\n listening: false,\n visible: this._visible,\n });\n const cmds = this.commands;\n group.add(new Konva.Shape({\n listening: false,\n perfectDrawEnabled: false,\n sceneFunc: (context) => {\n const ctx = context._context as CanvasRenderingContext2D;\n for (const cmd of cmds) {\n replayCommand(ctx, cmd);\n }\n },\n }));\n this._konvaGroup = group;\n return group;\n }\n}\n\n// --- Pure-data draw entry ---\n\n/**\n * Lightweight record extracted from a {@link RecordingGroupNode} when added\n * to a {@link DrawCommandLayerNode}. Contains only plain data so culling\n * callbacks can toggle visibility without a Konva node reference.\n */\nexport type DrawEntry = {\n x: number;\n y: number;\n noScaling: boolean;\n readonly commands: RecordingDrawCommand[];\n visible: boolean;\n};\n\n// --- DrawCommandLayerNode ---\n\n/**\n * LayerNode backed by a single Konva.Shape whose sceneFunc replays\n * {@link DrawEntry} objects. Used for the main scene layer where individual\n * shapes need cull toggling.\n */\nexport class DrawCommandLayerNode {\n private readonly entries: DrawEntry[] = [];\n private readonly nodeToEntry = new Map<RecordingGroupNode, DrawEntry>();\n private readonly konvaLayer: Konva.Layer;\n private konvaShape: Konva.Shape;\n\n constructor(konvaLayer: Konva.Layer) {\n this.konvaLayer = konvaLayer;\n konvaLayer.destroyChildren();\n const self = this;\n this.konvaShape = new Konva.Shape({\n listening: false,\n perfectDrawEnabled: false,\n sceneFunc: (context) => {\n const ctx = context._context as CanvasRenderingContext2D;\n const base = ctx.getTransform();\n const a = base.a, b = base.b, c = base.c, d = base.d;\n for (const entry of self.entries) {\n if (!entry.visible) continue;\n const tx = a * entry.x + c * entry.y + base.e;\n const ty = b * entry.x + d * entry.y + base.f;\n if (entry.noScaling) {\n ctx.setTransform(BASE_SCALE, 0, 0, BASE_SCALE, tx, ty);\n } else {\n ctx.setTransform(a, b, c, d, tx, ty);\n }\n for (const cmd of entry.commands) {\n replayCommand(ctx, cmd);\n }\n }\n ctx.setTransform(base);\n },\n });\n konvaLayer.add(this.konvaShape);\n }\n\n addNode(node: RecordingGroupNode): void {\n const entry: DrawEntry = {\n x: node.x,\n y: node.y,\n noScaling: node.noScaling,\n commands: node.commands,\n visible: node._visible,\n };\n this.entries.push(entry);\n this.nodeToEntry.set(node, entry);\n this.ensureShape();\n }\n\n /** Return the DrawEntry created when `node` was added, or undefined if not found. */\n getEntry(node: RecordingGroupNode): DrawEntry | undefined {\n return this.nodeToEntry.get(node);\n }\n\n destroyChildren(): void {\n this.entries.length = 0;\n this.nodeToEntry.clear();\n }\n\n batchDraw(): void {\n this.konvaLayer.batchDraw();\n }\n\n private ensureShape(): void {\n if (!this.konvaShape.getParent()) {\n this.konvaLayer.add(this.konvaShape);\n }\n }\n}\n\n// --- RecordingLayerNode ---\n\n/**\n * Layer backed by a single Konva.Shape whose sceneFunc replays every added\n * {@link RecordingGroupNode} in order. Used for layers that don't need\n * per-shape cull toggling (grid, top-label).\n */\nexport class RecordingLayerNode {\n private groups: RecordingGroupNode[] = [];\n private readonly konvaLayer: Konva.Layer;\n private konvaShape: Konva.Shape;\n\n constructor(konvaLayer: Konva.Layer) {\n this.konvaLayer = konvaLayer;\n konvaLayer.destroyChildren();\n const self = this;\n this.konvaShape = new Konva.Shape({\n listening: false,\n perfectDrawEnabled: false,\n sceneFunc: (context) => {\n const ctx = context._context as CanvasRenderingContext2D;\n const base = ctx.getTransform();\n const a = base.a, b = base.b, c = base.c, d = base.d;\n for (const group of self.groups) {\n if (!group._visible) continue;\n const tx = a * group.x + c * group.y + base.e;\n const ty = b * group.x + d * group.y + base.f;\n if (group.noScaling) {\n ctx.setTransform(BASE_SCALE, 0, 0, BASE_SCALE, tx, ty);\n } else {\n ctx.setTransform(a, b, c, d, tx, ty);\n }\n for (const cmd of group.commands) {\n replayCommand(ctx, cmd);\n }\n }\n ctx.setTransform(base);\n },\n });\n konvaLayer.add(this.konvaShape);\n }\n\n addNode(node: RecordingGroupNode) {\n this.groups.push(node);\n this.ensureShape();\n }\n\n destroyChildren() {\n this.groups.length = 0;\n }\n\n batchDraw() {\n this.konvaLayer.batchDraw();\n }\n\n private ensureShape() {\n if (!this.konvaShape.getParent()) {\n this.konvaLayer.add(this.konvaShape);\n }\n }\n}\n\n// --- MaterializingLayerNode ---\n\n/**\n * Layer that materializes each added {@link RecordingGroupNode} as its own\n * `Konva.Group`. Used for layers where the renderer keeps a reference to\n * each node (overlay highlights, position marker) so it can destroy them\n * individually.\n */\nexport class MaterializingLayerNode {\n constructor(private readonly konvaLayer: Konva.Layer) {}\n\n addNode(node: RecordingGroupNode) {\n this.konvaLayer.add(node.materialize());\n }\n\n destroyChildren() {\n this.konvaLayer.destroyChildren();\n }\n\n batchDraw() {\n this.konvaLayer.batchDraw();\n }\n}\n","/**\n * Walk a {@link GroupShape} into a {@link RecordingGroupNode} — the unit the\n * Konva layer infrastructure consumes for replay. Same shape → command map\n * the legacy DrawingBackend → CanvasBackend pair did, but in one step with\n * no backend interface.\n *\n * Returns the node without attaching it to a layer; the caller decides which\n * layer to push it into (overlay layer materializes one Konva.Group per\n * recording, scene/grid/topLabel batch many recordings into one shared\n * sceneFunc).\n */\n\nimport Konva from \"konva\";\nimport {RecordingGroupNode} from \"./RecordingLayer\";\nimport type {GroupShape, Shape} from \"../scene/Shape\";\n\nexport function shapeToRecording(group: GroupShape): RecordingGroupNode {\n const node = new RecordingGroupNode(group.x, group.y);\n if (group.noScale) node.noScaling = true;\n addChildren(node, group.children, 0, 0);\n return node;\n}\n\nfunction addChildren(\n node: RecordingGroupNode,\n children: Shape[],\n ox: number,\n oy: number,\n): void {\n for (const child of children) {\n addShape(node, child, ox, oy);\n }\n}\n\nfunction addShape(\n node: RecordingGroupNode,\n shape: Shape,\n ox: number,\n oy: number,\n): void {\n switch (shape.type) {\n case \"rect\":\n node.commands.push({\n type: \"rect\",\n x: shape.x + ox,\n y: shape.y + oy,\n w: shape.width,\n h: shape.height,\n fill: shape.paint.fill,\n stroke: shape.paint.stroke,\n sw: shape.paint.strokeWidth ?? 0,\n cr: shape.cornerRadius ?? 0,\n dash: (shape.paint.dashEnabled !== false && shape.paint.dash) ? shape.paint.dash : undefined,\n });\n return;\n case \"circle\":\n node.commands.push({\n type: \"circle\",\n cx: shape.cx + ox,\n cy: shape.cy + oy,\n r: shape.radius,\n fill: shape.paint.fill,\n stroke: shape.paint.stroke,\n sw: shape.paint.strokeWidth ?? 0,\n dash: (shape.paint.dashEnabled !== false && shape.paint.dash) ? shape.paint.dash : undefined,\n });\n return;\n case \"line\": {\n const points = (ox === 0 && oy === 0)\n ? shape.points\n : translatePoints(shape.points, ox, oy);\n node.commands.push({\n type: \"line\",\n points,\n stroke: shape.paint.stroke,\n sw: shape.paint.strokeWidth ?? 0,\n dash: shape.paint.dash,\n lineCap: shape.lineCap,\n lineJoin: shape.lineJoin,\n alpha: shape.paint.alpha,\n });\n return;\n }\n case \"polygon\":\n node.commands.push({\n type: \"polygon\",\n vertices: (ox === 0 && oy === 0)\n ? shape.vertices\n : translatePoints(shape.vertices, ox, oy),\n fill: shape.paint.fill,\n stroke: shape.paint.stroke,\n sw: shape.paint.strokeWidth ?? 0,\n });\n return;\n case \"text\":\n node.commands.push({\n type: \"text\",\n x: shape.x + ox,\n y: shape.y + oy,\n text: shape.text,\n fontSize: shape.fontSize,\n fontFamily: shape.fontFamily ?? \"sans-serif\",\n fontStyle: shape.fontStyle ?? \"normal\",\n fill: shape.fill ?? \"black\",\n stroke: shape.stroke,\n sw: shape.strokeWidth ?? 0,\n align: shape.align ?? \"left\",\n vAlign: shape.verticalAlign ?? \"top\",\n w: shape.width ?? 0,\n h: shape.height ?? 0,\n baselineRatio: shape.baselineRatio,\n transform: shape.transform,\n });\n return;\n case \"image\": {\n const image = createImageElement(shape.src);\n node.commands.push({\n type: \"image\",\n x: shape.x + ox,\n y: shape.y + oy,\n w: shape.width,\n h: shape.height,\n image,\n transform: shape.transform,\n });\n return;\n }\n case \"group\":\n addChildren(node, shape.children, ox + shape.x, oy + shape.y);\n return;\n }\n}\n\nfunction translatePoints(points: number[], ox: number, oy: number): number[] {\n const out = new Array<number>(points.length);\n for (let i = 0; i < points.length; i += 2) {\n out[i] = points[i] + ox;\n out[i + 1] = points[i + 1] + oy;\n }\n return out;\n}\n\nfunction createImageElement(src: string): HTMLImageElement | any {\n const image = typeof Konva !== \"undefined\"\n ? Konva.Util.createImageElement()\n : (typeof Image !== \"undefined\" ? new Image() : null);\n if (image) image.src = src;\n return image;\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 strokeColor: string;\n strokeAlpha: number;\n strokeWidth: number;\n fillColor: string;\n fillAlpha: number;\n dash?: number[];\n dashEnabled: boolean;\n};\n\nexport function computeHighlight(room: MapData.Room, color: string, settings: Settings): HighlightData {\n const hl = settings.highlight;\n const rs = settings.roomSize;\n const factor = hl.sizeFactor;\n const useRoomShape = hl.matchRoomShape && settings.roomShape !== \"circle\";\n return {\n shape: useRoomShape ? 'rect' : 'circle',\n cx: room.x,\n cy: room.y,\n size: rs / 2 * factor,\n cornerRadius: useRoomShape && settings.roomShape === \"roundedRectangle\" ? rs * factor * 0.2 : 0,\n strokeColor: color,\n strokeAlpha: hl.strokeAlpha,\n strokeWidth: hl.strokeWidth,\n fillColor: color,\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 DrawCommandBuilder} + 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 */\nexport function highlightToShape(data: HighlightData): 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 // 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/** 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 * - `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/** 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\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 */\n build(shapes: Shape[], roomSize: number, coordTransform?: CoordTransform): void {\n this.clear();\n this.roomSize = roomSize;\n this.bucketSize = Math.max(roomSize * 10, 5);\n this.transform = coordTransform ?? identity;\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.transform, 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 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 DrawCommandBuilder}, 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/** 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}\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 Konva from \"konva\";\nimport type {IArea} from \"../reader/Area\";\nimport type {IPlane} from \"../reader/Plane\";\nimport type {RendererEventMap, ViewportBounds} from \"../types/Settings\";\nimport type {AreaExitHitZone, DrawnExitEntry, DrawnSpecialExitEntry, DrawnStubEntry} from \"../ScenePipeline\";\nimport type {MapState} from \"../MapState\";\nimport {Camera} from \"../camera/Camera\";\nimport {CullingManager} from \"../CullingManager\";\nimport {SceneManager} from \"./SceneManager\";\nimport {InteractionHandler} from \"../InteractionHandler\";\nimport {TypedEventEmitter} from \"../TypedEventEmitter\";\nimport {\n DrawCommandLayerNode,\n MaterializingLayerNode,\n RecordingGroupNode,\n RecordingLayerNode,\n type DrawEntry,\n} from \"../render/RecordingLayer\";\nimport {shapeToRecording} from \"../render/shapeToRecording\";\nimport type {InteractiveBackend} from \"./MapRenderer\";\nimport type {CoordFn} from \"../coord/CoordFn\";\nimport {IDENTITY_TRANSFORM} from \"../coord/CoordFn\";\nimport {computeHighlight, computePositionMarker, computePathOverlay} from \"../scene/OverlayStyle\";\nimport {computeStubs} from \"../scene/StubStyle\";\nimport {computeSpecialExits} from \"../scene/SpecialExitStyle\";\nimport {layoutRoom} from \"../scene/elements/RoomLayout\";\nimport {layoutInnerExits} from \"../scene/elements/ExitLayout\";\nimport {layoutGrid} from \"../scene/elements/GridLayout\";\nimport {specialExitToShape} from \"../scene/elements/SpecialExitLayout\";\nimport {stubToShape} from \"../scene/elements/StubLayout\";\nimport {\n highlightToShape, positionMarkerToShape, pathToShapes,\n} from \"../scene/elements/OverlayLayout\";\nimport type {LiveEffect} from \"../overlay/LiveEffect\";\nimport type {SceneOverlay, SceneOverlayContext} from \"../overlay/SceneOverlay\";\nimport type {ExportCanvas} from \"../export/Exporter\";\nimport {HitTester} from \"../hit/HitTester\";\nimport type {GroupShape, Shape} from \"../scene/Shape\";\nimport type {Style, StyleContext} from \"../style/Style\";\nimport {identityStyle} from \"../style/Style\";\nimport {applyStyleToShapes} from \"../style/applyStyle\";\n\nconst currentRoomColor = 'rgb(120, 72, 0)';\n\n/**\n * Konva rendering engine. Owns the full rendering pipeline:\n * stage, layers, scene builder, culling, overlays.\n *\n * Camera is the source of truth for transform state.\n * This backend subscribes to the camera's `change` event and applies state to the Konva stage.\n *\n * Works identically in both modes:\n * - DOM container → stage attached to DOM, mouse/touch → camera\n * - No container → headless stage, same camera/culling, no input\n */\nexport class KonvaRenderBackend implements InteractiveBackend {\n readonly stage: Konva.Stage;\n readonly gridLayer: Konva.Layer;\n readonly linkLayer: Konva.Layer;\n readonly roomLayer: Konva.Layer;\n readonly topLabelLayer: Konva.Layer;\n readonly overlayLayer: Konva.Layer;\n readonly positionLayer: Konva.Layer;\n\n readonly camera: Camera;\n readonly culling: CullingManager;\n readonly events: TypedEventEmitter<RendererEventMap>;\n\n private readonly state: MapState;\n private readonly container?: HTMLDivElement;\n private readonly positionLayerNode: MaterializingLayerNode;\n private readonly overlayLayerNode: MaterializingLayerNode;\n private gridLayerNode!: RecordingLayerNode;\n private topLabelLayerNode!: RecordingLayerNode;\n /** Snapped grid bounds last rendered onto gridLayerNode. */\n private gridCachedBounds: {left: number; right: number; top: number; bottom: number} | null = null;\n private sceneManager: SceneManager;\n private currentStyle: Style = identityStyle;\n\n readonly hitTester: HitTester = new HitTester();\n private lastHitShapes: Shape[] = [];\n\n // Shape → DrawEntry map rebuilt on each buildScene; used by applyClipping\n // to toggle DrawEntry.visible without knowing about CullEntry or Konva internals.\n private shapeToDrawEntry: Map<Shape, DrawEntry> = new Map();\n private sceneNode!: DrawCommandLayerNode;\n /** Lookup from a pipeline-emitted shape to its recording node; rebuilt per buildScene. */\n private shapeToGroup: Map<Shape, RecordingGroupNode> = new Map();\n\n private positionMarker?: RecordingGroupNode;\n private highlightShapes: Map<number, RecordingGroupNode> = new Map();\n private pathShapes: RecordingGroupNode[] = [];\n private currentRoomOverlay: RecordingGroupNode[] = [];\n private areaExitHitZones: AreaExitHitZone[] = [];\n private interactionHandler?: InteractionHandler;\n private origSetSize?: (w: number, h: number) => void;\n private cameraChangeHandler?: () => void;\n private destroyed = false;\n private _coordinateTransform: CoordFn = IDENTITY_TRANSFORM;\n private coordinateInverse: CoordFn = IDENTITY_TRANSFORM;\n\n get coordinateTransform(): CoordFn {\n return this._coordinateTransform;\n }\n private liveEffects: Map<string, LiveEffect> = new Map();\n private sceneOverlays: Map<string, SceneOverlay> = new Map();\n private sceneOverlayNodes: Map<string, RecordingGroupNode[]> = new Map();\n private viewportSubscribers: Set<() => void> = new Set();\n\n constructor(state: MapState, container?: HTMLDivElement) {\n this.state = state;\n this.container = container;\n\n if (container) {\n this.stage = new Konva.Stage({\n container,\n width: container.clientWidth,\n height: container.clientHeight,\n draggable: false,\n });\n container.style.backgroundColor = state.settings.backgroundColor;\n this.camera = new Camera(container.clientWidth, container.clientHeight);\n } else {\n this.stage = new Konva.Stage({width: 1, height: 1});\n this.camera = new Camera(1, 1);\n }\n\n this.gridLayer = new Konva.Layer({listening: false});\n this.stage.add(this.gridLayer);\n // linkLayer and roomLayer share one physical Konva.Layer to stay under\n // Konva's recommended layer count. Z-order between link exits and rooms\n // is preserved by insertion order within the shared RecordingLayerNode.\n const sceneLayer = new Konva.Layer({listening: false});\n this.linkLayer = sceneLayer;\n this.roomLayer = sceneLayer;\n this.stage.add(sceneLayer);\n this.positionLayer = new Konva.Layer({listening: false});\n this.stage.add(this.positionLayer);\n this.overlayLayer = new Konva.Layer({listening: false});\n this.stage.add(this.overlayLayer);\n this.topLabelLayer = new Konva.Layer({listening: false});\n this.stage.add(this.topLabelLayer);\n\n this.positionLayerNode = new MaterializingLayerNode(this.positionLayer);\n this.overlayLayerNode = new MaterializingLayerNode(this.overlayLayer);\n\n this.sceneNode = new DrawCommandLayerNode(sceneLayer);\n this.gridLayerNode = new RecordingLayerNode(this.gridLayer);\n this.topLabelLayerNode = new RecordingLayerNode(this.topLabelLayer);\n this.sceneManager = new SceneManager(this.camera, state.settings, state.mapReader);\n\n this.events = new TypedEventEmitter<RendererEventMap>(container);\n\n // linkLayer and roomLayer share sceneNode; one batchDraw covers both.\n this.culling = new CullingManager(state.settings, () => this.applyClipping());\n\n // Camera drives the stage\n this.cameraChangeHandler = () => this.applyViewportToStage();\n this.camera.on('change', this.cameraChangeHandler);\n\n if (container) {\n // Sync stage size when camera resizes\n this.origSetSize = this.camera.setSize.bind(this.camera);\n const origSetSize = this.origSetSize;\n this.camera.setSize = (w: number, h: number) => {\n origSetSize(w, h);\n this.stage.width(w);\n this.stage.height(h);\n };\n\n this.interactionHandler = new InteractionHandler(container, this.camera, state, {\n clientToMapPoint: (cx, cy) => this.camera.clientToMapPoint(cx, cy, container.getBoundingClientRect()),\n pickAtPoint: (x, y) => this.hitTester.pick(x, y),\n }, this.events);\n }\n\n this.applyStyleTransforms();\n this.subscribeToState(state);\n }\n\n setStyle(style: Style) {\n this.currentStyle = style;\n this.sceneNode = new DrawCommandLayerNode(this.linkLayer);\n this.gridLayerNode = new RecordingLayerNode(this.gridLayer);\n this.topLabelLayerNode = new RecordingLayerNode(this.topLabelLayer);\n this.gridCachedBounds = null;\n this.sceneManager.resetPipeline(this.state.mapReader);\n\n // Drop scene state pinned to the now-destroyed sceneNode/layers.\n this.shapeToDrawEntry = new Map();\n this.shapeToGroup.clear();\n this.areaExitHitZones = [];\n this.lastHitShapes = [];\n this.hitTester.clear();\n\n this.applyStyleTransforms();\n this.refresh();\n }\n\n /** Pull forward/inverse coord transforms from the active shape Style. */\n private applyStyleTransforms() {\n // Capture the style pointer locally: the arrows below outlive setStyle\n // calls (held by TerrainOverlay via updateViewport, by `oldInverse`\n // below, etc.), so they must not dereference `this.currentStyle` at\n // invocation time — that would resolve against whatever style was\n // installed *later*, which may not implement worldToScene/sceneToWorld.\n const style = this.currentStyle;\n const forward: CoordFn = style.worldToScene\n ? (x, y) => style.worldToScene!(x, y)\n : IDENTITY_TRANSFORM;\n const newInverse: CoordFn = style.sceneToWorld\n ? (x, y) => style.sceneToWorld!(x, y)\n : IDENTITY_TRANSFORM;\n const oldInverse = this.coordinateInverse;\n\n this._coordinateTransform = forward;\n this.coordinateInverse = newInverse;\n this.culling.setCoordinateTransform(forward);\n this.gridCachedBounds = null;\n if (this.lastHitShapes.length > 0) {\n this.hitTester.build(this.lastHitShapes, this.state.settings.roomSize, forward);\n }\n\n // Reposition camera so the same map point stays at screen center\n const scale = this.camera.getScale();\n const screenCX = this.camera.width / 2;\n const screenCY = this.camera.height / 2;\n const oldRX = (screenCX - this.camera.position.x) / scale;\n const oldRY = (screenCY - this.camera.position.y) / scale;\n const map = oldInverse(oldRX, oldRY);\n const nr = forward(map.x, map.y);\n\n this.camera.position = {\n x: screenCX - nr.x * scale,\n y: screenCY - nr.y * scale,\n };\n this.applyViewportToStage();\n }\n\n private styleContext(): StyleContext {\n return {\n scale: this.camera.getScale(),\n roomSize: this.state.settings.roomSize,\n };\n }\n\n private mapPoint(x: number, y: number): { x: number; y: number } {\n return this._coordinateTransform(x, y);\n }\n\n get exitRenderer() {\n return this.sceneManager.exitRenderer;\n }\n\n getDrawnExits(): readonly DrawnExitEntry[] {\n return this.sceneManager.drawnExits;\n }\n\n getDrawnSpecialExits(): readonly DrawnSpecialExitEntry[] {\n return this.sceneManager.drawnSpecialExits;\n }\n\n getDrawnStubs(): readonly DrawnStubEntry[] {\n return this.sceneManager.drawnStubs;\n }\n\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n\n // Remove all MapState event subscriptions\n this.state.events.removeAllListeners();\n\n // Destroy interaction handler (removes DOM listeners)\n this.interactionHandler?.destroy();\n\n // Stop overlay plugins\n for (const plugin of this.liveEffects.values()) plugin.destroy();\n this.liveEffects.clear();\n\n // Detach scene overlays so they can unsubscribe from events\n for (const overlay of this.sceneOverlays.values()) overlay.detach?.();\n this.sceneOverlays.clear();\n for (const nodes of this.sceneOverlayNodes.values()) {\n for (const node of nodes) node.destroy();\n }\n this.sceneOverlayNodes.clear();\n this.viewportSubscribers.clear();\n\n // Cancel any running camera animation\n this.camera.cancelAnimation();\n\n // Restore monkey-patched setSize\n if (this.origSetSize) {\n this.camera.setSize = this.origSetSize;\n }\n\n // Disconnect camera from stage\n if (this.cameraChangeHandler) {\n this.camera.off('change', this.cameraChangeHandler);\n this.cameraChangeHandler = undefined;\n }\n\n // Destroy all Konva nodes and the stage\n this.clearOverlayShapes();\n this.clearCurrentRoomOverlay();\n if (this.positionMarker) {\n this.positionMarker.destroy();\n this.positionMarker = undefined;\n }\n this.stage.destroy();\n\n // Clear renderer events\n this.events.removeAllListeners();\n }\n\n updateBackground() {\n if (this.container) {\n this.container.style.backgroundColor = this.state.settings.backgroundColor;\n }\n }\n\n exportCanvas(options?: { pixelRatio?: number }): ExportCanvas | undefined {\n if (this.state.currentArea === undefined || this.state.currentZIndex === undefined) return;\n const stageCanvas = this.stage.toCanvas({ pixelRatio: options?.pixelRatio ?? 1 });\n const composite = document.createElement('canvas');\n composite.width = stageCanvas.width;\n composite.height = stageCanvas.height;\n const ctx = composite.getContext('2d')!;\n ctx.fillStyle = this.state.settings.backgroundColor;\n ctx.fillRect(0, 0, composite.width, composite.height);\n ctx.drawImage(stageCanvas, 0, 0);\n return composite;\n }\n\n // --- Camera → Stage (one-way, called from the camera's change event) ---\n\n private applyViewportToStage() {\n const scale = this.camera.getScale();\n this.stage.scale({x: scale, y: scale});\n this.stage.position(this.camera.position);\n this.stage.batchDraw();\n const vpBounds = this.camera.getViewportBounds();\n this.refreshGrid(vpBounds);\n this.culling.scheduleCulling();\n this.events.emit('pan', vpBounds);\n for (const cb of this.viewportSubscribers) cb();\n for (const plugin of this.liveEffects.values()) {\n plugin.updateViewport(vpBounds, scale, this.coordinateTransform);\n }\n }\n\n // --- State event handlers ---\n\n refresh() {\n const {currentAreaInstance, currentZIndex, positionRoomId} = this.state;\n if (!currentAreaInstance || currentZIndex === undefined) return;\n const plane = currentAreaInstance.getPlane(currentZIndex);\n if (!plane) {\n this.shapeToDrawEntry = new Map();\n this.sceneManager.reset();\n this.hitTester.clear();\n this.lastHitShapes = [];\n this.gridLayer.destroyChildren();\n this.linkLayer.destroyChildren();\n this.positionLayer.destroyChildren();\n this.positionMarker = undefined;\n this.clearOverlayShapes();\n this.currentRoomOverlay = [];\n this.stage.batchDraw();\n return;\n }\n this.updateBackground();\n this.buildScene(currentAreaInstance, plane, currentZIndex);\n this.onSceneBuilt();\n this.syncHighlights();\n this.syncPaths();\n if (positionRoomId !== undefined) {\n this.onPositionChanged(positionRoomId, false, false);\n }\n for (const [id, overlay] of this.sceneOverlays) {\n this.renderSceneOverlay(id, overlay);\n }\n }\n\n addLiveEffect(id: string, effect: LiveEffect) {\n this.removeLiveEffect(id);\n effect.attach(this.overlayLayer);\n this.liveEffects.set(id, effect);\n effect.updateViewport(this.camera.getViewportBounds(), this.camera.getScale(), this.coordinateTransform);\n }\n\n removeLiveEffect(id: string) {\n const existing = this.liveEffects.get(id);\n if (existing) {\n existing.destroy();\n this.liveEffects.delete(id);\n }\n }\n\n addSceneOverlay(id: string, overlay: SceneOverlay) {\n const existing = this.sceneOverlays.get(id);\n if (existing) {\n existing.detach?.();\n this.clearSceneOverlayNodes(id);\n }\n this.sceneOverlays.set(id, overlay);\n overlay.attach?.(this.createOverlayContext(id, overlay));\n this.renderSceneOverlay(id, overlay);\n }\n\n removeSceneOverlay(id: string) {\n const overlay = this.sceneOverlays.get(id);\n if (!overlay) return;\n overlay.detach?.();\n this.sceneOverlays.delete(id);\n this.clearSceneOverlayNodes(id);\n this.overlayLayer.batchDraw();\n }\n\n /** Iterable of scene overlays — used by exporters to apply them over static outputs. */\n getSceneOverlays(): Iterable<SceneOverlay> {\n return this.sceneOverlays.values();\n }\n\n private createOverlayContext(id: string, overlay: SceneOverlay): SceneOverlayContext {\n return {\n state: this.state,\n onViewportChange: (cb) => {\n this.viewportSubscribers.add(cb);\n return () => this.viewportSubscribers.delete(cb);\n },\n invalidate: () => {\n // Skip if the overlay was removed/replaced since attach.\n if (this.sceneOverlays.get(id) !== overlay) return;\n this.renderSceneOverlay(id, overlay);\n },\n };\n }\n\n private renderSceneOverlay(id: string, overlay: SceneOverlay) {\n this.clearSceneOverlayNodes(id);\n const bounds = this.camera.getViewportBounds();\n const out = overlay.render(this.state, bounds);\n if (out) {\n const shapes = Array.isArray(out) ? out : [out];\n const stored: RecordingGroupNode[] = [];\n for (const shape of shapes) {\n const node = this.addStyledShape(shape, this.overlayLayerNode);\n if (node) stored.push(node);\n }\n this.sceneOverlayNodes.set(id, stored);\n }\n this.overlayLayer.batchDraw();\n }\n\n private clearSceneOverlayNodes(id: string) {\n const nodes = this.sceneOverlayNodes.get(id);\n if (!nodes) return;\n for (const node of nodes) node.destroy();\n this.sceneOverlayNodes.delete(id);\n }\n\n private subscribeToState(state: MapState) {\n state.events.on('area', () => {\n this.refresh();\n });\n\n state.events.on('position', ({roomId, center, areaChanged}) => {\n this.onPositionChanged(roomId, center, areaChanged);\n });\n\n state.events.on('center', ({roomId, instant}) => {\n const room = state.mapReader.getRoom(roomId);\n if (room) {\n const p = this.mapPoint(room.x, room.y);\n this.camera.panToMapPointAnimated(p.x, p.y,\n instant || this.state.settings.instantMapMove);\n }\n });\n\n state.events.on('highlight', ({roomId, color}) => {\n this.syncHighlight(roomId, color);\n });\n\n state.events.on('path', () => {\n this.syncPaths();\n });\n\n state.events.on('clear', () => {\n this.syncHighlights();\n });\n\n state.events.on('lens', () => {\n this.refresh();\n });\n }\n\n // --- Scene lifecycle ---\n\n private buildScene(area: IArea, plane: IPlane, zIndex: number): void {\n this.positionLayer.destroyChildren();\n this.positionMarker = undefined;\n this.clearOverlayShapes();\n this.currentRoomOverlay = [];\n\n // Reset the scene-driving layer nodes; sceneManager.rebuild only\n // produces shapes now, so this renderer is responsible for walking\n // them onto the Konva layers as RecordingGroupNodes.\n this.sceneNode.destroyChildren();\n this.gridLayerNode.destroyChildren();\n this.gridCachedBounds = null;\n this.topLabelLayerNode.destroyChildren();\n\n const result = this.sceneManager.rebuild(area, plane, zIndex, this.state.lens);\n\n // Track ORIGINAL shape (pre-style) → recording node so onSceneBuilt\n // can find each room/exit's DrawEntry inside `sceneNode` for culling.\n // When style.transform fans a shape into multiple, we record the first\n // resulting node against the original — cull toggles that one.\n this.shapeToGroup = new Map();\n\n // Seed the grid for the first paint; subsequent updates are driven by\n // refreshGrid() on every camera change.\n this.refreshGrid(this.camera.getViewportBounds());\n\n for (const shape of result.sceneShapes.link) {\n const node = this.addStyledShape(shape, this.sceneNode);\n if (node) this.shapeToGroup.set(shape, node);\n }\n for (const shape of result.sceneShapes.room) {\n const node = this.addStyledShape(shape, this.sceneNode);\n if (node) this.shapeToGroup.set(shape, node);\n }\n for (const shape of result.sceneShapes.topLabel) {\n this.addStyledShape(shape, this.topLabelLayerNode);\n }\n }\n\n /** Wrap a non-group shape and walk to a recording node. */\n private shapeToRecordingNode(shape: Shape): RecordingGroupNode {\n const groupShape: GroupShape = shape.type === \"group\"\n ? shape\n : {type: \"group\", x: 0, y: 0, children: [shape], layer: shape.layer};\n return shapeToRecording(groupShape);\n }\n\n /**\n * Run the active {@link Style} over `shape`, walk every result through\n * {@link shapeToRecording}, and add it to `layerNode`. Returns the first\n * emitted node (used as the cull-tracking / destroy handle).\n */\n private addStyledShape(\n shape: Shape,\n layerNode: {addNode(node: RecordingGroupNode): void},\n ): RecordingGroupNode | undefined {\n const styled = this.currentStyle === identityStyle\n ? [shape]\n : applyStyleToShapes([shape], this.currentStyle, this.styleContext());\n let first: RecordingGroupNode | undefined;\n for (const s of styled) {\n const node = this.shapeToRecordingNode(s);\n layerNode.addNode(node);\n if (!first) first = node;\n }\n return first;\n }\n\n /**\n * Recompute and re-publish the grid lines for the given viewport.\n * Cached against snapped grid bounds so per-frame redraws are cheap when\n * the viewport hasn't crossed a grid step.\n */\n private refreshGrid(bounds: ViewportBounds): void {\n const settings = this.state.settings;\n if (!settings.gridEnabled) {\n if (this.gridCachedBounds !== null) {\n this.gridLayerNode.destroyChildren();\n this.gridLayerNode.batchDraw();\n this.gridCachedBounds = null;\n }\n return;\n }\n\n const inv = this.coordinateInverse;\n const c1 = inv(bounds.minX, bounds.minY);\n const c2 = inv(bounds.maxX, bounds.minY);\n const c3 = inv(bounds.maxX, bounds.maxY);\n const c4 = inv(bounds.minX, bounds.maxY);\n const cartMinX = Math.min(c1.x, c2.x, c3.x, c4.x);\n const cartMaxX = Math.max(c1.x, c2.x, c3.x, c4.x);\n const cartMinY = Math.min(c1.y, c2.y, c3.y, c4.y);\n const cartMaxY = Math.max(c1.y, c2.y, c3.y, c4.y);\n const buffer = settings.gridSize * 2;\n const left = Math.floor((cartMinX - buffer) / settings.gridSize) * settings.gridSize;\n const right = Math.ceil((cartMaxX + buffer) / settings.gridSize) * settings.gridSize;\n const top = Math.floor((cartMinY - buffer) / settings.gridSize) * settings.gridSize;\n const bottom = Math.ceil((cartMaxY + buffer) / settings.gridSize) * settings.gridSize;\n\n const cached = this.gridCachedBounds;\n if (cached && cached.left === left && cached.right === right && cached.top === top && cached.bottom === bottom) {\n return;\n }\n\n this.gridLayerNode.destroyChildren();\n const lines = layoutGrid(bounds, settings, {inverseTransform: this.coordinateInverse});\n for (const line of lines) {\n this.addStyledShape(line, this.gridLayerNode);\n }\n this.gridCachedBounds = {left, right, top, bottom};\n this.gridLayerNode.batchDraw();\n }\n\n private onSceneBuilt() {\n this.areaExitHitZones = this.sceneManager.areaExitHitZones as AreaExitHitZone[];\n this.lastHitShapes = this.sceneManager.hitShapes as Shape[];\n this.hitTester.build(this.lastHitShapes, this.state.settings.roomSize, this._coordinateTransform);\n\n const scale = this.camera.getScale();\n this.stage.scale({x: scale, y: scale});\n\n // Build shape → DrawEntry map for all scene shapes (link + room layers).\n // TopLabel shapes live on a separate RecordingLayerNode and are always visible.\n this.shapeToDrawEntry = new Map();\n for (const [shape, node] of this.shapeToGroup) {\n const drawEntry = this.sceneNode.getEntry(node);\n if (drawEntry) this.shapeToDrawEntry.set(shape, drawEntry);\n }\n this.applyClipping();\n this.stage.batchDraw();\n }\n\n private applyClipping(): void {\n if (!this.sceneManager.lastResult) return;\n\n const visibilityMap = this.sceneManager.cullInteractive(this._coordinateTransform);\n let changed = false;\n for (const [shape, entry] of this.shapeToDrawEntry) {\n const vis = visibilityMap.get(shape) ?? true;\n if (entry.visible !== vis) {\n entry.visible = vis;\n changed = true;\n }\n }\n if (changed) this.sceneNode.batchDraw();\n }\n\n // --- Position & overlay ---\n\n private onPositionChanged(roomId: number | undefined, center: boolean, instant: boolean) {\n if (roomId === undefined) {\n if (this.positionMarker) {\n this.positionMarker.destroy();\n this.positionMarker = undefined;\n }\n this.positionLayerNode.batchDraw();\n this.clearCurrentRoomOverlay();\n this.overlayLayerNode.batchDraw();\n return;\n }\n\n const room = this.state.mapReader.getRoom(roomId);\n if (!room) return;\n\n if (center) {\n const p = this.mapPoint(room.x, room.y);\n this.camera.panToMapPointAnimated(p.x, p.y,\n instant || this.state.settings.instantMapMove);\n }\n\n this.updateCurrentRoomOverlay(room);\n this.applyPositionMarker(room);\n }\n\n private applyPositionMarker(room: MapData.Room) {\n if (this.positionMarker) {\n this.positionMarker.destroy();\n this.positionMarker = undefined;\n }\n // Mirrors the contract in MapState.getOverlaysForArea: the position\n // marker is only visible when the player room is on the displayed area/z.\n if (room.area !== this.state.currentArea || room.z !== this.state.currentZIndex) {\n this.positionLayerNode.batchDraw();\n return;\n }\n const data = computePositionMarker(room, this.state.settings);\n this.positionMarker = this.addStyledShape(positionMarkerToShape(data), this.positionLayerNode);\n }\n\n private clearCurrentRoomOverlay() {\n this.currentRoomOverlay.forEach(node => node.destroy());\n this.currentRoomOverlay = [];\n this.positionLayerNode.batchDraw();\n }\n\n private updateCurrentRoomOverlay(room: MapData.Room) {\n this.clearCurrentRoomOverlay();\n\n if (room.area !== this.state.currentArea || room.z !== this.state.currentZIndex) {\n this.positionLayerNode.batchDraw();\n return;\n }\n\n const settings = this.state.settings;\n\n if (!settings.highlightCurrentRoom) {\n if (this.positionMarker) this.positionMarker.moveToTop();\n this.positionLayerNode.batchDraw();\n return;\n }\n\n const roomsToRedraw = new Map<number, MapData.Room>();\n roomsToRedraw.set(room.id, room);\n\n const preRoomShapes: Shape[] = [];\n const exitRenderer = this.sceneManager.exitRenderer;\n const lens = this.state.lens;\n\n // Link exits for this room → rendered as ExitDrawData through DrawingBackend\n if (this.state.currentAreaInstance && this.state.currentZIndex !== undefined) {\n const exits = this.state.currentAreaInstance\n .getLinkExits(this.state.currentZIndex)\n .filter(exit => exit.a === room.id || exit.b === room.id);\n exits.forEach(exit => {\n const data = exitRenderer.renderDataWithColor(exit, currentRoomColor, this.state.currentZIndex!);\n if (data) {\n preRoomShapes.push(this.sceneManager.buildExitShape(data));\n }\n });\n }\n\n // Special exits\n for (const se of computeSpecialExits(room, settings, currentRoomColor)) {\n preRoomShapes.push(specialExitToShape(se, room.id));\n }\n\n // Stubs\n for (const stub of computeStubs(room, settings, currentRoomColor)) {\n preRoomShapes.push(stubToShape(stub));\n }\n\n [...Object.values(room.exits), ...Object.values(room.specialExits)].forEach(id => {\n const otherRoom = this.state.mapReader.getRoom(id);\n if (\n otherRoom &&\n otherRoom.area === this.state.currentArea &&\n otherRoom.z === this.state.currentZIndex &&\n lens.isVisible(otherRoom)\n ) {\n roomsToRedraw.set(id, otherRoom);\n }\n });\n\n preRoomShapes.forEach(shape => {\n const node = this.addStyledShape(shape, this.positionLayerNode);\n if (node) this.currentRoomOverlay.push(node);\n });\n\n roomsToRedraw.forEach((roomToRedraw, id) => {\n const isCurrent = id === room.id;\n const overlayShape = layoutRoom(\n roomToRedraw,\n this.state.mapReader,\n settings,\n {\n strokeOverride: isCurrent ? currentRoomColor : settings.lineColor,\n flatPipeline: true,\n },\n );\n overlayShape.children.push(...layoutInnerExits(roomToRedraw, this.state.mapReader, settings));\n const node = this.addStyledShape(overlayShape, this.positionLayerNode);\n if (node) this.currentRoomOverlay.push(node);\n });\n\n if (this.positionMarker) {\n this.positionMarker.moveToTop();\n }\n\n this.positionLayerNode.batchDraw();\n }\n\n // --- Highlight & path sync ---\n\n syncHighlight(roomId: number, color: string | undefined) {\n const existing = this.highlightShapes.get(roomId);\n if (existing) {\n existing.destroy();\n this.highlightShapes.delete(roomId);\n }\n if (color !== undefined) {\n const room = this.state.mapReader.getRoom(roomId);\n if (room && room.area === this.state.currentArea && room.z === this.state.currentZIndex) {\n const data = computeHighlight(room, color, this.state.settings);\n const node = this.addStyledShape(highlightToShape(data), this.overlayLayerNode);\n if (node) this.highlightShapes.set(roomId, node);\n }\n }\n this.overlayLayerNode.batchDraw();\n }\n\n syncHighlights() {\n for (const node of this.highlightShapes.values()) node.destroy();\n this.highlightShapes.clear();\n\n for (const [roomId, entry] of this.state.highlights) {\n if (entry.area !== this.state.currentArea || entry.z !== this.state.currentZIndex) continue;\n const room = this.state.mapReader.getRoom(roomId);\n if (!room) continue;\n const data = computeHighlight(room, entry.color, this.state.settings);\n const node = this.addStyledShape(highlightToShape(data), this.overlayLayerNode);\n if (node) this.highlightShapes.set(roomId, node);\n }\n this.overlayLayerNode.batchDraw();\n }\n\n syncPaths() {\n this.clearPathShapes();\n const {currentArea, currentZIndex} = this.state;\n if (currentArea === undefined || currentZIndex === undefined) return;\n\n for (const path of this.state.paths) {\n const data = computePathOverlay(\n this.state.mapReader, this.state.settings,\n path.locations, path.color,\n currentArea, currentZIndex,\n );\n for (const s of pathToShapes(data)) {\n const node = this.addStyledShape(s, this.overlayLayerNode);\n if (node) this.pathShapes.push(node);\n }\n }\n this.overlayLayerNode.batchDraw();\n }\n\n // --- Private helpers ---\n\n private clearOverlayShapes() {\n for (const node of this.highlightShapes.values()) node.destroy();\n this.highlightShapes.clear();\n this.clearPathShapes();\n }\n\n private clearPathShapes() {\n for (const shape of this.pathShapes) {\n shape.destroy();\n }\n this.pathShapes = [];\n }\n}\n","/**\n * Shared colour-parsing + paint-rewriting helpers used by the shape-based\n * Style implementations (Parchment, Blueprint, Neon).\n *\n * The old BaseStyle decorator versions in `../ParchmentStyle.ts`,\n * `../BlueprintStyle.ts`, and `../NeonStyle.ts` keep their own copies for\n * now; they're deleted in step 11.\n */\n\nexport interface ParsedRgb {\n r: number;\n g: number;\n b: number;\n /** 0..1 alpha — defaults to 1 when not present. */\n a: number;\n}\n\n/**\n * Parse a colour string in `rgb(r, g, b)`, `rgba(r, g, b, a)`, or\n * `#rrggbb` form. Returns `null` for anything else (named colours,\n * `hsl(...)`, etc.) so callers can fall back to a default.\n */\nexport function parseRgb(color: string): ParsedRgb | null {\n const rgbaMatch = color.match(/rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)(?:\\s*,\\s*([\\d.]+))?\\s*\\)/);\n if (rgbaMatch) {\n return {\n r: parseInt(rgbaMatch[1]),\n g: parseInt(rgbaMatch[2]),\n b: parseInt(rgbaMatch[3]),\n a: rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1,\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 a: 1,\n };\n }\n return null;\n}\n\n/** Format an `rgb(r, g, b)` or `rgba(r, g, b, a)` string. */\nexport function formatRgb(r: number, g: number, b: number, a = 1): string {\n if (a < 1) return `rgba(${r}, ${g}, ${b}, ${a})`;\n return `rgb(${r}, ${g}, ${b})`;\n}\n\n/** Perceived luminance in [0, 1] using ITU-R BT.601 weights. */\nexport function luminance(c: ParsedRgb): number {\n return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) / 255;\n}\n","import type {Shape, Paint} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {formatRgb, luminance, parseRgb} from \"./paintMap\";\n\n/** Dark brown ink used for strokes and outlines. */\nconst INK = \"#4a3728\";\n/** Very dark brown used for text. */\nconst INK_TEXT = \"#3b2a1a\";\n\n// Parchment luminance range endpoints (dark brown → light parchment).\nconst DARK_R = 139, DARK_G = 115, DARK_B = 85;\nconst LIGHT_R = 244, LIGHT_G = 228, LIGHT_B = 193;\n\n/**\n * Map a colour to the parchment palette based on its perceived luminance.\n * Dark rooms become dark warm brown, light rooms become light parchment.\n */\nfunction toParchment(color: string): string {\n const c = parseRgb(color);\n if (!c) return formatRgb(LIGHT_R, LIGHT_G, LIGHT_B);\n const lum = luminance(c);\n return formatRgb(\n Math.round(DARK_R + (LIGHT_R - DARK_R) * lum),\n Math.round(DARK_G + (LIGHT_G - DARK_G) * lum),\n Math.round(DARK_B + (LIGHT_B - DARK_B) * lum),\n c.a,\n );\n}\n\n/** Convert a colour to dark ink, preserving any alpha channel. */\nfunction toInk(color: string): string {\n const c = parseRgb(color);\n if (!c || c.a >= 1) return INK;\n const ink = parseRgb(INK)!;\n return formatRgb(ink.r, ink.g, ink.b, c.a);\n}\n\n/** Apply paint rewrites to a single Paint record. */\nfunction rewriteFillStroke(paint: Paint): Paint {\n return {\n ...paint,\n fill: paint.fill ? toParchment(paint.fill) : paint.fill,\n stroke: paint.stroke ? toInk(paint.stroke) : paint.stroke,\n };\n}\n\n/**\n * Warm sepia / old-parchment palette as a {@link Style}.\n *\n * - Fills are mapped through a luminance-based parchment gradient\n * (dark brown → light parchment) so different environment colours stay\n * visually distinct while keeping the aged-paper look.\n * - Strokes become dark ink brown.\n * - Text uses a very dark brown for legibility.\n * - Images and groups pass through unchanged (the caller walks group\n * children separately).\n */\nexport const parchmentShapeStyle: Style = {\n transform(shape: Shape): Shape {\n switch (shape.type) {\n case \"rect\":\n case \"circle\":\n case \"polygon\":\n return {...shape, paint: rewriteFillStroke(shape.paint)};\n case \"line\":\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: toInk(shape.paint.stroke)}\n : shape.paint,\n };\n case \"text\":\n return {...shape, fill: INK_TEXT};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","import type {Shape, Paint} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {formatRgb, luminance, parseRgb} from \"./paintMap\";\n\n/** Light cyan used for strokes and lines. */\nconst LINE_COLOR = \"#c0deff\";\n/** Near-white blue used for text. */\nconst TEXT_COLOR = \"#e0f0ff\";\n\n// Blueprint luminance range endpoints (deep blue → lighter blue).\nconst DARK_R = 20, DARK_G = 40, DARK_B = 80;\nconst LIGHT_R = 60, LIGHT_G = 100, LIGHT_B = 160;\n\n/**\n * Map a colour to the blueprint palette based on its perceived luminance.\n * Dark rooms become deep blueprint blue, light rooms lighter blueprint blue.\n */\nfunction toBlueprint(color: string): string {\n const c = parseRgb(color);\n if (!c) return formatRgb(LIGHT_R, LIGHT_G, LIGHT_B);\n const lum = luminance(c);\n return formatRgb(\n Math.round(DARK_R + (LIGHT_R - DARK_R) * lum),\n Math.round(DARK_G + (LIGHT_G - DARK_G) * lum),\n Math.round(DARK_B + (LIGHT_B - DARK_B) * lum),\n c.a,\n );\n}\n\n/** Convert a colour to the blueprint line colour, preserving any alpha. */\nfunction toLine(color: string): string {\n const c = parseRgb(color);\n if (!c || c.a >= 1) return LINE_COLOR;\n const line = parseRgb(LINE_COLOR)!;\n return formatRgb(line.r, line.g, line.b, c.a);\n}\n\nfunction rewriteFillStroke(paint: Paint): Paint {\n return {\n ...paint,\n fill: paint.fill ? toBlueprint(paint.fill) : paint.fill,\n stroke: paint.stroke ? toLine(paint.stroke) : paint.stroke,\n };\n}\n\n/**\n * Technical blueprint aesthetic as a {@link Style} — white lines on deep blue.\n *\n * - Fills are mapped through a luminance-based blue gradient (deep blue →\n * lighter blue) so environment differences stay visible.\n * - Strokes become light cyan-white.\n * - Text uses near-white blue for legibility.\n * - Images and groups pass through unchanged.\n */\nexport const blueprintShapeStyle: Style = {\n transform(shape: Shape): Shape {\n switch (shape.type) {\n case \"rect\":\n case \"circle\":\n case \"polygon\":\n return {...shape, paint: rewriteFillStroke(shape.paint)};\n case \"line\":\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: toLine(shape.paint.stroke)}\n : shape.paint,\n };\n case \"text\":\n return {...shape, fill: TEXT_COLOR};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","import type {Shape, Paint, RectShape, CircleShape, LineShape} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {parseRgb} from \"./paintMap\";\n\n/** Bright cyan-green used for text. */\nconst TEXT_COLOR = \"#00ffd0\";\n\n/** Default glow alpha. */\nconst GLOW_ALPHA = 0.3;\n\n/** Glow stroke width multiplier (relative to the original stroke). */\nconst GLOW_WIDTH_MULT = 3;\n\nfunction rgbToHsl(r: number, g: number, b: number): [number, number, number] {\n r /= 255; g /= 255; b /= 255;\n const max = Math.max(r, g, b), min = Math.min(r, g, b);\n const l = (max + min) / 2;\n if (max === min) return [0, 0, l];\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h: number;\n if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n else if (max === g) h = ((b - r) / d + 2) / 6;\n else h = ((r - g) / d + 4) / 6;\n return [h * 360, s, l];\n}\n\nfunction hslToRgbString(h: number, s: number, l: number, a = 1): string {\n h = ((h % 360) + 360) % 360;\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n let r: number, g: number, b: number;\n if (h < 60) { r = c; g = x; b = 0; }\n else if (h < 120) { r = x; g = c; b = 0; }\n else if (h < 180) { r = 0; g = c; b = x; }\n else if (h < 240) { r = 0; g = x; b = c; }\n else if (h < 300) { r = x; g = 0; b = c; }\n else { r = c; g = 0; b = x; }\n const ri = Math.round((r + m) * 255);\n const gi = Math.round((g + m) * 255);\n const bi = Math.round((b + m) * 255);\n if (a < 1) return `rgba(${ri}, ${gi}, ${bi}, ${a})`;\n return `rgb(${ri}, ${gi}, ${bi})`;\n}\n\nfunction neonHueAlpha(color: string): {h: number; a: number} {\n const c = parseRgb(color);\n if (!c) return {h: 150, a: 1};\n const [h] = rgbToHsl(c.r, c.g, c.b);\n return {h, a: c.a};\n}\n\nfunction toNeonStroke(color: string): string {\n const {h, a} = neonHueAlpha(color);\n return hslToRgbString(h, 0.95, 0.65, a);\n}\n\nfunction toNeonFill(color: string): string {\n const c = parseRgb(color);\n if (!c) return \"rgb(8, 12, 10)\";\n const [h] = rgbToHsl(c.r, c.g, c.b);\n return hslToRgbString(h, 0.4, 0.1, c.a);\n}\n\nfunction toGlowStroke(color: string): string {\n const {h, a} = neonHueAlpha(color);\n return hslToRgbString(h, 0.95, 0.65, Math.min(a, GLOW_ALPHA));\n}\n\nfunction neonPaint(paint: Paint): Paint {\n return {\n ...paint,\n fill: paint.fill ? toNeonFill(paint.fill) : paint.fill,\n stroke: paint.stroke ? toNeonStroke(paint.stroke) : paint.stroke,\n };\n}\n\nfunction glowPaint(paint: Paint, withAlpha = false): Paint {\n if (!paint.stroke) return paint;\n const glowWidth = (paint.strokeWidth ?? 1) * GLOW_WIDTH_MULT;\n return {\n stroke: toGlowStroke(paint.stroke),\n strokeWidth: glowWidth,\n dash: paint.dash,\n dashEnabled: paint.dashEnabled,\n alpha: withAlpha ? GLOW_ALPHA : paint.alpha,\n };\n}\n\n/**\n * Cyberpunk / neon aesthetic as a {@link Style} — glowing coloured outlines\n * on a dark background.\n *\n * - Fills become very dark with a subtle tint of the original hue.\n * - Strokes become bright neon (hue-preserved, saturation/lightness boosted).\n * - A glow effect is added for stroked rect / circle / line shapes by emitting\n * an extra wider translucent shape **before** the main one, so callers see\n * `[glow, main]` (the order in which they should be rendered).\n * - Polygons get the neon repaint without a glow pass — same as the legacy\n * decorator, since polygons in the pipeline are inner-exit triangles and\n * exit arrowheads where a glow halo reads as visual noise.\n * - Text uses bright cyan-green.\n * - Images and groups pass through unchanged.\n */\nexport const neonShapeStyle: Style = {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n const main: RectShape = {...shape, paint: neonPaint(shape.paint)};\n if (!shape.paint.stroke) return main;\n const glow: RectShape = {\n ...shape,\n paint: {...glowPaint(shape.paint), fill: undefined},\n };\n return [glow, main];\n }\n case \"circle\": {\n const main: CircleShape = {...shape, paint: neonPaint(shape.paint)};\n if (!shape.paint.stroke) return main;\n const glow: CircleShape = {\n ...shape,\n paint: {...glowPaint(shape.paint), fill: undefined},\n };\n return [glow, main];\n }\n case \"line\": {\n const main: LineShape = {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: toNeonStroke(shape.paint.stroke)}\n : shape.paint,\n };\n if (!shape.paint.stroke) return main;\n const glow: LineShape = {...shape, paint: glowPaint(shape.paint, true)};\n return [glow, main];\n }\n case \"polygon\":\n return {...shape, paint: neonPaint(shape.paint)};\n case \"text\":\n return {...shape, fill: TEXT_COLOR};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","/**\n * Hand-drawn wobble math used by the shape-based SketchyStyle.\n *\n * Identical algorithms to `../SketchyStyle.ts` so cross-style snapshots\n * stay byte-identical between old and new pipelines. When step 11 deletes\n * the legacy decorator, this module becomes the single source of truth.\n */\n\n/** Simple seeded LCG. Returns values in `[0, 1)`. */\nexport function createRng(seed: number): () => number {\n let s = seed | 0 || 1;\n return () => {\n s = (s * 1664525 + 1013904223) | 0;\n return (s >>> 0) / 4294967296;\n };\n}\n\n/** Hash a set of numeric values into a stable integer seed. */\nexport function hashCoords(...values: number[]): number {\n let h = 0x9e3779b9;\n for (const v of values) {\n h ^= ((v * 1000) | 0) + 0x9e3779b9 + (h << 6) + (h >> 2);\n }\n return h;\n}\n\n/**\n * Subdivide a straight segment into several sub-segments with slight\n * perpendicular displacement, producing a hand-drawn wobble.\n */\nexport function wobbleSegment(\n x1: number, y1: number, x2: number, y2: number,\n jitter: number, rng: () => number,\n): number[] {\n const dx = x2 - x1;\n const dy = y2 - y1;\n const len = Math.sqrt(dx * dx + dy * dy);\n if (len < 0.001) return [x1, y1, x2, y2];\n\n const nx = -dy / len;\n const ny = dx / len;\n\n const subdivisions = Math.max(2, Math.min(6, Math.ceil(len / 0.15)));\n const points: number[] = [\n x1 + nx * (rng() - 0.5) * jitter * 0.3,\n y1 + ny * (rng() - 0.5) * jitter * 0.3,\n ];\n\n for (let i = 1; i < subdivisions; i++) {\n const t = i / subdivisions;\n const px = x1 + dx * t;\n const py = y1 + dy * t;\n const offset = (rng() - 0.5) * 2 * jitter;\n points.push(px + nx * offset, py + ny * offset);\n }\n\n points.push(\n x2 + nx * (rng() - 0.5) * jitter * 0.3,\n y2 + ny * (rng() - 0.5) * jitter * 0.3,\n );\n return points;\n}\n\n/** Wobble an open polyline. */\nexport function wobblePolyline(points: number[], jitter: number, rng: () => number): number[] {\n if (points.length < 4) return points;\n const result: number[] = [];\n for (let i = 0; i < points.length - 2; i += 2) {\n const seg = wobbleSegment(\n points[i], points[i + 1],\n points[i + 2], points[i + 3],\n jitter, rng,\n );\n if (i === 0) {\n result.push(...seg);\n } else {\n for (let j = 2; j < seg.length; j++) result.push(seg[j]);\n }\n }\n return result;\n}\n\n/** Convert a rectangle to a closed wobbly polygon. */\nexport function wobbleRect(\n x: number, y: number, w: number, h: number,\n jitter: number, rng: () => number,\n): number[] {\n const corners = [x, y, x + w, y, x + w, y + h, x, y + h];\n const result: number[] = [];\n for (let i = 0; i < 4; i++) {\n const x1 = corners[i * 2], y1 = corners[i * 2 + 1];\n const ni = (i + 1) % 4;\n const x2 = corners[ni * 2], y2 = corners[ni * 2 + 1];\n const seg = wobbleSegment(x1, y1, x2, y2, jitter, rng);\n for (let j = 0; j < seg.length - 2; j++) result.push(seg[j]);\n }\n return result;\n}\n\n/** Convert a circle to a wobbly polygon with slight radius variation. */\nexport function wobbleCircle(\n cx: number, cy: number, radius: number,\n jitter: number, rng: () => number,\n): number[] {\n const segments = 24;\n const points: number[] = [];\n for (let i = 0; i < segments; i++) {\n const angle = (i / segments) * Math.PI * 2;\n const rJitter = radius + (rng() - 0.5) * 2 * jitter;\n points.push(cx + Math.cos(angle) * rJitter, cy + Math.sin(angle) * rJitter);\n }\n return points;\n}\n\n/** Wobble a closed polygon by subdividing each edge. */\nexport function wobblePolygonEdges(vertices: number[], jitter: number, rng: () => number): number[] {\n const n = vertices.length / 2;\n if (n < 2) return vertices;\n const result: number[] = [];\n for (let i = 0; i < n; i++) {\n const x1 = vertices[i * 2], y1 = vertices[i * 2 + 1];\n const ni = (i + 1) % n;\n const x2 = vertices[ni * 2], y2 = vertices[ni * 2 + 1];\n const seg = wobbleSegment(x1, y1, x2, y2, jitter, rng);\n for (let j = 0; j < seg.length - 2; j++) result.push(seg[j]);\n }\n return result;\n}\n","import type {Shape, PolygonShape, Paint} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {\n createRng,\n hashCoords,\n wobbleCircle,\n wobblePolygonEdges,\n wobblePolyline,\n wobbleRect,\n} from \"./wobble\";\n\nexport interface SketchyOptions {\n /** Jitter amount in map units (e.g. 0.015). */\n jitter: number;\n /** Pencil colour for all strokes/fills (e.g. `#444444`). */\n color: string;\n}\n\n/** Apply pencil colour to a stroke, preserving any alpha from the original. */\nfunction applyPencilWithAlpha(pencil: string, original: string | undefined): string {\n if (!original) return pencil;\n const alphaMatch = original.match(/^rgba\\(.+,\\s*([\\d.]+)\\s*\\)$/);\n if (!alphaMatch) return pencil;\n const alpha = parseFloat(alphaMatch[1]);\n if (alpha >= 1) return pencil;\n const r = parseInt(pencil.slice(1, 3), 16);\n const g = parseInt(pencil.slice(3, 5), 16);\n const b = parseInt(pencil.slice(5, 7), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n/**\n * Hand-drawn pencil wobble as a {@link Style}. Subdivides edges into wobbly\n * segments with seeded perpendicular jitter, so re-renders produce the same\n * pattern.\n *\n * - Rectangles → wobbly polygons (paper-white fill on parchment, pencil stroke).\n * Rounded-corner rectangles render as a paper-white fill rect plus a wobbly\n * polygon outline, so the corners stay rounded but the silhouette wobbles.\n * - Circles → wobbly 24-segment polygons.\n * - Lines → wobbled polylines.\n * - Polygons → wobbled edge subdivisions.\n * - Text → repainted in pencil colour.\n * - Images and groups pass through unchanged.\n */\nexport function sketchyShapeStyle(options: SketchyOptions): Style {\n const {jitter, color: pencil} = options;\n\n return {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n const rng = createRng(hashCoords(shape.x, shape.y, shape.width, shape.height));\n const fill = shape.paint.fill ? \"#ffffff\" : undefined;\n const stroke = shape.paint.stroke ? pencil : undefined;\n\n if (shape.cornerRadius && shape.cornerRadius > 0) {\n const out: Shape[] = [];\n if (fill) {\n // Keep rounded fill rect; only the outline wobbles.\n out.push({...shape, paint: {fill, stroke: undefined, strokeWidth: 0}});\n }\n if (stroke && shape.paint.strokeWidth) {\n const verts = wobbleRect(shape.x, shape.y, shape.width, shape.height, jitter, rng);\n const wobbly: PolygonShape = {\n type: \"polygon\",\n vertices: verts,\n paint: {stroke, strokeWidth: shape.paint.strokeWidth},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n out.push(wobbly);\n }\n if (out.length === 1) return out[0];\n return out;\n }\n\n const verts = wobbleRect(shape.x, shape.y, shape.width, shape.height, jitter, rng);\n return {\n type: \"polygon\",\n vertices: verts,\n paint: {fill, stroke, strokeWidth: shape.paint.strokeWidth ?? 0},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n }\n case \"circle\": {\n const rng = createRng(hashCoords(shape.cx, shape.cy, shape.radius));\n const verts = wobbleCircle(shape.cx, shape.cy, shape.radius, jitter, rng);\n return {\n type: \"polygon\",\n vertices: verts,\n paint: {\n fill: shape.paint.fill ? \"#ffffff\" : undefined,\n stroke: shape.paint.stroke ? pencil : undefined,\n strokeWidth: shape.paint.strokeWidth ?? 0,\n },\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n }\n case \"line\": {\n const rng = createRng(hashCoords(...shape.points.slice(0, 4)));\n const wobbly = wobblePolyline(shape.points, jitter, rng);\n const stroke = applyPencilWithAlpha(pencil, shape.paint.stroke);\n const paint: Paint = {...shape.paint, stroke};\n return {...shape, points: wobbly, paint};\n }\n case \"polygon\": {\n const rng = createRng(hashCoords(...shape.vertices.slice(0, 4)));\n const wobbly = wobblePolygonEdges(shape.vertices, jitter, rng);\n return {\n ...shape,\n vertices: wobbly,\n paint: {\n ...shape.paint,\n fill: shape.paint.fill ? pencil : undefined,\n stroke: shape.paint.stroke ? pencil : undefined,\n },\n };\n }\n case \"text\":\n return {...shape, fill: pencil};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n };\n}\n","import type {Shape, LineShape, PolygonShape} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {darkenColor} from \"../../utils/color\";\n\nexport type IsometricRotation = number;\n\nexport interface IsometricOptions {\n /** Cube side face height. Defaults to 0.18. */\n depth?: number;\n /** Rotation angle in degrees. Defaults to 0. */\n rotation?: IsometricRotation;\n}\n\ntype IsoFn = (x: number, y: number) => [number, number];\n\nfunction buildTransform(angleDeg: number): IsoFn {\n const rad = (angleDeg * Math.PI) / 180;\n const cosA = Math.cos(rad);\n const sinA = Math.sin(rad);\n return (x, y) => [\n x * cosA - y * sinA,\n (x * sinA + y * cosA) * 0.5,\n ];\n}\n\nfunction buildInverseTransform(angleDeg: number): IsoFn {\n const rad = (angleDeg * Math.PI) / 180;\n const cosA = Math.cos(rad);\n const sinA = Math.sin(rad);\n return (u, v) => [\n u * cosA + 2 * v * sinA,\n -u * sinA + 2 * v * cosA,\n ];\n}\n\nconst CIRCLE_SEGMENTS = 32;\n\nfunction projectPoints(iso: IsoFn, pts: number[]): number[] {\n const out = new Array<number>(pts.length);\n for (let i = 0; i < pts.length; i += 2) {\n const [ox, oy] = iso(pts[i], pts[i + 1]);\n out[i] = ox;\n out[i + 1] = oy;\n }\n return out;\n}\n\nfunction rectToDiamond(iso: IsoFn, x: number, y: number, w: number, h: number): number[] {\n const [t0x, t0y] = iso(x, y);\n const [t1x, t1y] = iso(x + w, y);\n const [t2x, t2y] = iso(x + w, y + h);\n const [t3x, t3y] = iso(x, y + h);\n return [t0x, t0y, t1x, t1y, t2x, t2y, t3x, t3y];\n}\n\n/**\n * 2:1 isometric projection as a {@link Style}.\n *\n * - Rectangles → diamond (rhombus) polygons. Dashed rects decompose to four\n * dashed line shapes (one per edge). With `depth > 0` and a fill, rooms gain\n * right + left side faces (darker shades of the fill) drawn beneath the top\n * diamond plus six outline edges, producing a 3D cube look.\n * - Circles → ellipse polygons (32 segments). Dashed circles decompose to\n * line segments. With `depth > 0` and a fill, the lower half extrudes into\n * left + right side faces.\n * - Lines / polygons / grid lines have their coordinates projected.\n * - Text / images keep their position and gain an affine `transform` field\n * that maps their bounding box to the iso parallelogram (text stays\n * upright; readers consume `transform` directly).\n * - Group origins are projected; children are projected separately. Because\n * iso is linear, `iso(g) + iso(c) === iso(g + c)`, so the final positions\n * line up exactly.\n *\n * Provides `worldToScene` / `sceneToWorld` so `HitTester` and `Camera` can\n * round-trip clicks through the projection. Link-layer groups (exits,\n * special exits, stubs) are shifted down by the cube depth in render space\n * so connectors attach at the cube base instead of the top face.\n */\nexport function isometricShapeStyle(options: IsometricOptions = {}): Style {\n const rotation = options.rotation ?? 0;\n const depth = options.depth ?? 0.18;\n const iso = buildTransform(rotation);\n const isoInv = buildInverseTransform(rotation);\n\n function isoAffine(x: number, y: number, w: number, h: number): [number, number, number, number, number, number] {\n const [tlX, tlY] = iso(x, y);\n const [trX, trY] = iso(x + w, y);\n const [blX, blY] = iso(x, y + h);\n return [\n (trX - tlX) / w, (trY - tlY) / w,\n (blX - tlX) / h, (blY - tlY) / h,\n tlX, tlY,\n ];\n }\n\n function projectRect(shape: Extract<Shape, {type: \"rect\"}>): Shape | Shape[] {\n const {x, y, width: w, height: h, paint} = shape;\n const diamond = rectToDiamond(iso, x, y, w, h);\n\n // Dashed rects: decompose to 4 dashed lines.\n if (paint.dash || paint.dashEnabled) {\n const lines: LineShape[] = [];\n for (let i = 0; i < 8; i += 2) {\n const ni = (i + 2) % 8;\n lines.push({\n type: \"line\",\n points: [diamond[i], diamond[i + 1], diamond[ni], diamond[ni + 1]],\n paint: {\n stroke: paint.stroke,\n strokeWidth: paint.strokeWidth,\n dash: paint.dash,\n },\n layer: shape.layer,\n });\n }\n return lines;\n }\n\n // Cube with side faces.\n if (depth > 0 && paint.fill) {\n const out: Shape[] = [];\n // Find bottom-most vertex for cube extrusion.\n let bottomIdx = 0;\n for (let i = 1; i < 4; i++) {\n if (diamond[i * 2 + 1] > diamond[bottomIdx * 2 + 1]) bottomIdx = i;\n }\n const bX = diamond[bottomIdx * 2], bY = diamond[bottomIdx * 2 + 1];\n const rIdx = (bottomIdx + 3) % 4;\n const rX = diamond[rIdx * 2], rY = diamond[rIdx * 2 + 1];\n const lIdx = (bottomIdx + 1) % 4;\n const lX = diamond[lIdx * 2], lY = diamond[lIdx * 2 + 1];\n const tIdx = (bottomIdx + 2) % 4;\n const tX = diamond[tIdx * 2], tY = diamond[tIdx * 2 + 1];\n\n // Right face, left face (no strokes), then top diamond.\n out.push({\n type: \"polygon\",\n vertices: [rX, rY, bX, bY, bX, bY + depth, rX, rY + depth],\n paint: {fill: darkenColor(paint.fill, 0.2)},\n layer: shape.layer,\n });\n out.push({\n type: \"polygon\",\n vertices: [bX, bY, lX, lY, lX, lY + depth, bX, bY + depth],\n paint: {fill: darkenColor(paint.fill, 0.4)},\n layer: shape.layer,\n });\n out.push({\n type: \"polygon\",\n vertices: diamond,\n paint: {fill: paint.fill},\n layer: shape.layer,\n hit: shape.hit,\n });\n\n // Outline edges (visible only — top face top edges + outer side edges).\n if (paint.stroke && paint.strokeWidth) {\n const sw = paint.strokeWidth;\n const stroke = paint.stroke;\n const lineLayer = shape.layer;\n const push = (points: number[]) => out.push({\n type: \"line\",\n points,\n paint: {stroke, strokeWidth: sw},\n layer: lineLayer,\n });\n push([tX, tY, rX, rY]);\n push([lX, lY, tX, tY]);\n push([rX, rY, rX, rY + depth]);\n push([lX, lY, lX, lY + depth]);\n push([rX, rY + depth, bX, bY + depth]);\n push([bX, bY + depth, lX, lY + depth]);\n }\n return out;\n }\n\n // Flat diamond.\n return {\n type: \"polygon\",\n vertices: diamond,\n paint: {fill: paint.fill, stroke: paint.stroke, strokeWidth: paint.strokeWidth},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n }\n\n function projectCircle(shape: Extract<Shape, {type: \"circle\"}>): Shape | Shape[] {\n const {cx, cy, radius, paint} = shape;\n const verts = new Array<number>(CIRCLE_SEGMENTS * 2);\n for (let i = 0; i < CIRCLE_SEGMENTS; i++) {\n const a = (i / CIRCLE_SEGMENTS) * Math.PI * 2;\n const [ox, oy] = iso(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);\n verts[i * 2] = ox;\n verts[i * 2 + 1] = oy;\n }\n\n if (paint.dash || paint.dashEnabled) {\n const lines: LineShape[] = [];\n for (let i = 0; i < CIRCLE_SEGMENTS; i++) {\n const ni = (i + 1) % CIRCLE_SEGMENTS;\n lines.push({\n type: \"line\",\n points: [verts[i * 2], verts[i * 2 + 1], verts[ni * 2], verts[ni * 2 + 1]],\n paint: {\n stroke: paint.stroke,\n strokeWidth: paint.strokeWidth,\n dash: paint.dash,\n },\n layer: shape.layer,\n });\n }\n return lines;\n }\n\n const out: Shape[] = [];\n if (depth > 0 && paint.fill) {\n // Find extreme vertices.\n let rightIdx = 0, leftIdx = 0, bottomIdx = 0;\n let maxX = -Infinity, minX = Infinity, maxY = -Infinity;\n for (let i = 0; i < CIRCLE_SEGMENTS; i++) {\n if (verts[i * 2] > maxX) { maxX = verts[i * 2]; rightIdx = i; }\n if (verts[i * 2] < minX) { minX = verts[i * 2]; leftIdx = i; }\n if (verts[i * 2 + 1] > maxY) { maxY = verts[i * 2 + 1]; bottomIdx = i; }\n }\n\n const rightFace: number[] = [];\n let idx = rightIdx;\n for (let safety = 0; safety <= CIRCLE_SEGMENTS; safety++) {\n rightFace.push(verts[idx * 2], verts[idx * 2 + 1]);\n if (idx === bottomIdx) break;\n idx = (idx + 1) % CIRCLE_SEGMENTS;\n }\n for (let i = rightFace.length - 2; i >= 0; i -= 2) {\n rightFace.push(rightFace[i], rightFace[i + 1] + depth);\n }\n out.push({\n type: \"polygon\",\n vertices: rightFace,\n paint: {\n fill: darkenColor(paint.fill, 0.2),\n stroke: paint.stroke,\n strokeWidth: paint.strokeWidth ? paint.strokeWidth * 0.5 : undefined,\n },\n layer: shape.layer,\n });\n\n const leftFace: number[] = [];\n idx = bottomIdx;\n for (let safety = 0; safety <= CIRCLE_SEGMENTS; safety++) {\n leftFace.push(verts[idx * 2], verts[idx * 2 + 1]);\n if (idx === leftIdx) break;\n idx = (idx + 1) % CIRCLE_SEGMENTS;\n }\n for (let i = leftFace.length - 2; i >= 0; i -= 2) {\n leftFace.push(leftFace[i], leftFace[i + 1] + depth);\n }\n out.push({\n type: \"polygon\",\n vertices: leftFace,\n paint: {\n fill: darkenColor(paint.fill, 0.4),\n stroke: paint.stroke,\n strokeWidth: paint.strokeWidth ? paint.strokeWidth * 0.5 : undefined,\n },\n layer: shape.layer,\n });\n }\n\n const top: PolygonShape = {\n type: \"polygon\",\n vertices: verts,\n paint: {fill: paint.fill, stroke: paint.stroke, strokeWidth: paint.strokeWidth},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n out.push(top);\n return out.length === 1 ? out[0] : out;\n }\n\n return {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\":\n return projectRect(shape);\n case \"circle\":\n return projectCircle(shape);\n case \"line\":\n return {...shape, points: projectPoints(iso, shape.points)};\n case \"polygon\":\n return {...shape, vertices: projectPoints(iso, shape.vertices)};\n case \"text\": {\n const w = shape.width ?? 0;\n const h = shape.height ?? 0;\n if (w > 0 && h > 0) {\n return {\n ...shape,\n x: 0,\n y: 0,\n transform: isoAffine(shape.x, shape.y, w, h),\n };\n }\n const [ox, oy] = iso(shape.x, shape.y);\n return {...shape, x: ox, y: oy};\n }\n case \"image\":\n return {\n ...shape,\n x: 0,\n y: 0,\n transform: isoAffine(shape.x, shape.y, shape.width, shape.height),\n };\n case \"group\": {\n const [ox, oy] = iso(shape.x, shape.y);\n // Link-layer connectors (exits, special exits, stubs) attach\n // to the cube base, not the projected top face. Shift the\n // group origin down by the cube depth in render space.\n if (depth > 0 && shape.layer === \"link\") {\n return {...shape, x: ox, y: oy + depth};\n }\n return {...shape, x: ox, y: oy};\n }\n }\n },\n\n worldToScene(x, y) {\n const [ox, oy] = iso(x, y);\n return {x: ox, y: oy};\n },\n\n sceneToWorld(x, y) {\n const [ox, oy] = isoInv(x, y);\n return {x: ox, y: oy};\n },\n };\n}\n","import type {Shape, Paint, RectShape, PolygonShape} from \"../../scene/Shape\";\nimport type {Style, StyleContext} from \"../Style\";\nimport {formatRgb, luminance, parseRgb} from \"./paintMap\";\n\n/** Near-black asphalt used for dark room fills and strokes. */\nconst ASPHALT = \"#1a1a1a\";\n/** Bright safety yellow for stripe fills and text. */\nconst CAUTION_YELLOW = \"#ffc200\";\n/** Safety orange used for exit lines and polygon arrows. */\nconst EXIT_COLOR = \"#ff6600\";\n\n// Luminance gradient endpoints (non-striped shapes: circles, etc.)\nconst DARK_R = 26, DARK_G = 26, DARK_B = 26;\nconst LIGHT_R = 255, LIGHT_G = 194, LIGHT_B = 0;\n\nfunction toConstruction(color: string): string {\n const c = parseRgb(color);\n if (!c) return formatRgb(LIGHT_R, LIGHT_G, LIGHT_B);\n const lum = luminance(c);\n return formatRgb(\n Math.round(DARK_R + (LIGHT_R - DARK_R) * lum),\n Math.round(DARK_G + (LIGHT_G - DARK_G) * lum),\n Math.round(DARK_B + (LIGHT_B - DARK_B) * lum),\n c.a,\n );\n}\n\nfunction rewriteFillStroke(paint: Paint): Paint {\n return {\n ...paint,\n fill: paint.fill ? toConstruction(paint.fill) : paint.fill,\n stroke: paint.stroke ? ASPHALT : paint.stroke,\n };\n}\n\n// --- Diagonal stripe clipping via Sutherland-Hodgman ---\n\ntype Pt = [number, number];\n\n/** Clip a convex polygon to the half-plane a·x + b·y ≥ c. */\nfunction clipHalfplane(poly: Pt[], a: number, b: number, c: number): Pt[] {\n const out: Pt[] = [];\n const n = poly.length;\n for (let i = 0; i < n; i++) {\n const curr = poly[i];\n const next = poly[(i + 1) % n];\n const dc = a * curr[0] + b * curr[1] - c;\n const dn = a * next[0] + b * next[1] - c;\n if (dc >= 0) out.push(curr);\n if ((dc > 0 && dn < 0) || (dc < 0 && dn > 0)) {\n const t = dc / (dc - dn);\n out.push([curr[0] + t * (next[0] - curr[0]), curr[1] + t * (next[1] - curr[1])]);\n }\n }\n return out;\n}\n\n/**\n * Return flat vertex arrays for the yellow stripe polygons that fall inside\n * the given rect. Stripes run at 45° in the \"/\" direction (x + y = const),\n * matching classic hazard-tape markings.\n */\nfunction rectStripeVertices(\n x: number, y: number, w: number, h: number, roomSize: number,\n): number[][] {\n const corners: Pt[] = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]];\n const period = roomSize * 0.4;\n const half = period * 0.35;\n const uMin = x + y;\n const uMax = x + w + y + h;\n const startK = Math.floor(uMin / period) - 1;\n const endK = Math.ceil(uMax / period) + 1;\n const result: number[][] = [];\n\n for (let k = startK; k <= endK; k++) {\n const u1 = k * period;\n const u2 = u1 + half;\n // Yellow band: u1 ≤ x + y ≤ u2\n let poly = clipHalfplane(corners, 1, 1, u1); // x + y ≥ u1\n poly = clipHalfplane(poly, -1, -1, -u2); // x + y ≤ u2\n if (poly.length >= 3) result.push(poly.flatMap(p => [p[0], p[1]]));\n }\n\n return result;\n}\n\n/**\n * Construction-site hazard aesthetic as a {@link Style}.\n *\n * Rect rooms get diagonal yellow/black stripes: the rect is rendered as a\n * dark asphalt base, then yellow stripe polygons (clipped to the room bounds\n * via Sutherland-Hodgman) are emitted on top, then the black border is drawn\n * last so room edges stay crisp. The stripe period scales with `roomSize`\n * so markings look consistent across zoom levels and room-size settings.\n *\n * Other shape types:\n * - Circles use a luminance-mapped gradient (asphalt → safety yellow).\n * - Exit lines and polygon arrows become safety orange (#FF6600).\n * - Text uses safety yellow.\n * - Images and groups pass through unchanged.\n */\nexport const constructionShapeStyle: Style = {\n transform(shape: Shape, ctx: StyleContext): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n if (!shape.paint.fill) {\n // Stroke-only rect — just recolour, no stripes needed.\n return {...shape, paint: rewriteFillStroke(shape.paint)};\n }\n\n // 1. Dark asphalt base (carries hit info, fill only).\n const base: RectShape = {\n ...shape,\n paint: {fill: ASPHALT, strokeWidth: shape.paint.strokeWidth},\n };\n\n // 2. Yellow diagonal stripes clipped to the room rect.\n const verts = rectStripeVertices(shape.x, shape.y, shape.width, shape.height, ctx.roomSize);\n const stripes: PolygonShape[] = verts.map(v => ({\n type: \"polygon\" as const,\n vertices: v,\n layer: shape.layer,\n paint: {fill: CAUTION_YELLOW, alpha: 0.65},\n }));\n\n // 3. Black border on top — no fill, no hit (hit is on base).\n const border: RectShape = {\n ...shape,\n hit: undefined,\n paint: {stroke: ASPHALT, strokeWidth: shape.paint.strokeWidth},\n };\n\n return [base, ...stripes, border];\n }\n\n case \"circle\":\n return {...shape, paint: rewriteFillStroke(shape.paint)};\n\n case \"polygon\":\n return {\n ...shape,\n paint: {\n ...shape.paint,\n fill: shape.paint.fill ? EXIT_COLOR : shape.paint.fill,\n stroke: shape.paint.stroke ? ASPHALT : shape.paint.stroke,\n },\n };\n\n case \"line\":\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: EXIT_COLOR}\n : shape.paint,\n };\n\n case \"text\":\n return {...shape, fill: EXIT_COLOR, stroke: ASPHALT, strokeWidth: ctx.roomSize * 0.25};\n\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","import type {Shape, Paint, RectShape, CircleShape, LineShape} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {parseRgb} from \"./paintMap\";\n\n/** Near-white blue for labels. */\nconst TEXT_COLOR = \"#c8eeff\";\n/** Electric cyan for room borders and glow. */\nconst STROKE_COLOR = \"#00c8ff\";\n/** Targeting green-cyan for exits and arrows. */\nconst EXIT_COLOR = \"#00ffaa\";\n\nconst GLOW_ALPHA = 0.2;\nconst GLOW_WIDTH_MULT = 5;\n\n/** Default hue (cyan) used when the original colour is achromatic. */\nconst DEFAULT_HUE = 190;\n\nfunction rgbToHsl(r: number, g: number, b: number): [number, number, number] {\n r /= 255; g /= 255; b /= 255;\n const max = Math.max(r, g, b), min = Math.min(r, g, b);\n const l = (max + min) / 2;\n if (max === min) return [DEFAULT_HUE, 0, l];\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h: number;\n if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n else if (max === g) h = ((b - r) / d + 2) / 6;\n else h = ((r - g) / d + 4) / 6;\n return [h * 360, s, l];\n}\n\nfunction hslToRgb(h: number, s: number, l: number, a = 1): string {\n h = ((h % 360) + 360) % 360;\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n let r: number, g: number, b: number;\n if (h < 60) { r = c; g = x; b = 0; }\n else if (h < 120) { r = x; g = c; b = 0; }\n else if (h < 180) { r = 0; g = c; b = x; }\n else if (h < 240) { r = 0; g = x; b = c; }\n else if (h < 300) { r = x; g = 0; b = c; }\n else { r = c; g = 0; b = x; }\n const ri = Math.round((r + m) * 255);\n const gi = Math.round((g + m) * 255);\n const bi = Math.round((b + m) * 255);\n return a < 1 ? `rgba(${ri},${gi},${bi},${a})` : `rgb(${ri},${gi},${bi})`;\n}\n\n/**\n * Derive a fill from the original room colour: preserve the hue so each area\n * keeps its identity, but force a dark lightness and moderate saturation to\n * fit the deep-space aesthetic. Achromatic rooms default to cyan (190°).\n */\nfunction toSciFiFill(color: string): string {\n const c = parseRgb(color);\n if (!c) return hslToRgb(DEFAULT_HUE, 0.55, 0.18);\n const [h, s] = rgbToHsl(c.r, c.g, c.b);\n const hue = s < 0.08 ? DEFAULT_HUE : h;\n return hslToRgb(hue, 0.55, 0.18, c.a);\n}\n\nfunction rewriteFillStroke(paint: Paint): Paint {\n return {\n ...paint,\n fill: paint.fill ? toSciFiFill(paint.fill) : paint.fill,\n stroke: paint.stroke ? STROKE_COLOR : paint.stroke,\n };\n}\n\nfunction roomGlowPaint(paint: Paint): Paint {\n return {\n stroke: STROKE_COLOR,\n strokeWidth: (paint.strokeWidth ?? 1) * GLOW_WIDTH_MULT,\n alpha: GLOW_ALPHA,\n };\n}\n\nfunction lineGlowPaint(paint: Paint): Paint {\n return {\n stroke: EXIT_COLOR,\n strokeWidth: (paint.strokeWidth ?? 1) * GLOW_WIDTH_MULT,\n alpha: GLOW_ALPHA,\n dash: paint.dash,\n dashEnabled: paint.dashEnabled,\n };\n}\n\n/**\n * Sci-fi / space-exploration aesthetic as a {@link Style}.\n *\n * - Fills preserve the original room hue (so area colours stay distinct) but\n * are pushed to a fixed dark lightness and moderate saturation, giving each\n * zone a deep-space tinted look. Achromatic rooms default to cyan.\n * - Room borders use electric cyan (#00C8FF) with a wide translucent glow halo,\n * giving the holographic-display bloom effect.\n * - Exit lines and arrow polygons use targeting green-cyan (#00FFAA) with a\n * glow pass, evoking radar / navigation overlays.\n * - Grid lines are rendered as a barely-visible dark tint — no glow.\n * - Text uses near-white blue for crisp legibility on dark fills.\n * - Images and groups pass through unchanged.\n */\nexport const scifiShapeStyle: Style = {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n const main: RectShape = {...shape, paint: rewriteFillStroke(shape.paint)};\n if (!shape.paint.stroke) return main;\n const glow: RectShape = {\n ...shape,\n hit: undefined,\n paint: {...roomGlowPaint(shape.paint), fill: undefined},\n };\n return [glow, main];\n }\n case \"circle\": {\n const main: CircleShape = {...shape, paint: rewriteFillStroke(shape.paint)};\n if (!shape.paint.stroke) return main;\n const glow: CircleShape = {\n ...shape,\n hit: undefined,\n paint: {...roomGlowPaint(shape.paint), fill: undefined},\n };\n return [glow, main];\n }\n case \"line\": {\n if (shape.grid) {\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: \"#0a1e2e\"}\n : shape.paint,\n };\n }\n const main: LineShape = {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: EXIT_COLOR}\n : shape.paint,\n };\n if (!shape.paint.stroke) return main;\n const glow: LineShape = {...shape, hit: undefined, paint: lineGlowPaint(shape.paint)};\n return [glow, main];\n }\n case \"polygon\":\n return {\n ...shape,\n paint: {\n ...shape.paint,\n fill: shape.paint.fill ? EXIT_COLOR : shape.paint.fill,\n stroke: undefined,\n },\n };\n case \"text\":\n return {...shape, fill: TEXT_COLOR};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","/**\n * Target-agnostic visual styles. Each style is a {@link Style} that transforms\n * world-space {@link Shape}s before they hit culling, hit-testing, and the\n * draw-command pipeline. The same style drives interactive canvas rendering,\n * SVG export, and Canvas / PNG rasterization.\n *\n * Usage:\n * ```ts\n * import {compose, Parchment, Sketchy, Isometric} from 'mudlet-map-renderer';\n *\n * const style = compose(\n * Parchment,\n * Sketchy({jitter: 0.012, color: '#4a3728'}),\n * Isometric({rotation: 30, depth: 0.18}),\n * );\n * renderer.setStyle(style);\n * ```\n */\n\nimport type {Style} from \"./Style\";\nimport {parchmentShapeStyle} from \"./shape/ParchmentStyle\";\nimport {blueprintShapeStyle} from \"./shape/BlueprintStyle\";\nimport {neonShapeStyle} from \"./shape/NeonStyle\";\nimport {sketchyShapeStyle, type SketchyOptions} from \"./shape/SketchyStyle\";\nimport {isometricShapeStyle, type IsometricOptions, type IsometricRotation} from \"./shape/IsometricStyle\";\nimport {constructionShapeStyle} from \"./shape/ConstructionStyle\";\nimport {scifiShapeStyle} from \"./shape/SciFiStyle\";\n\nexport {compose, identityStyle} from \"./Style\";\nexport type {Style, StyleContext} from \"./Style\";\nexport {applyStyleToShapes} from \"./applyStyle\";\n\n/** Warm sepia / old-parchment palette. */\nexport const Parchment: Style = parchmentShapeStyle;\n\n/** Technical blueprint aesthetic — white lines on deep blue. */\nexport const Blueprint: Style = blueprintShapeStyle;\n\n/** Cyberpunk / neon aesthetic — glowing outlines on dark background. */\nexport const Neon: Style = neonShapeStyle;\n\n/** Hand-drawn pencil wobble. */\nexport function Sketchy(options: SketchyOptions): Style {\n return sketchyShapeStyle(options);\n}\n\n/** 2:1 isometric projection with optional cube depth. */\nexport function Isometric(options: IsometricOptions = {}): Style {\n return isometricShapeStyle(options);\n}\n\n/** Construction-site hazard aesthetic — safety yellow on asphalt, orange exits. */\nexport const Construction: Style = constructionShapeStyle;\n\n/** Sci-fi / space-exploration aesthetic — holographic cyan glow on void black. */\nexport const SciFi: Style = scifiShapeStyle;\n\nexport type {SketchyOptions, IsometricOptions, IsometricRotation};\n","import type {IMapReader} from \"../reader/MapReader\";\nimport type {IArea} from \"../reader/Area\";\nimport type {RoomLens} from \"../lens/RoomLens\";\nimport type {ViewportBounds, RendererEventMap, CullingMode} from \"../types/Settings\";\nimport {createSettings} from \"../types/Settings\";\nimport type {Settings} from \"../types/Settings\";\nimport {MapState} from \"../MapState\";\nimport {KonvaRenderBackend} from \"./KonvaRenderBackend\";\nimport type {CoordFn} from \"../coord/CoordFn\";\nimport type {Style} from \"../style\";\nimport {identityStyle} from \"../style\";\nimport type {Camera} from \"../camera/Camera\";\nimport type {CullingManager} from \"../CullingManager\";\nimport type {TypedEventEmitter} from \"../TypedEventEmitter\";\nimport type {SceneOverlay} from \"../overlay/SceneOverlay\";\nimport type {LiveEffect} from \"../overlay/LiveEffect\";\nimport type {Exporter, ExportContext, ExportCanvas} from \"../export/Exporter\";\nimport type {DrawnExitEntry, DrawnSpecialExitEntry, DrawnStubEntry} from \"../ScenePipeline\";\nimport type {HitTester, HitResult} from \"../hit/HitTester\";\n\n/**\n * Contract for interactive render backends.\n *\n * Engine-neutral surface — anything that requires a specific render engine\n * (Konva layers for live effects, Konva.Stage for `toCanvas`) is intentionally\n * not on this interface and lives only on the concrete backend.\n */\nexport interface InteractiveBackend {\n readonly camera: Camera;\n readonly culling: CullingManager;\n readonly hitTester: HitTester;\n readonly events: TypedEventEmitter<RendererEventMap>;\n /** Forward map → render-space transform from the active style (identity for flat styles). */\n readonly coordinateTransform: CoordFn;\n /**\n * Apply a target-agnostic {@link Style} to the live scene. The style\n * transforms world-space shapes (paint, geometry, projection) before they\n * are rasterized; the backend re-renders the current scene to reflect it.\n */\n setStyle(style: Style): void;\n updateBackground(): void;\n refresh(): void;\n /** Capture the current camera as a canvas with background fill. */\n exportCanvas(options?: { pixelRatio?: number }): ExportCanvas | undefined;\n addSceneOverlay(id: string, overlay: SceneOverlay): void;\n removeSceneOverlay(id: string): void;\n getSceneOverlays(): Iterable<SceneOverlay>;\n /**\n * Snapshot of inter-room exits as drawn in the last `buildScene` call\n * (polyline segments, arrows, bounds, dashes — exactly what the user\n * sees). Empty before the first draw. The list already reflects the\n * renderer's suppression rules, so anything drawn appears here and\n * anything not drawn does not.\n */\n getDrawnExits(): readonly DrawnExitEntry[];\n /** Companion to {@link getDrawnExits} for custom-line special exits. */\n getDrawnSpecialExits(): readonly DrawnSpecialExitEntry[];\n /** Companion to {@link getDrawnExits} for one-way stub indicators. */\n getDrawnStubs(): readonly DrawnStubEntry[];\n destroy(): void;\n}\n\n/**\n * Unified map renderer facade.\n *\n * Public surface is intentionally narrow:\n * - **State mutations** (`drawArea`, `setPosition`, `renderHighlight`, …) emit\n * events through `MapState` that the interactive backend reacts to.\n * - **{@link setStyle}** applies a target-agnostic visual transformation to the\n * interactive canvas and every exporter.\n * - **{@link export}** runs an {@link Exporter} plug-in and returns its output\n * (SVG string, PNG data URL, canvas, PDF bytes, …). New formats are added by\n * shipping new `Exporter<T>` implementations — no new methods on this class.\n * - **{@link addSceneOverlay}** is target-agnostic and appears in every output.\n * - **{@link addLiveEffect}** registers interactive-only animated effects (Konva\n * canvas only; skipped by exporters).\n */\nexport class MapRenderer {\n readonly state: MapState;\n readonly backend: InteractiveBackend;\n private currentStyle: Style = identityStyle;\n\n get settings(): Settings {\n return this.state.settings;\n }\n\n /** Camera owned by the active interactive backend. */\n get camera(): Camera {\n return this.backend.camera;\n }\n\n /** Culling manager owned by the active interactive backend. */\n get culling(): CullingManager {\n return this.backend.culling;\n }\n\n /** Hit-test index owned by the active interactive backend. */\n get hitTester(): HitTester {\n return this.backend.hitTester;\n }\n\n /** Renderer event emitter (room click, area exit click, zoom change, …). */\n get events(): TypedEventEmitter<RendererEventMap> {\n return this.backend.events;\n }\n\n /**\n * @param mapReader Map data source.\n * @param settings Renderer settings. Defaults to `createSettings()`.\n * @param container DOM element for interactive rendering. Omit for headless.\n * @param backendFactory Optional factory that receives the `MapState` and returns\n * a custom `InteractiveBackend`. When omitted, a `KonvaRenderBackend` is created.\n */\n constructor(\n mapReader: IMapReader,\n settings?: Settings,\n container?: HTMLDivElement,\n backendFactory?: (state: MapState) => InteractiveBackend,\n ) {\n const resolvedSettings = settings ?? createSettings();\n this.state = new MapState(mapReader, resolvedSettings);\n this.backend = backendFactory\n ? backendFactory(this.state)\n : new KonvaRenderBackend(this.state, container);\n }\n\n destroy() {\n this.backend.destroy();\n }\n\n // --- State mutations (emit events → backend reacts) ---\n\n drawArea(id: number, zIndex: number) {\n this.state.setArea(id, zIndex);\n }\n\n getCurrentArea(): IArea | undefined {\n return this.state.currentAreaInstance;\n }\n\n setPosition(roomId: number, center: boolean = true) {\n this.state.setPosition(roomId, center);\n }\n\n updatePositionMarker(roomId: number) {\n this.state.updatePositionMarker(roomId);\n }\n\n clearPosition() {\n this.state.clearPosition();\n }\n\n centerOn(roomId: number, instant?: boolean) {\n this.state.setCenterRoom(roomId, instant);\n }\n\n renderHighlight(roomId: number, color: string) {\n this.state.addHighlight(roomId, color);\n }\n\n removeHighlight(roomId: number) {\n this.state.removeHighlight(roomId);\n }\n\n hasHighlight(roomId: number): boolean {\n return this.state.hasHighlight(roomId);\n }\n\n clearHighlights() {\n this.state.clearHighlights();\n }\n\n renderPath(locations: number[], color?: string) {\n this.state.addPath(locations, color);\n }\n\n clearPaths() {\n this.state.clearPaths();\n }\n\n refreshCurrentRoomOverlay() {\n this.state.refreshPosition();\n }\n\n // --- Visibility lens ---\n\n /**\n * Replace the active visibility lens. Lenses filter which rooms (and how\n * partially-visible exits) the renderer draws — exploration / fog-of-war\n * is one example, guild/quest scope overlays are others. Compose multiple\n * concerns with `composeLenses(...)`.\n *\n * Triggers an immediate rebuild. For mutations on a stateful lens (e.g.\n * `ExplorationLens.addVisited`), call {@link refresh} explicitly.\n */\n setLens(lens: RoomLens) {\n this.state.setLens(lens);\n }\n\n getLens(): RoomLens {\n return this.state.lens;\n }\n\n // --- Style ---\n\n /**\n * Apply a {@link Style} to the interactive canvas and every export path.\n * The backend rebuilds the current scene under the new style — no explicit\n * `refresh()` is needed.\n *\n * ```ts\n * import {compose, Parchment, Sketchy} from 'mudlet-map-renderer';\n * renderer.setStyle(compose(Parchment, Sketchy({jitter: 0.012, color: '#4a3728'})));\n * ```\n *\n * Pass `identityStyle` (or call {@link clearStyle}) to remove the current style.\n */\n setStyle(style: Style) {\n this.currentStyle = style;\n this.backend.setStyle(style);\n }\n\n /** Equivalent to `setStyle(identityStyle)`. */\n clearStyle() {\n this.setStyle(identityStyle);\n }\n\n /** Returns the style currently applied (defaults to identity). */\n getStyle(): Style {\n return this.currentStyle;\n }\n\n updateBackground() {\n this.backend.updateBackground();\n }\n\n refresh() {\n this.backend.updateBackground();\n this.backend.refresh();\n }\n\n // --- Overlays ---\n\n /** Target-agnostic overlay; appears in every output including exporters. */\n addSceneOverlay(id: string, overlay: SceneOverlay) {\n this.backend.addSceneOverlay(id, overlay);\n }\n\n removeSceneOverlay(id: string) {\n this.backend.removeSceneOverlay(id);\n }\n\n /**\n * Register an interactive-only animated effect. No-ops when running with a\n * non-Konva backend. Does not appear in SVG/PNG exports — use\n * {@link addSceneOverlay} for overlays that must appear in exports.\n *\n * ```ts\n * renderer.addLiveEffect('rain', new RainEffect());\n * ```\n */\n addLiveEffect(id: string, effect: LiveEffect) {\n if (this.backend instanceof KonvaRenderBackend) {\n this.backend.addLiveEffect(id, effect);\n }\n }\n\n removeLiveEffect(id: string) {\n if (this.backend instanceof KonvaRenderBackend) {\n this.backend.removeLiveEffect(id);\n }\n }\n\n // --- Hit testing ---\n\n /**\n * Hit-test a world-space map point against the current scene.\n *\n * Returns the nearest pickable shape at `(worldX, worldY)`, or `null`\n * when no hittable shape is within range. Coordinates must be in the\n * same flat map space as room positions — the method applies the active\n * style's world→scene projection internally, so callers never need to\n * think about Isometric mode.\n *\n * ```ts\n * const hit = renderer.hitTest(room.x, room.y);\n * if (hit?.kind === 'room') console.log('room', hit.id);\n * ```\n */\n hitTest(worldX: number, worldY: number): HitResult | null {\n const rendered = this.backend.coordinateTransform(worldX, worldY);\n return this.backend.hitTester.pick(rendered.x, rendered.y);\n }\n\n // --- Drawn geometry (hit-testing integration) ---\n\n /**\n * Polyline / arrow / bounds data for every inter-room exit the renderer\n * drew on the last scene build. Intended for tools (e.g. editors) that\n * need to hit-test against exactly what the user sees, including dash\n * patterns, one-way arrows, and the renderer's suppression rules.\n */\n getDrawnExits(): readonly DrawnExitEntry[] {\n return this.backend.getDrawnExits();\n }\n\n /** Companion to {@link getDrawnExits} for custom-line special exits. */\n getDrawnSpecialExits(): readonly DrawnSpecialExitEntry[] {\n return this.backend.getDrawnSpecialExits();\n }\n\n /**\n * Polyline data for every one-way stub indicator the renderer drew\n * (one entry per direction in `room.stubs`). Coordinates are in\n * render space and match what's on screen.\n */\n getDrawnStubs(): readonly DrawnStubEntry[] {\n return this.backend.getDrawnStubs();\n }\n\n // --- Export ---\n\n /**\n * Run an {@link Exporter} against the current scene and return its output.\n *\n * ```ts\n * const svg = renderer.export(new SvgExporter({ padding: 5 }));\n * const url = renderer.export(new PngExporter({ pixelRatio: 2 }));\n * const blob = await renderer.export(new PngBlobExporter());\n * const canvas = renderer.export(new CanvasExporter({ width, height }));\n * ```\n */\n export<T>(exporter: Exporter<T>): T {\n const context: ExportContext = {\n state: this.state,\n backend: this.backend,\n style: this.currentStyle,\n sceneOverlays: this.backend.getSceneOverlays(),\n };\n return exporter.render(context);\n }\n\n // --- Camera & interaction ---\n\n on<K extends keyof RendererEventMap>(event: K, handler: (detail: RendererEventMap[K]) => void): void {\n this.backend.events.on(event, handler);\n }\n\n off<K extends keyof RendererEventMap>(event: K, handler: (detail: RendererEventMap[K]) => void): void {\n this.backend.events.off(event, handler);\n }\n\n setZoom(zoom: number): boolean {\n return this.backend.camera.setZoom(zoom);\n }\n\n zoomToCenter(zoom: number): boolean {\n return this.backend.camera.zoomToCenter(zoom);\n }\n\n getZoom(): number {\n return this.backend.camera.zoom;\n }\n\n getViewportBounds(): ViewportBounds {\n return this.backend.camera.getViewportBounds();\n }\n\n getAreaBounds(): ViewportBounds | null {\n if (!this.state.currentAreaInstance || this.state.currentZIndex === undefined) return null;\n const plane = this.state.currentAreaInstance.getPlane(this.state.currentZIndex);\n if (!plane) return null;\n const b = this.state.getEffectiveBounds(this.state.currentAreaInstance, plane);\n const areaName = this.state.settings.areaName ? this.state.currentAreaInstance.getAreaName() : null;\n // Mirror the offsets used by computeExportBounds so that getAreaBounds()\n // covers the same region the export/preview image will render.\n const nameRight = areaName ? b.minX - 3.5 + areaName.length * 2.5 * 0.6 : b.maxX;\n const raw: ViewportBounds = {\n minX: areaName ? b.minX - 3.5 : b.minX,\n maxX: Math.max(b.maxX, nameRight),\n minY: areaName ? b.minY - 7 : b.minY,\n maxY: b.maxY,\n };\n const fn = this.backend.coordinateTransform;\n const c1 = fn(raw.minX, raw.minY);\n const c2 = fn(raw.maxX, raw.minY);\n const c3 = fn(raw.maxX, raw.maxY);\n const c4 = fn(raw.minX, raw.maxY);\n return {\n minX: Math.min(c1.x, c2.x, c3.x, c4.x),\n maxX: Math.max(c1.x, c2.x, c3.x, c4.x),\n minY: Math.min(c1.y, c2.y, c3.y, c4.y),\n maxY: Math.max(c1.y, c2.y, c3.y, c4.y),\n };\n }\n\n fitArea(insets?: { top?: number; right?: number; bottom?: number; left?: number }) {\n const bounds = this.getAreaBounds();\n if (!bounds) return;\n this.backend.camera.fitToMapBounds(bounds.minX, bounds.maxX, bounds.minY, bounds.maxY, insets);\n }\n\n get centerOnResize(): boolean {\n return this.backend.camera.centerOnResize;\n }\n\n set centerOnResize(value: boolean) {\n this.backend.camera.centerOnResize = value;\n }\n\n get minZoom(): number {\n return this.backend.camera.minZoom;\n }\n\n set minZoom(value: number) {\n this.backend.camera.minZoom = value;\n }\n\n setCullingMode(mode: CullingMode) {\n this.state.settings.cullingMode = mode;\n this.state.settings.cullingEnabled = mode !== \"none\";\n this.backend.culling.scheduleCulling();\n }\n\n getCullingMode(): CullingMode {\n return this.state.settings.cullingMode;\n }\n}\n","/**\n * {@link DrawCommandBuilder} — single source of truth for camera transform math.\n *\n * Walks a {@link SceneIR}-shaped tree (already passed through styles) and\n * produces a flat {@link DrawCommand} list per logical layer. Coordinates\n * arrive in world space; commands leave in render space (camera scale and\n * pan applied), with stroke widths and corner radii already scaled.\n *\n * `noScale` group shapes are emitted as `pushTransform`/`popTransform`\n * pairs: the group origin is placed at its world-mapped screen position,\n * but the children render at fixed pixel size regardless of zoom — the\n * legacy `RecordingLayerNode.canScaleNoScalingGroups` invariant.\n */\n\nimport type {\n DrawCommand,\n DrawCommandBatch,\n LineCommand,\n PolygonCommand,\n RectCommand,\n TextCommand,\n} from \"./DrawCommand\";\nimport type {\n GroupShape,\n LayerId,\n Shape,\n} from \"../scene/Shape\";\n\n/** Camera state needed to project world coordinates into render space. */\nexport interface CameraTransform {\n /** Pixels per world unit. */\n scale: number;\n /** Render-space origin in pixels. */\n offsetX: number;\n offsetY: number;\n}\n\nconst DEFAULT_LAYER: LayerId = \"room\";\n\n/**\n * Build per-layer {@link DrawCommandBatch}es from a list of world-space shapes\n * and an active camera transform.\n *\n * Layer order in the result follows first-appearance order; renderers iterate\n * the array as-is (z-order is the caller's responsibility — typically scene\n * layout uses the LayerId taxonomy to pre-sort).\n */\nexport function buildDrawCommands(\n shapes: Shape[],\n camera: CameraTransform,\n): DrawCommandBatch[] {\n const batches = new Map<LayerId, DrawCommand[]>();\n\n function commandsFor(layer: LayerId): DrawCommand[] {\n let bucket = batches.get(layer);\n if (!bucket) {\n bucket = [];\n batches.set(layer, bucket);\n }\n return bucket;\n }\n\n function emitShape(\n shape: Shape,\n worldX: number,\n worldY: number,\n scale: number,\n offsetX: number,\n offsetY: number,\n layerOverride: LayerId | undefined,\n ) {\n const layer = shape.layer ?? layerOverride ?? DEFAULT_LAYER;\n\n switch (shape.type) {\n case \"rect\": {\n const x = (worldX + shape.x) * scale + offsetX;\n const y = (worldY + shape.y) * scale + offsetY;\n const w = shape.width * scale;\n const h = shape.height * scale;\n const cmd: RectCommand = {\n type: \"rect\",\n x, y, w, h,\n fill: shape.paint.fill,\n stroke: shape.paint.stroke,\n sw: (shape.paint.strokeWidth ?? 0) * scale,\n cr: (shape.cornerRadius ?? 0) * scale,\n dash: scaleDash(shape.paint.dash, scale, shape.paint.dashEnabled),\n };\n commandsFor(layer).push(cmd);\n return;\n }\n case \"circle\": {\n commandsFor(layer).push({\n type: \"circle\",\n cx: (worldX + shape.cx) * scale + offsetX,\n cy: (worldY + shape.cy) * scale + offsetY,\n r: shape.radius * scale,\n fill: shape.paint.fill,\n stroke: shape.paint.stroke,\n sw: (shape.paint.strokeWidth ?? 0) * scale,\n dash: scaleDash(shape.paint.dash, scale, shape.paint.dashEnabled),\n });\n return;\n }\n case \"line\": {\n const cmd: LineCommand = {\n type: \"line\",\n points: scalePoints(shape.points, worldX, worldY, scale, offsetX, offsetY),\n stroke: shape.paint.stroke,\n sw: (shape.paint.strokeWidth ?? 0) * scale,\n dash: scaleDash(shape.paint.dash, scale, shape.paint.dashEnabled),\n lineCap: shape.lineCap,\n lineJoin: shape.lineJoin,\n alpha: shape.paint.alpha,\n };\n commandsFor(layer).push(cmd);\n return;\n }\n case \"polygon\": {\n const cmd: PolygonCommand = {\n type: \"polygon\",\n vertices: scalePoints(shape.vertices, worldX, worldY, scale, offsetX, offsetY),\n fill: shape.paint.fill,\n stroke: shape.paint.stroke,\n sw: (shape.paint.strokeWidth ?? 0) * scale,\n };\n commandsFor(layer).push(cmd);\n return;\n }\n case \"text\": {\n const t = scaleTransform(shape.transform, worldX, worldY, scale, offsetX, offsetY);\n // When a transform is set the matrix carries position; the\n // command's x/y is zero so renderers don't double-translate.\n const x = t ? 0 : (worldX + shape.x) * scale + offsetX;\n const y = t ? 0 : (worldY + shape.y) * scale + offsetY;\n const cmd: TextCommand = {\n type: \"text\",\n x, y,\n text: shape.text,\n fontSize: shape.fontSize * scale,\n fontFamily: shape.fontFamily ?? \"sans-serif\",\n fontStyle: shape.fontStyle ?? \"normal\",\n fill: shape.fill ?? \"black\",\n stroke: shape.stroke,\n sw: (shape.strokeWidth ?? 0) * scale,\n align: shape.align ?? \"left\",\n vAlign: shape.verticalAlign ?? \"top\",\n w: (shape.width ?? 0) * scale,\n h: (shape.height ?? 0) * scale,\n baselineRatio: shape.baselineRatio,\n konvaCorrectionRatio: shape.konvaCorrectionRatio,\n transform: t,\n };\n commandsFor(layer).push(cmd);\n return;\n }\n case \"image\": {\n const t = scaleTransform(shape.transform, worldX, worldY, scale, offsetX, offsetY);\n const x = t ? 0 : (worldX + shape.x) * scale + offsetX;\n const y = t ? 0 : (worldY + shape.y) * scale + offsetY;\n commandsFor(layer).push({\n type: \"image\",\n x, y,\n w: shape.width * scale,\n h: shape.height * scale,\n src: shape.src,\n transform: t,\n });\n return;\n }\n case \"group\":\n emitGroup(shape, worldX, worldY, scale, offsetX, offsetY, layer);\n return;\n }\n }\n\n function emitGroup(\n group: GroupShape,\n worldX: number,\n worldY: number,\n scale: number,\n offsetX: number,\n offsetY: number,\n layerOverride: LayerId | undefined,\n ) {\n const childLayer = group.layer ?? layerOverride;\n\n if (group.noScale) {\n // Anchor the group at its scaled world position, then render\n // children in pixel space (scale = 1, offset = 0).\n const px = (worldX + group.x) * scale + offsetX;\n const py = (worldY + group.y) * scale + offsetY;\n\n const pushLayer = childLayer ?? DEFAULT_LAYER;\n const bucket = commandsFor(pushLayer);\n bucket.push({type: \"pushTransform\", matrix: [1, 0, 0, 1, px, py]});\n for (const child of group.children) {\n emitShape(child, 0, 0, 1, 0, 0, childLayer);\n }\n // popTransform must land in the same bucket as its push so\n // renderers can replay them in order. Children whose `layer`\n // diverts them elsewhere are pulled out of this push/pop scope\n // — that's the intended behaviour, since cross-layer children\n // are independent draws (e.g. a noScale label group has a\n // pixmap on the link layer plus a rect on the top layer).\n bucket.push({type: \"popTransform\"});\n return;\n }\n\n const nextX = worldX + group.x;\n const nextY = worldY + group.y;\n for (const child of group.children) {\n emitShape(child, nextX, nextY, scale, offsetX, offsetY, childLayer);\n }\n }\n\n for (const shape of shapes) {\n emitShape(shape, 0, 0, camera.scale, camera.offsetX, camera.offsetY, undefined);\n }\n\n const out: DrawCommandBatch[] = [];\n for (const [layer, commands] of batches) {\n out.push({layer, commands});\n }\n return out;\n}\n\nfunction scalePoints(\n pts: number[],\n worldX: number,\n worldY: number,\n scale: number,\n offsetX: number,\n offsetY: number,\n): number[] {\n const out = new Array<number>(pts.length);\n for (let i = 0; i < pts.length; i += 2) {\n out[i] = (worldX + pts[i]) * scale + offsetX;\n out[i + 1] = (worldY + pts[i + 1]) * scale + offsetY;\n }\n return out;\n}\n\n/** Scale dash lengths from world units to render units. `dashEnabled === false` blanks the dash. */\nfunction scaleDash(\n dash: number[] | undefined,\n scale: number,\n dashEnabled: boolean | undefined,\n): number[] | undefined {\n if (dashEnabled === false) return undefined;\n if (!dash || dash.length === 0) return dash;\n const out = new Array<number>(dash.length);\n for (let i = 0; i < dash.length; i++) out[i] = dash[i] * scale;\n return out;\n}\n\n/**\n * Apply the camera's scale + offset to a 2D affine matrix originally\n * authored in world space. Result composes camera ∘ shape on world points.\n */\nfunction scaleTransform(\n matrix: [number, number, number, number, number, number] | undefined,\n worldX: number,\n worldY: number,\n scale: number,\n offsetX: number,\n offsetY: number,\n): [number, number, number, number, number, number] | undefined {\n if (!matrix) return undefined;\n const [a, b, c, d, e, f] = matrix;\n // Camera: [scale, 0, 0, scale, worldX*scale + offsetX, worldY*scale + offsetY]\n // Composed = Camera * Shape (column-major affine).\n return [\n a * scale,\n b * scale,\n c * scale,\n d * scale,\n e * scale + worldX * scale + offsetX,\n f * scale + worldY * scale + offsetY,\n ];\n}\n","/**\n * SvgRenderer — flushes {@link DrawCommandBatch}es to a list of SVG element\n * strings.\n *\n * Coordinates in commands are already in render space (camera transform\n * applied), so primitives map directly to SVG attributes. Stack commands\n * (`pushTransform`/`popTransform`, `pushClip`/`popClip`) wrap the elements\n * emitted between them in `<g transform=…>` / `<g clip-path=…>` groups.\n */\n\nimport type {\n DrawCommand,\n DrawCommandBatch,\n PrimitiveDrawCommand,\n} from \"../draw/DrawCommand\";\n\nfunction escapeXml(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n .replace(/\"/g, \""\").replace(/'/g, \"'\");\n}\n\nfunction attr(name: string, value: string | number | undefined): string {\n if (value === undefined) return \"\";\n return ` ${name}=\"${typeof value === \"string\" ? escapeXml(value) : value}\"`;\n}\n\nfunction dashAttr(dash: number[] | undefined): string {\n if (!dash || dash.length === 0) return \"\";\n return ` stroke-dasharray=\"${dash.join(\" \")}\"`;\n}\n\n/**\n * Convert one or more {@link DrawCommandBatch}es into SVG element strings, in\n * the order they appear. Caller wraps the result in `<svg viewBox=…>` /\n * `</svg>` and inserts a background rect.\n */\nexport function svgFromBatches(batches: DrawCommandBatch[]): string[] {\n const lines: string[] = [];\n for (const batch of batches) {\n emitCommands(batch.commands, lines);\n }\n return lines;\n}\n\nfunction emitCommands(commands: DrawCommand[], out: string[]): void {\n // Walk commands sequentially. Stack commands open/close `<g>` wrappers,\n // tracked via a stack of insertion indices so the closing `</g>` is\n // appended in the correct position.\n const groupStack: string[] = [];\n\n for (const cmd of commands) {\n switch (cmd.type) {\n case \"pushTransform\": {\n const [a, b, c, d, e, f] = cmd.matrix;\n out.push(`<g transform=\"matrix(${a},${b},${c},${d},${e},${f})\">`);\n groupStack.push(\"</g>\");\n break;\n }\n case \"pushClip\": {\n // No clipPath element emitted yet — most Mudlet exporters don't\n // need it. Fall back to a transparent group so the corresponding\n // pop balances correctly.\n out.push(`<g>`);\n groupStack.push(\"</g>\");\n break;\n }\n case \"popTransform\":\n case \"popClip\": {\n const close = groupStack.pop();\n if (close) out.push(close);\n break;\n }\n default: {\n const el = svgElement(cmd);\n if (el) out.push(el);\n break;\n }\n }\n }\n\n while (groupStack.length > 0) {\n out.push(groupStack.pop()!);\n }\n}\n\nfunction svgElement(cmd: PrimitiveDrawCommand): string | undefined {\n switch (cmd.type) {\n case \"rect\": {\n const fill = cmd.fill ?? \"none\";\n const corner = cmd.cr > 0 ? `${attr(\"rx\", cmd.cr)}${attr(\"ry\", cmd.cr)}` : \"\";\n return `<rect${attr(\"x\", cmd.x)}${attr(\"y\", cmd.y)}${attr(\"width\", cmd.w)}${attr(\"height\", cmd.h)}${attr(\"fill\", fill)}${attr(\"stroke\", cmd.stroke)}${cmd.sw ? attr(\"stroke-width\", cmd.sw) : \"\"}${corner}${dashAttr(cmd.dash)}/>`;\n }\n case \"circle\": {\n const fill = cmd.fill ?? \"none\";\n return `<circle${attr(\"cx\", cmd.cx)}${attr(\"cy\", cmd.cy)}${attr(\"r\", cmd.r)}${attr(\"fill\", fill)}${attr(\"stroke\", cmd.stroke)}${cmd.sw ? attr(\"stroke-width\", cmd.sw) : \"\"}${dashAttr(cmd.dash)}/>`;\n }\n case \"line\": {\n const points = cmd.points;\n if (points.length < 2) return undefined;\n const svgPoints: string[] = [];\n for (let i = 0; i < points.length; i += 2) {\n svgPoints.push(`${points[i]},${points[i + 1]}`);\n }\n return `<polyline points=\"${svgPoints.join(\" \")}\"${attr(\"stroke\", cmd.stroke)}${cmd.sw ? attr(\"stroke-width\", cmd.sw) : \"\"}${dashAttr(cmd.dash)}${attr(\"stroke-linecap\", cmd.lineCap)}${attr(\"stroke-linejoin\", cmd.lineJoin)}${attr(\"opacity\", cmd.alpha)} fill=\"none\"/>`;\n }\n case \"polygon\": {\n const verts = cmd.vertices;\n if (verts.length < 4) return undefined;\n const svgPoints: string[] = [];\n for (let i = 0; i < verts.length; i += 2) {\n svgPoints.push(`${verts[i]},${verts[i + 1]}`);\n }\n return `<polygon points=\"${svgPoints.join(\" \")}\"${attr(\"fill\", cmd.fill)}${attr(\"stroke\", cmd.stroke)}${cmd.sw ? attr(\"stroke-width\", cmd.sw) : \"\"}/>`;\n }\n case \"text\": {\n // SVG text positioning matches the legacy SvgBackend: alignment\n // collapses width into an anchor (left/middle/end), vertical\n // centering uses either an explicit baseline ratio or the SVG\n // `dominant-baseline=\"central\"` shortcut.\n let x = cmd.x;\n let y = cmd.y;\n let anchor = \"start\";\n let baseline = \"auto\";\n\n if (cmd.w > 0) {\n if (cmd.align === \"center\") {\n x = cmd.x + cmd.w / 2;\n anchor = \"middle\";\n } else if (cmd.align === \"right\") {\n x = cmd.x + cmd.w;\n anchor = \"end\";\n }\n }\n if (cmd.h > 0 && cmd.vAlign === \"middle\") {\n if (cmd.baselineRatio !== undefined) {\n y = cmd.y + cmd.h / 2 + cmd.baselineRatio * cmd.fontSize;\n } else {\n y = cmd.y + cmd.h / 2;\n baseline = \"central\";\n }\n }\n\n const transformAttr = cmd.transform\n ? ` transform=\"matrix(${cmd.transform.join(\",\")})\"`\n : \"\";\n const weight = cmd.fontStyle === \"bold\" ? ` font-weight=\"bold\"` : \"\";\n const strokeAttr = cmd.stroke && cmd.sw > 0\n ? ` stroke=\"${cmd.stroke}\" stroke-width=\"${cmd.sw}\" paint-order=\"stroke fill\"`\n : \"\";\n return `<text${attr(\"x\", x)}${attr(\"y\", y)}${attr(\"font-size\", cmd.fontSize)}${cmd.fontFamily ? attr(\"font-family\", cmd.fontFamily) : \"\"}${weight}${attr(\"fill\", cmd.fill)}${strokeAttr} text-anchor=\"${anchor}\" dominant-baseline=\"${baseline}\"${transformAttr}>${escapeXml(cmd.text)}</text>`;\n }\n case \"image\": {\n if (cmd.transform) {\n const [a, b, c, d, e, f] = cmd.transform;\n return `<image${attr(\"width\", cmd.w)}${attr(\"height\", cmd.h)} href=\"${escapeXml(cmd.src)}\" transform=\"matrix(${a},${b},${c},${d},${e},${f})\"/>`;\n }\n return `<image${attr(\"x\", cmd.x)}${attr(\"y\", cmd.y)}${attr(\"width\", cmd.w)}${attr(\"height\", cmd.h)} href=\"${escapeXml(cmd.src)}\"/>`;\n }\n }\n}\n","/**\n * CanvasRenderer — replays {@link DrawCommandBatch}es onto a Canvas2D\n * context.\n *\n * Coordinates in commands are already in render space (camera transform\n * applied), so the renderer issues calls directly without applying any extra\n * scale or translation. Stack commands push/pop matching `ctx.save()` /\n * `ctx.restore()` pairs and apply the transform/clip on the way in.\n *\n * Used by {@link CanvasExporter} (and {@link PngBytesExporter}) so headless\n * exports rasterize through the shared {@link DrawCommandBuilder} pipeline\n * instead of grabbing the live Konva stage.\n */\n\nimport type {\n DrawCommand,\n DrawCommandBatch,\n PrimitiveDrawCommand,\n} from \"../draw/DrawCommand\";\n\n/**\n * Image loader hook. Canvas2D's `drawImage` needs an `HTMLImageElement` (or\n * compatible). Browsers create one via `new Image()`; Node-canvas provides\n * its own `Image` constructor. The default tries both — callers can override\n * for tests or unusual targets.\n */\nexport type ImageFactory = (src: string) => unknown;\n\nconst defaultImageFactory: ImageFactory = (src) => {\n if (typeof Image !== \"undefined\") {\n const img = new Image();\n img.src = src;\n return img;\n }\n return null;\n};\n\nexport interface CanvasRenderOptions {\n imageFactory?: ImageFactory;\n}\n\n/**\n * Replay every batch onto `ctx`, in order. Each batch's commands are flushed\n * sequentially; transform / clip stacks are independent per batch so a stray\n * push in one layer cannot leak into the next.\n */\nexport function renderToCanvas(\n ctx: CanvasRenderingContext2D,\n batches: DrawCommandBatch[],\n options: CanvasRenderOptions = {},\n): void {\n const imageFactory = options.imageFactory ?? defaultImageFactory;\n for (const batch of batches) {\n replayCommands(ctx, batch.commands, imageFactory);\n }\n}\n\nfunction replayCommands(\n ctx: CanvasRenderingContext2D,\n commands: DrawCommand[],\n imageFactory: ImageFactory,\n): void {\n let stackDepth = 0;\n\n for (const cmd of commands) {\n switch (cmd.type) {\n case \"pushTransform\": {\n ctx.save();\n stackDepth++;\n ctx.transform(...cmd.matrix);\n break;\n }\n case \"pushClip\": {\n ctx.save();\n stackDepth++;\n ctx.beginPath();\n ctx.rect(cmd.x, cmd.y, cmd.w, cmd.h);\n ctx.clip();\n break;\n }\n case \"popTransform\":\n case \"popClip\": {\n if (stackDepth > 0) {\n ctx.restore();\n stackDepth--;\n }\n break;\n }\n default:\n replayPrimitive(ctx, cmd, imageFactory);\n break;\n }\n }\n\n while (stackDepth > 0) {\n ctx.restore();\n stackDepth--;\n }\n}\n\nfunction replayPrimitive(\n ctx: CanvasRenderingContext2D,\n cmd: PrimitiveDrawCommand,\n imageFactory: ImageFactory,\n): void {\n switch (cmd.type) {\n case \"rect\": {\n ctx.beginPath();\n if (cmd.cr > 0 && typeof ctx.roundRect === \"function\") {\n ctx.roundRect(cmd.x, cmd.y, cmd.w, cmd.h, cmd.cr);\n } else {\n ctx.rect(cmd.x, cmd.y, cmd.w, cmd.h);\n }\n if (cmd.fill) {\n ctx.fillStyle = cmd.fill;\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n ctx.setLineDash(cmd.dash ?? []);\n ctx.stroke();\n }\n break;\n }\n case \"circle\": {\n ctx.beginPath();\n ctx.arc(cmd.cx, cmd.cy, cmd.r, 0, Math.PI * 2);\n if (cmd.fill) {\n ctx.fillStyle = cmd.fill;\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n ctx.setLineDash(cmd.dash ?? []);\n ctx.stroke();\n }\n break;\n }\n case \"line\": {\n if (cmd.points.length < 4) break;\n const savedAlpha = ctx.globalAlpha;\n if (cmd.alpha !== undefined) ctx.globalAlpha = cmd.alpha;\n ctx.beginPath();\n ctx.moveTo(cmd.points[0], cmd.points[1]);\n for (let i = 2; i < cmd.points.length; i += 2) {\n ctx.lineTo(cmd.points[i], cmd.points[i + 1]);\n }\n if (cmd.stroke) ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n ctx.setLineDash(cmd.dash ?? []);\n if (cmd.lineCap) ctx.lineCap = cmd.lineCap;\n if (cmd.lineJoin) ctx.lineJoin = cmd.lineJoin;\n ctx.stroke();\n if (cmd.alpha !== undefined) ctx.globalAlpha = savedAlpha;\n break;\n }\n case \"polygon\": {\n if (cmd.vertices.length < 4) break;\n ctx.beginPath();\n ctx.moveTo(cmd.vertices[0], cmd.vertices[1]);\n for (let i = 2; i < cmd.vertices.length; i += 2) {\n ctx.lineTo(cmd.vertices[i], cmd.vertices[i + 1]);\n }\n ctx.closePath();\n if (cmd.fill) {\n ctx.fillStyle = cmd.fill;\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n ctx.setLineDash([]);\n ctx.stroke();\n }\n break;\n }\n case \"text\": {\n // Sub-pixel font sizes break Canvas2D text metrics on some\n // engines (notably node-canvas). Render at TEXT_SCALE × the\n // requested size and counter-scale the matrix so output stays\n // pixel-correct.\n const TEXT_SCALE = 100;\n const scaledSize = cmd.fontSize * TEXT_SCALE;\n const font = `${cmd.fontStyle} ${scaledSize}px ${cmd.fontFamily}`;\n ctx.save();\n ctx.font = font;\n ctx.fillStyle = cmd.fill;\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw * TEXT_SCALE;\n ctx.lineJoin = \"round\";\n }\n const hasBaselineRatio = cmd.baselineRatio !== undefined;\n if (cmd.transform) {\n ctx.transform(...cmd.transform);\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n ctx.textAlign = \"center\";\n if (hasBaselineRatio) {\n ctx.textBaseline = \"alphabetic\";\n const by = (cmd.h / 2 + cmd.baselineRatio! * cmd.fontSize) * TEXT_SCALE;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, cmd.w * TEXT_SCALE / 2, by);\n ctx.fillText(cmd.text, cmd.w * TEXT_SCALE / 2, by);\n } else {\n ctx.textBaseline = \"middle\";\n const mx = cmd.w * TEXT_SCALE / 2;\n const my = cmd.h * TEXT_SCALE / 2;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, mx, my);\n ctx.fillText(cmd.text, mx, my);\n }\n } else if (cmd.w > 0 && cmd.h > 0) {\n ctx.textAlign = (cmd.align || \"left\");\n const tx = cmd.align === \"center\"\n ? cmd.x + cmd.w / 2\n : cmd.align === \"right\"\n ? cmd.x + cmd.w\n : cmd.x;\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n if (cmd.vAlign === \"middle\" && hasBaselineRatio) {\n ctx.textBaseline = \"alphabetic\";\n const ty = cmd.y + cmd.h / 2 + cmd.baselineRatio! * cmd.fontSize;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n ctx.fillText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n } else {\n ctx.textBaseline = cmd.vAlign === \"middle\" ? \"middle\" : \"top\";\n const ty = cmd.vAlign === \"middle\" ? cmd.y + cmd.h / 2 : cmd.y;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n ctx.fillText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n }\n } else {\n ctx.textAlign = \"left\";\n ctx.textBaseline = \"top\";\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, cmd.x * TEXT_SCALE, cmd.y * TEXT_SCALE);\n ctx.fillText(cmd.text, cmd.x * TEXT_SCALE, cmd.y * TEXT_SCALE);\n }\n ctx.restore();\n break;\n }\n case \"image\": {\n const image = imageFactory(cmd.src) as CanvasImageSource | null;\n if (!image) break;\n if (cmd.transform) {\n ctx.save();\n ctx.transform(...cmd.transform);\n ctx.drawImage(image, 0, 0, cmd.w, cmd.h);\n ctx.restore();\n } else {\n ctx.drawImage(image, cmd.x, cmd.y, cmd.w, cmd.h);\n }\n break;\n }\n }\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[]) => 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 flush(Array.isArray(out) ? out : [out]);\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","import type {Style} from \"../style/Style\";\n\nexport interface ExportBounds {\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n}\n\n/**\n * Convert world-space export bounds into the scene-space AABB an exporter\n * (SVG, canvas) should target.\n *\n * Coordinate-warping styles (e.g. Isometric) project world coords into a\n * different render space, so the SVG viewBox / canvas fit must be sized to\n * the projected region — otherwise the rendered scene drifts outside the\n * background rect and is partially clipped.\n *\n * For non-warping styles (identity, parchment, sketchy, …) `worldToScene`\n * is undefined and the bounds pass through unchanged.\n *\n * `scenePad` adds a uniform buffer in scene space — used to absorb\n * decorations the projection itself doesn't account for (cube depth in\n * Isometric, neon glow, …).\n */\nexport function projectExportBoundsToScene(\n bounds: ExportBounds,\n style: Style,\n scenePad = 0,\n): ExportBounds {\n if (!style.worldToScene) return bounds;\n const corners = [\n style.worldToScene(bounds.x, bounds.y),\n style.worldToScene(bounds.x + bounds.w, bounds.y),\n style.worldToScene(bounds.x, bounds.y + bounds.h),\n style.worldToScene(bounds.x + bounds.w, bounds.y + bounds.h),\n ];\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (const p of corners) {\n if (p.x < minX) minX = p.x;\n if (p.x > maxX) maxX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.y > maxY) maxY = p.y;\n }\n return {\n x: minX - scenePad,\n y: minY - scenePad,\n w: maxX - minX + scenePad * 2,\n h: maxY - minY + scenePad * 2,\n };\n}\n","import {ScenePipeline} from \"../ScenePipeline\";\nimport {Camera} from \"../camera/Camera\";\nimport {buildDrawCommands} from \"../draw/DrawCommandBuilder\";\nimport {svgFromBatches} from \"../render/SvgRenderer\";\nimport type {Shape} from \"../scene/Shape\";\nimport type {SvgExportOptions} from \"../SvgTypes\";\nimport {applyStyleToShapes} from \"../style/applyStyle\";\nimport {identityStyle} from \"../style/Style\";\nimport type {Style} from \"../style/Style\";\nimport type {Exporter, ExportContext} from \"./Exporter\";\nimport {flushSceneShapes} from \"./flushSceneShapes\";\nimport {clipSceneToViewport} from \"./clipSceneToViewport\";\nimport {projectExportBoundsToScene} from \"./sceneBounds\";\n\nconst IDENTITY_CAMERA = {scale: 1, offsetX: 0, offsetY: 0};\n\nfunction escapeXml(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n .replace(/\"/g, \""\").replace(/'/g, \"'\");\n}\n\n/**\n * Renders the current scene as an SVG string by driving\n * {@link ScenePipeline} → {@link buildDrawCommands} → {@link svgFromBatches}.\n *\n * The pipeline is rebuilt with bounded viewport bounds (so the grid layout\n * matches the export region), then `sceneShapes` is flushed through the\n * draw-command pipeline at scale 1 — coordinates land inside the SVG\n * viewBox in world space, identical to the legacy SvgBackend output. Active\n * {@link SceneOverlay}s are still rendered against an {@link SvgBackend}\n * so user code that builds custom overlays via the DrawingBackend keeps\n * working until the overlay API is migrated to shapes.\n */\nexport class SvgExporter implements Exporter<string | undefined> {\n constructor(private readonly options: SvgExportOptions = {}) {}\n\n render({state, style, sceneOverlays}: ExportContext): string | undefined {\n const {currentArea, currentZIndex, currentAreaInstance} = state;\n if (currentArea === undefined || currentZIndex === undefined || !currentAreaInstance) return;\n\n const area = currentAreaInstance;\n const plane = area.getPlane(currentZIndex);\n if (!plane) return;\n\n const settings = state.settings;\n const padding = this.options.padding ?? 3;\n const bounds = state.computeExportBounds(area, plane, this.options.roomId, padding);\n const exportCamera = Camera.forMapBounds(bounds.x, bounds.x + bounds.w, bounds.y, bounds.y + bounds.h);\n const viewportBounds = exportCamera.getViewportBounds();\n\n const pipeline = new ScenePipeline(state.mapReader, settings);\n const result = pipeline.buildScene(area, plane, currentZIndex, state.lens);\n const transforms = {\n forward: style.worldToScene ? (x: number, y: number) => style.worldToScene!(x, y) : undefined,\n inverse: style.sceneToWorld ? (x: number, y: number) => style.sceneToWorld!(x, y) : undefined,\n };\n const clipped = clipSceneToViewport(result, viewportBounds, settings, transforms);\n const ctx = {scale: 1, roomSize: settings.roomSize};\n const styled = (shapes: Shape[]): Shape[] =>\n style === identityStyle ? shapes : applyStyleToShapes(shapes, style as Style, ctx);\n\n // Coordinate-warping styles (Isometric) render shapes in scene space —\n // the viewBox and background rect must follow the projection so rooms\n // don't drift off the background. Scene-pad covers projection-unaware\n // decorations (cube depth, glow halos, …).\n const sceneBounds = projectExportBoundsToScene(bounds, style, settings.roomSize * 0.5);\n\n const lines: string[] = [];\n lines.push(`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"${sceneBounds.x} ${sceneBounds.y} ${sceneBounds.w} ${sceneBounds.h}\">`);\n lines.push(`<rect x=\"${sceneBounds.x}\" y=\"${sceneBounds.y}\" width=\"${sceneBounds.w}\" height=\"${sceneBounds.h}\" fill=\"${escapeXml(settings.backgroundColor)}\"/>`);\n\n const flush = (shapes: Shape[]) => {\n if (shapes.length === 0) return;\n lines.push(...svgFromBatches(buildDrawCommands(styled(shapes), IDENTITY_CAMERA)));\n };\n\n flushSceneShapes(\n clipped,\n {state, viewportBounds, sceneOverlays, overlays: this.options.overlays},\n flush,\n );\n\n lines.push(\"</svg>\");\n return lines.join(\"\\n\");\n }\n}\n","import type {Exporter, ExportContext} from \"./Exporter\";\n\nexport interface PngExportOptions {\n pixelRatio?: number;\n}\n\n/**\n * Exports the current interactive canvas as a PNG data URL. Rasterizes the\n * live Konva stage — whatever style the renderer currently has applied is\n * what gets captured.\n *\n * For re-framing with an explicit width/height (e.g. server-side rendering),\n * use {@link CanvasExporter} and serialize its output yourself.\n */\nexport class PngExporter implements Exporter<string | undefined> {\n constructor(private readonly options: PngExportOptions = {}) {}\n\n render({backend}: ExportContext): string | undefined {\n const canvas = backend.exportCanvas(this.options);\n return canvas?.toDataURL('image/png');\n }\n}\n\n/** Companion to {@link PngExporter} that returns a Blob instead of a data URL. */\nexport class PngBlobExporter implements Exporter<Promise<Blob> | undefined> {\n constructor(private readonly options: PngExportOptions = {}) {}\n\n render({backend}: ExportContext): Promise<Blob> | undefined {\n const canvas = backend.exportCanvas(this.options);\n if (!canvas || !canvas.toBlob) return;\n const toBlob = canvas.toBlob.bind(canvas);\n return new Promise<Blob>((resolve) => {\n toBlob((blob: Blob | null) => { if (blob) resolve(blob); }, 'image/png');\n });\n }\n}\n","import type {ExportCanvas} from \"./Exporter\";\n\n/**\n * Serialize an {@link ExportCanvas} to raw bytes, synchronously, using the\n * portable `toDataURL` API. Works identically in the browser and in Node.\n *\n * ```ts\n * const canvas = renderer.export(new CanvasExporter({ width, height }));\n * const png = canvasToBytes(canvas); // Uint8Array\n * fs.writeFileSync('out.png', png); // Node\n * // or: new Blob([png], { type: 'image/png' }) — browser\n * ```\n *\n * @param canvas Canvas produced by {@link CanvasExporter} or the\n * interactive backend's `exportCanvas`.\n * @param mimeType Output format. Defaults to `'image/png'`. Pass `'image/jpeg'`\n * + `quality` (0..1) for JPEG.\n * @param quality JPEG quality (ignored for PNG).\n */\nexport function canvasToBytes(\n canvas: ExportCanvas,\n mimeType: string = 'image/png',\n quality?: number,\n): Uint8Array {\n const dataUrl = canvas.toDataURL(mimeType, quality);\n const comma = dataUrl.indexOf(',');\n const base64 = comma >= 0 ? dataUrl.slice(comma + 1) : dataUrl;\n\n // `atob` is standard in browsers and available globally in Node ≥16.\n const bin = atob(base64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n","import Konva from \"konva\";\nimport {ScenePipeline} from \"../ScenePipeline\";\nimport {Camera} from \"../camera/Camera\";\nimport {applyStyleToShapes} from \"../style/applyStyle\";\nimport {identityStyle} from \"../style/Style\";\nimport type {Style} from \"../style/Style\";\nimport {buildDrawCommands} from \"../draw/DrawCommandBuilder\";\nimport {renderToCanvas} from \"../render/CanvasRenderer\";\nimport type {Shape} from \"../scene/Shape\";\nimport type {Exporter, ExportContext, ExportCanvas} from \"./Exporter\";\nimport {canvasToBytes} from \"./canvasToBytes\";\nimport {flushSceneShapes} from \"./flushSceneShapes\";\nimport {clipSceneToViewport} from \"./clipSceneToViewport\";\nimport {projectExportBoundsToScene} from \"./sceneBounds\";\n\nexport interface CanvasExportOptions {\n /** Width of the output image in pixels. */\n width: number;\n /** Height of the output image in pixels. */\n height: number;\n /** Room ID to center the export on. If omitted, exports the full area. */\n roomId?: number;\n /** Padding in map units around the exported region. Default: 3 */\n padding?: number;\n /** Overlays to render over the scene (position marker, highlights, paths). */\n overlays?: {\n position?: { roomId: number };\n highlights?: Array<{ roomId: number; color: string }>;\n paths?: Array<{ locations: number[]; color: string }>;\n };\n}\n\n/**\n * Renders the current scene into a canvas at the requested width/height by\n * driving {@link ScenePipeline} → {@link buildDrawCommands} →\n * {@link renderToCanvas}.\n *\n * Decoupled from the live Konva stage: the pipeline is rebuilt against the\n * export bounds, shapes are projected through a fitted camera transform, and\n * the result is rasterized onto a fresh 2D canvas. Background colour is\n * filled before any draw commands replay, so PNG / JPEG output looks identical\n * to the on-screen map.\n *\n * Unlike {@link PngExporter} (which captures the on-screen viewport via the\n * live Konva stage), `CanvasExporter` is fully headless and reproducible.\n */\nexport class CanvasExporter implements Exporter<ExportCanvas | undefined> {\n constructor(private readonly options: CanvasExportOptions) {}\n\n render({state, style, sceneOverlays}: ExportContext): ExportCanvas | undefined {\n const {currentArea, currentZIndex, currentAreaInstance} = state;\n if (currentArea === undefined || currentZIndex === undefined || !currentAreaInstance) return;\n\n const area = currentAreaInstance;\n const plane = area.getPlane(currentZIndex);\n if (!plane) return;\n\n const settings = state.settings;\n const {width, height} = this.options;\n const padding = this.options.padding ?? 3;\n const bounds = state.computeExportBounds(area, plane, this.options.roomId, padding);\n\n // Fit scene-space bounds (post-style-projection) into the requested\n // canvas size. Coordinate-warping styles (Isometric) render outside\n // the world AABB, so fitting world bounds clips/offsets the scene.\n const sceneBounds = projectExportBoundsToScene(bounds, style, settings.roomSize * 0.5);\n const scale = Math.min(width / sceneBounds.w, height / sceneBounds.h);\n const mapPixelW = sceneBounds.w * scale;\n const mapPixelH = sceneBounds.h * scale;\n const offsetX = (width - mapPixelW) / 2 - sceneBounds.x * scale;\n const offsetY = (height - mapPixelH) / 2 - sceneBounds.y * scale;\n const renderCam = {scale, offsetX, offsetY};\n\n // Culling must cover the full canvas, not just the tight export region.\n // Camera.forRenderCamera reproduces the letterbox-extended viewport from\n // the fitted transform, so rooms in aspect-ratio padding areas are not\n // incorrectly culled.\n const cullingCamera = Camera.forRenderCamera(width, height, scale, offsetX, offsetY);\n\n const transforms = {\n forward: style.worldToScene ? (x: number, y: number) => style.worldToScene!(x, y) : undefined,\n inverse: style.sceneToWorld ? (x: number, y: number) => style.sceneToWorld!(x, y) : undefined,\n };\n\n const pipeline = new ScenePipeline(state.mapReader, settings);\n const result = pipeline.buildScene(area, plane, currentZIndex, state.lens);\n const clipped = clipSceneToViewport(result, cullingCamera.getViewportBounds(), settings, transforms);\n\n const canvas = Konva.Util.createCanvasElement() as unknown as HTMLCanvasElement;\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n ctx.fillStyle = settings.backgroundColor;\n ctx.fillRect(0, 0, width, height);\n\n const styleCtx = {scale, roomSize: settings.roomSize};\n const styled = (shapes: Shape[]): Shape[] =>\n style === identityStyle ? shapes : applyStyleToShapes(shapes, style as Style, styleCtx);\n\n const flush = (shapes: Shape[]) => {\n if (shapes.length === 0) return;\n renderToCanvas(ctx, buildDrawCommands(styled(shapes), renderCam));\n };\n\n const viewportBounds = Camera.forMapBounds(bounds.x, bounds.x + bounds.w, bounds.y, bounds.y + bounds.h).getViewportBounds();\n flushSceneShapes(\n clipped,\n {state, viewportBounds, sceneOverlays, overlays: this.options.overlays},\n flush,\n );\n\n return canvas as unknown as ExportCanvas;\n }\n}\n\nexport interface PngBytesExportOptions extends CanvasExportOptions {\n /** MIME type to encode. Defaults to `'image/png'`. */\n mimeType?: string;\n /** Encoder quality (0..1). Only used for lossy formats like `'image/jpeg'`. */\n quality?: number;\n}\n\n/**\n * Headless PNG/JPEG bytes at a specific width × height.\n *\n * Composes {@link CanvasExporter} with a portable `toDataURL` → `Uint8Array`\n * decode, so callers get bytes directly without touching a canvas or casting\n * to platform-specific types:\n *\n * ```ts\n * const png = renderer.export(new PngBytesExporter({ width: 1920, height: 1080 }));\n * fs.writeFileSync('out.png', png!); // Node\n * new Blob([png!], { type: 'image/png' }); // Browser\n * ```\n *\n * For JPEG: `new PngBytesExporter({ width, height, mimeType: 'image/jpeg', quality: 0.9 })`.\n */\nexport class PngBytesExporter implements Exporter<Uint8Array | undefined> {\n private readonly canvasExporter: CanvasExporter;\n\n constructor(private readonly options: PngBytesExportOptions) {\n this.canvasExporter = new CanvasExporter(options);\n }\n\n render(context: ExportContext): Uint8Array | undefined {\n const canvas = this.canvasExporter.render(context);\n if (!canvas) return;\n return canvasToBytes(canvas, this.options.mimeType, this.options.quality);\n }\n}\n","/**\n * Ambient light data: a single image containing a smooth radial vignette,\n * positioned centered on the player.\n */\nexport type AmbientLightData = {\n cx: number;\n cy: number;\n displaySize: number;\n src: string;\n};\n\n/** Tunable parameters for the ambient light vignette. */\nexport type AmbientLightParams = {\n color: string;\n radius: number;\n intensity: number;\n};\n\n// --- Vignette texture cache ---\n// Regenerate only when settings or display ratio change.\nlet _cachedUrl: string | undefined;\nlet _cacheKey: string | undefined;\n\nconst TEXTURE_SIZE = 256;\n\n/**\n * Generate a vignette texture as a data URL.\n * Uses canvas 2D radial gradient + destination-out compositing to create\n * a smooth darkness overlay with a clear center — no banding.\n */\nfunction getVignetteDataUrl(intensity: number, color: string, lightRatio: number): string {\n const key = `${intensity.toFixed(2)}:${color}:${lightRatio.toFixed(3)}`;\n if (_cacheKey === key && _cachedUrl) return _cachedUrl;\n\n const size = TEXTURE_SIZE;\n const half = size / 2;\n\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d')!;\n\n // 1. Fill entire canvas with uniform darkness\n ctx.fillStyle = `rgba(0, 0, 0, ${intensity})`;\n ctx.fillRect(0, 0, size, size);\n\n // 2. Punch a smooth gradient \"hole\" using destination-out.\n // At center: remove all darkness (fully clear).\n // At light edge (lightRatio): remove nothing (stays dark).\n ctx.globalCompositeOperation = 'destination-out';\n\n const gradient = ctx.createRadialGradient(half, half, 0, half, half, half);\n const lr = Math.min(lightRatio, 0.99);\n\n gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');\n gradient.addColorStop(lr * 0.25, 'rgba(0, 0, 0, 0.97)');\n gradient.addColorStop(lr * 0.50, 'rgba(0, 0, 0, 0.82)');\n gradient.addColorStop(lr * 0.70, 'rgba(0, 0, 0, 0.50)');\n gradient.addColorStop(lr * 0.85, 'rgba(0, 0, 0, 0.22)');\n gradient.addColorStop(lr * 0.95, 'rgba(0, 0, 0, 0.06)');\n gradient.addColorStop(lr, 'rgba(0, 0, 0, 0)');\n if (lr < 0.98) {\n gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');\n }\n\n ctx.fillStyle = gradient;\n ctx.beginPath();\n ctx.arc(half, half, half, 0, Math.PI * 2);\n ctx.fill();\n\n // 3. Optional: subtle colored glow in the center\n ctx.globalCompositeOperation = 'source-over';\n const cr = parseInt(color.slice(1, 3), 16);\n const cg = parseInt(color.slice(3, 5), 16);\n const cb = parseInt(color.slice(5, 7), 16);\n\n const tintGradient = ctx.createRadialGradient(half, half, 0, half, half, half * lr);\n tintGradient.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, 0.05)`);\n tintGradient.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);\n ctx.fillStyle = tintGradient;\n ctx.fillRect(0, 0, size, size);\n\n _cachedUrl = canvas.toDataURL();\n _cacheKey = key;\n return _cachedUrl;\n}\n\n/**\n * Compute ambient light vignette for the given player position and viewport.\n *\n * Produces a single image overlay containing a smooth radial gradient:\n * clear at center → full darkness at edges. The image is sized to always\n * cover the entire visible viewport with margin.\n */\nexport function computeAmbientLight(\n cx: number,\n cy: number,\n viewportBounds: { minX: number; maxX: number; minY: number; maxY: number },\n params: AmbientLightParams,\n): AmbientLightData {\n const {radius, intensity, color} = params;\n\n const vw = viewportBounds.maxX - viewportBounds.minX;\n const vh = viewportBounds.maxY - viewportBounds.minY;\n const viewportDiag = Math.sqrt(vw * vw + vh * vh);\n\n // Display size: large enough that the dark edges always cover the full viewport.\n // The image is square and centered on the player, so corner distance = displaySize * √2 / 2.\n const displaySize = Math.max(viewportDiag * 2.5, radius * 4);\n\n // What fraction of the image half-diagonal corresponds to the light radius\n const lightRatio = Math.round(radius / (displaySize / 2) * 50) / 50; // snap to 0.02 steps for caching\n\n const src = getVignetteDataUrl(intensity, color, lightRatio);\n\n return {cx, cy, displaySize, src};\n}\n","import type {MapState} from \"../MapState\";\nimport type {ViewportBounds} from \"../types/Settings\";\nimport {computeAmbientLight, type AmbientLightParams} from \"../scene/AmbientLightStyle\";\nimport type {Shape} from \"../scene/Shape\";\nimport type {SceneOverlay, SceneOverlayContext} from \"./SceneOverlay\";\n\nexport type AmbientLightOptions = Partial<AmbientLightParams>;\n\nconst DEFAULTS: AmbientLightParams = {\n color: '#ffcc44',\n radius: 12,\n intensity: 0.7,\n};\n\n/**\n * Vignette centered on the player room. Re-renders on position changes and\n * viewport changes (pan/zoom/resize). Registering the overlay turns the effect\n * on; removing it turns it off — there is no global settings flag.\n *\n * ```ts\n * const ambient = new AmbientLightOverlay({ color: '#ffcc44', radius: 12 });\n * renderer.addSceneOverlay('ambient-light', ambient);\n * // later:\n * ambient.setOptions({ intensity: 0.9 });\n * renderer.removeSceneOverlay('ambient-light');\n * ```\n */\nexport class AmbientLightOverlay implements SceneOverlay {\n private params: AmbientLightParams;\n private ctx?: SceneOverlayContext;\n private viewportUnsub?: () => void;\n private readonly onPosition = () => this.ctx?.invalidate();\n\n constructor(options?: AmbientLightOptions) {\n this.params = {...DEFAULTS, ...options};\n }\n\n /** Update tunables at runtime. Triggers a re-render. */\n setOptions(options: AmbientLightOptions): void {\n this.params = {...this.params, ...options};\n this.ctx?.invalidate();\n }\n\n getOptions(): AmbientLightParams {\n return {...this.params};\n }\n\n attach(ctx: SceneOverlayContext): void {\n this.ctx = ctx;\n ctx.state.events.on('position', this.onPosition);\n this.viewportUnsub = ctx.onViewportChange(() => ctx.invalidate());\n }\n\n detach(): void {\n this.ctx?.state.events.off('position', this.onPosition);\n this.viewportUnsub?.();\n this.viewportUnsub = undefined;\n this.ctx = undefined;\n }\n\n render(state: MapState, bounds: ViewportBounds): Shape | void {\n if (state.positionRoomId === undefined) return;\n const room = state.mapReader.getRoom(state.positionRoomId);\n if (!room) return;\n const data = computeAmbientLight(room.x, room.y, bounds, this.params);\n return {\n type: \"image\",\n x: data.cx - data.displaySize / 2,\n y: data.cy - data.displaySize / 2,\n width: data.displaySize,\n height: data.displaySize,\n src: data.src,\n layer: \"overlay\",\n };\n }\n}\n","/**\n * This very basic implementation of a priority queue is used to select the\n * next node of the graph to walk to.\n *\n * The queue is always sorted to have the least expensive node on top.\n * Some helper methods are also implemented.\n *\n * You should **never** modify the queue directly, but only using the methods\n * provided by the class.\n */\nclass PriorityQueue {\n /**\n * Creates a new empty priority queue\n */\n constructor() {\n // The `keys` set is used to greatly improve the speed at which we can\n // check the presence of a value in the queue\n this.keys = new Set();\n this.queue = [];\n }\n\n /**\n * Sort the queue to have the least expensive node to visit on top\n *\n * @private\n */\n sort() {\n this.queue.sort((a, b) => a.priority - b.priority);\n }\n\n /**\n * Sets a priority for a key in the queue.\n * Inserts it in the queue if it does not already exists.\n *\n * @param {any} key Key to update or insert\n * @param {number} value Priority of the key\n * @return {number} Size of the queue\n */\n set(key, value) {\n const priority = Number(value);\n if (isNaN(priority)) throw new TypeError('\"priority\" must be a number');\n\n if (!this.keys.has(key)) {\n // Insert a new entry if the key is not already in the queue\n this.keys.add(key);\n this.queue.push({ key, priority });\n } else {\n // Update the priority of an existing key\n this.queue.map((element) => {\n if (element.key === key) {\n Object.assign(element, { priority });\n }\n\n return element;\n });\n }\n\n this.sort();\n return this.queue.length;\n }\n\n /**\n * The next method is used to dequeue a key:\n * It removes the first element from the queue and returns it\n *\n * @return {object} First priority queue entry\n */\n next() {\n const element = this.queue.shift();\n\n // Remove the key from the `_keys` set\n this.keys.delete(element.key);\n\n return element;\n }\n\n /**\n * @return {boolean} `true` when the queue is empty\n */\n isEmpty() {\n return Boolean(this.queue.length === 0);\n }\n\n /**\n * Check if the queue has a key in it\n *\n * @param {any} key Key to lookup\n * @return {boolean}\n */\n has(key) {\n return this.keys.has(key);\n }\n\n /**\n * Get the element in the queue with the specified key\n *\n * @param {any} key Key to lookup\n * @return {object}\n */\n get(key) {\n return this.queue.find((element) => element.key === key);\n }\n}\n\nmodule.exports = PriorityQueue;\n","/**\n * Removes a key and all of its references from a map.\n * This function has no side-effects as it returns\n * a brand new map.\n *\n * @param {Map} map - Map to remove the key from\n * @param {string} key - Key to remove from the map\n * @return {Map} New map without the passed key\n */\nfunction removeDeepFromMap(map, key) {\n const newMap = new Map();\n\n for (const [aKey, val] of map) {\n if (aKey !== key && val instanceof Map) {\n newMap.set(aKey, removeDeepFromMap(val, key));\n } else if (aKey !== key) {\n newMap.set(aKey, val);\n }\n }\n\n return newMap;\n}\n\nmodule.exports = removeDeepFromMap;\n","/**\n * Validates a cost for a node\n *\n * @private\n * @param {number} val - Cost to validate\n * @return {bool}\n */\nfunction isValidNode(val) {\n const cost = Number(val);\n\n if (isNaN(cost) || cost <= 0) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Creates a deep `Map` from the passed object.\n *\n * @param {Object} source - Object to populate the map with\n * @return {Map} New map with the passed object data\n */\nfunction toDeepMap(source) {\n const map = new Map();\n const keys = Object.keys(source);\n\n keys.forEach((key) => {\n const val = source[key];\n\n if (val !== null && typeof val === \"object\" && !Array.isArray(val)) {\n return map.set(key, toDeepMap(val));\n }\n\n if (!isValidNode(val)) {\n throw new Error(\n `Could not add node at key \"${key}\", make sure it's a valid node`,\n val\n );\n }\n\n return map.set(key, Number(val));\n });\n\n return map;\n}\n\nmodule.exports = toDeepMap;\n","/**\n * Validate a map to ensure all it's values are either a number or a map\n *\n * @param {Map} map - Map to valiadte\n */\nfunction validateDeep(map) {\n if (!(map instanceof Map)) {\n throw new Error(`Invalid graph: Expected Map instead found ${typeof map}`);\n }\n\n map.forEach((value, key) => {\n if (typeof value === \"object\" && value instanceof Map) {\n validateDeep(value);\n return;\n }\n\n if (typeof value !== \"number\" || value <= 0) {\n throw new Error(\n `Values must be numbers greater than 0. Found value ${value} at ${key}`\n );\n }\n });\n}\n\nmodule.exports = validateDeep;\n","const Queue = require(\"./PriorityQueue\");\nconst removeDeepFromMap = require(\"./removeDeepFromMap\");\nconst toDeepMap = require(\"./toDeepMap\");\nconst validateDeep = require(\"./validateDeep\");\n\n/** Creates and manages a graph */\nclass Graph {\n /**\n * Creates a new Graph, optionally initializing it a nodes graph representation.\n *\n * A graph representation is an object that has as keys the name of the point and as values\n * the points reacheable from that node, with the cost to get there:\n *\n * {\n * node (Number|String): {\n * neighbor (Number|String): cost (Number),\n * ...,\n * },\n * }\n *\n * In alternative to an object, you can pass a `Map` of `Map`. This will\n * allow you to specify numbers as keys.\n *\n * @param {Objec|Map} [graph] - Initial graph definition\n * @example\n *\n * const route = new Graph();\n *\n * // Pre-populated graph\n * const route = new Graph({\n * A: { B: 1 },\n * B: { A: 1, C: 2, D: 4 },\n * });\n *\n * // Passing a Map\n * const g = new Map()\n *\n * const a = new Map()\n * a.set('B', 1)\n *\n * const b = new Map()\n * b.set('A', 1)\n * b.set('C', 2)\n * b.set('D', 4)\n *\n * g.set('A', a)\n * g.set('B', b)\n *\n * const route = new Graph(g)\n */\n constructor(graph) {\n if (graph instanceof Map) {\n validateDeep(graph);\n this.graph = graph;\n } else if (graph) {\n this.graph = toDeepMap(graph);\n } else {\n this.graph = new Map();\n }\n }\n\n /**\n * Adds a node to the graph\n *\n * @param {string} name - Name of the node\n * @param {Object|Map} neighbors - Neighbouring nodes and cost to reach them\n * @return {this}\n * @example\n *\n * const route = new Graph();\n *\n * route.addNode('A', { B: 1 });\n *\n * // It's possible to chain the calls\n * route\n * .addNode('B', { A: 1 })\n * .addNode('C', { A: 3 });\n *\n * // The neighbors can be expressed in a Map\n * const d = new Map()\n * d.set('A', 2)\n * d.set('B', 8)\n *\n * route.addNode('D', d)\n */\n addNode(name, neighbors) {\n let nodes;\n if (neighbors instanceof Map) {\n validateDeep(neighbors);\n nodes = neighbors;\n } else {\n nodes = toDeepMap(neighbors);\n }\n\n this.graph.set(name, nodes);\n\n return this;\n }\n\n /**\n * @deprecated since version 2.0, use `Graph#addNode` instead\n */\n addVertex(name, neighbors) {\n return this.addNode(name, neighbors);\n }\n\n /**\n * Removes a node and all of its references from the graph\n *\n * @param {string|number} key - Key of the node to remove from the graph\n * @return {this}\n * @example\n *\n * const route = new Graph({\n * A: { B: 1, C: 5 },\n * B: { A: 3 },\n * C: { B: 2, A: 2 },\n * });\n *\n * route.removeNode('C');\n * // The graph now is:\n * // { A: { B: 1 }, B: { A: 3 } }\n */\n removeNode(key) {\n this.graph = removeDeepFromMap(this.graph, key);\n\n return this;\n }\n\n /**\n * Compute the shortest path between the specified nodes\n *\n * @param {string} start - Starting node\n * @param {string} goal - Node we want to reach\n * @param {object} [options] - Options\n *\n * @param {boolean} [options.trim] - Exclude the origin and destination nodes from the result\n * @param {boolean} [options.reverse] - Return the path in reversed order\n * @param {boolean} [options.cost] - Also return the cost of the path when set to true\n *\n * @return {array|object} Computed path between the nodes.\n *\n * When `option.cost` is set to true, the returned value will be an object with shape:\n * - `path` *(Array)*: Computed path between the nodes\n * - `cost` *(Number)*: Cost of the path\n *\n * @example\n *\n * const route = new Graph()\n *\n * route.addNode('A', { B: 1 })\n * route.addNode('B', { A: 1, C: 2, D: 4 })\n * route.addNode('C', { B: 2, D: 1 })\n * route.addNode('D', { C: 1, B: 4 })\n *\n * route.path('A', 'D') // => ['A', 'B', 'C', 'D']\n *\n * // trimmed\n * route.path('A', 'D', { trim: true }) // => [B', 'C']\n *\n * // reversed\n * route.path('A', 'D', { reverse: true }) // => ['D', 'C', 'B', 'A']\n *\n * // include the cost\n * route.path('A', 'D', { cost: true })\n * // => {\n * // path: [ 'A', 'B', 'C', 'D' ],\n * // cost: 4\n * // }\n */\n path(start, goal, options = {}) {\n // Don't run when we don't have nodes set\n if (!this.graph.size) {\n if (options.cost) return { path: null, cost: 0 };\n\n return null;\n }\n\n const explored = new Set();\n const frontier = new Queue();\n const previous = new Map();\n\n let path = [];\n let totalCost = 0;\n\n let avoid = [];\n if (options.avoid) avoid = [].concat(options.avoid);\n\n if (avoid.includes(start)) {\n throw new Error(`Starting node (${start}) cannot be avoided`);\n } else if (avoid.includes(goal)) {\n throw new Error(`Ending node (${goal}) cannot be avoided`);\n }\n\n // Add the starting point to the frontier, it will be the first node visited\n frontier.set(start, 0);\n\n // Run until we have visited every node in the frontier\n while (!frontier.isEmpty()) {\n // Get the node in the frontier with the lowest cost (`priority`)\n const node = frontier.next();\n\n // When the node with the lowest cost in the frontier in our goal node,\n // we can compute the path and exit the loop\n if (node.key === goal) {\n // Set the total cost to the current value\n totalCost = node.priority;\n\n let nodeKey = node.key;\n while (previous.has(nodeKey)) {\n path.push(nodeKey);\n nodeKey = previous.get(nodeKey);\n }\n\n break;\n }\n\n // Add the current node to the explored set\n explored.add(node.key);\n\n // Loop all the neighboring nodes\n const neighbors = this.graph.get(node.key) || new Map();\n neighbors.forEach((nCost, nNode) => {\n // If we already explored the node, or the node is to be avoided, skip it\n if (explored.has(nNode) || avoid.includes(nNode)) return null;\n\n // If the neighboring node is not yet in the frontier, we add it with\n // the correct cost\n if (!frontier.has(nNode)) {\n previous.set(nNode, node.key);\n return frontier.set(nNode, node.priority + nCost);\n }\n\n const frontierPriority = frontier.get(nNode).priority;\n const nodeCost = node.priority + nCost;\n\n // Otherwise we only update the cost of this node in the frontier when\n // it's below what's currently set\n if (nodeCost < frontierPriority) {\n previous.set(nNode, node.key);\n return frontier.set(nNode, nodeCost);\n }\n\n return null;\n });\n }\n\n // Return null when no path can be found\n if (!path.length) {\n if (options.cost) return { path: null, cost: 0 };\n\n return null;\n }\n\n // From now on, keep in mind that `path` is populated in reverse order,\n // from destination to origin\n\n // Remove the first value (the goal node) if we want a trimmed result\n if (options.trim) {\n path.shift();\n } else {\n // Add the origin waypoint at the end of the array\n path = path.concat([start]);\n }\n\n // Reverse the path if we don't want it reversed, so the result will be\n // from `start` to `goal`\n if (!options.reverse) {\n path = path.reverse();\n }\n\n // Return an object if we also want the cost\n if (options.cost) {\n return {\n path,\n cost: totalCost,\n };\n }\n\n return path;\n }\n\n /**\n * @deprecated since version 2.0, use `Graph#path` instead\n */\n shortestPath(...args) {\n return this.path(...args);\n }\n}\n\nmodule.exports = Graph;\n","import type {IMapReader} from \"./reader/MapReader\";\n\nconst exitNumberToDirection: 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\nconst directionToExitWeightKey: Record<MapData.direction, string> = {\n north: \"n\", northeast: \"ne\", northwest: \"nw\",\n east: \"e\", west: \"w\",\n south: \"s\", southeast: \"se\", southwest: \"sw\",\n up: \"up\", down: \"down\", in: \"in\", out: \"out\",\n};\n\nexport interface Edge {\n id: number;\n weight: number;\n}\n\nexport interface GraphData {\n adj: Map<number, Edge[]>;\n /** For node-dijkstra library */\n graphDefinition: Record<string, Record<string, number>>;\n maxEdgeDistance: number;\n minEdgeWeight: number;\n}\n\n/**\n * Builds a weighted adjacency graph from MapReader room/exit data.\n * Separated from PathFinder so the graph can be reused and tested independently.\n */\nexport class MapGraph {\n\n private readonly mapReader: IMapReader;\n private readonly data: GraphData;\n\n constructor(mapReader: IMapReader) {\n this.mapReader = mapReader;\n this.data = this.buildGraph();\n }\n\n getAdj(): Map<number, Edge[]> {\n return this.data.adj;\n }\n\n getGraphDefinition(): Record<string, Record<string, number>> {\n return this.data.graphDefinition;\n }\n\n getMaxEdgeDistance(): number {\n return this.data.maxEdgeDistance;\n }\n\n getMinEdgeWeight(): number {\n return this.data.minEdgeWeight;\n }\n\n getRoom(roomId: number) {\n return this.mapReader.getRoom(roomId);\n }\n\n private resolveEdgeWeight(room: MapData.Room, exitWeightKey: string, target: MapData.Room): number {\n const exitWeight = room.exitWeights?.[exitWeightKey];\n if (exitWeight !== undefined && exitWeight > 0) return exitWeight;\n return Math.max(target.weight, 1);\n }\n\n private buildGraph(): GraphData {\n const adj = new Map<number, Edge[]>();\n const graphDefinition: Record<string, Record<string, number>> = {};\n let maxEdgeDist = 1;\n let minEdgeWeight = Infinity;\n\n this.mapReader.getRooms().forEach(room => {\n const edges: Edge[] = [];\n const connections: Record<string, number> = {};\n\n const lockedDirections = new Set(\n (room.exitLocks ?? [])\n .map(lockId => exitNumberToDirection[lockId])\n .filter((direction): direction is MapData.direction => Boolean(direction))\n );\n\n const lockedSpecialTargets = new Set(room.mSpecialExitLocks ?? []);\n\n Object.entries(room.exits ?? {}).forEach(([direction, targetRoomId]) => {\n if (lockedDirections.has(direction as MapData.direction)) return;\n const target = this.mapReader.getRoom(targetRoomId);\n if (target) {\n const weightKey = directionToExitWeightKey[direction as MapData.direction] ?? direction;\n const weight = this.resolveEdgeWeight(room, weightKey, target);\n edges.push({id: targetRoomId, weight});\n connections[targetRoomId.toString()] = weight;\n if (weight < minEdgeWeight) minEdgeWeight = weight;\n const dx = target.x - room.x;\n const dy = target.y - room.y;\n const dz = target.z - room.z;\n const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);\n if (dist > maxEdgeDist) maxEdgeDist = dist;\n }\n });\n\n Object.entries(room.specialExits ?? {}).forEach(([exitCommand, targetRoomId]) => {\n if (lockedSpecialTargets.has(targetRoomId)) return;\n const target = this.mapReader.getRoom(targetRoomId);\n if (target) {\n const weight = this.resolveEdgeWeight(room, exitCommand, target);\n edges.push({id: targetRoomId, weight});\n connections[targetRoomId.toString()] = weight;\n if (weight < minEdgeWeight) minEdgeWeight = weight;\n const dx = target.x - room.x;\n const dy = target.y - room.y;\n const dz = target.z - room.z;\n const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);\n if (dist > maxEdgeDist) maxEdgeDist = dist;\n }\n });\n\n adj.set(room.id, edges);\n graphDefinition[room.id.toString()] = connections;\n });\n\n if (!isFinite(minEdgeWeight)) minEdgeWeight = 1;\n\n return {adj, graphDefinition, maxEdgeDistance: maxEdgeDist, minEdgeWeight};\n }\n}\n","import Graph from \"node-dijkstra\";\nimport type {IMapReader} from \"./reader/MapReader\";\nimport {MapGraph} from \"./MapGraph\";\nimport type {Edge} from \"./MapGraph\";\n\nexport type PathFindingAlgorithm = 'dijkstra' | 'astar';\n\n// --- Min-heap for A* ---\n\ninterface HeapEntry {\n id: number;\n priority: number;\n}\n\nfunction heapPush(heap: HeapEntry[], entry: HeapEntry) {\n heap.push(entry);\n let i = heap.length - 1;\n while (i > 0) {\n const parent = (i - 1) >> 1;\n if (heap[parent].priority <= heap[i].priority) break;\n [heap[parent], heap[i]] = [heap[i], heap[parent]];\n i = parent;\n }\n}\n\nfunction heapPop(heap: HeapEntry[]): HeapEntry | undefined {\n if (heap.length === 0) return undefined;\n const top = heap[0];\n const last = heap.pop()!;\n if (heap.length > 0) {\n heap[0] = last;\n let i = 0;\n const n = heap.length;\n while (true) {\n let smallest = i;\n const left = 2 * i + 1;\n const right = 2 * i + 2;\n if (left < n && heap[left].priority < heap[smallest].priority) smallest = left;\n if (right < n && heap[right].priority < heap[smallest].priority) smallest = right;\n if (smallest === i) break;\n [heap[i], heap[smallest]] = [heap[smallest], heap[i]];\n i = smallest;\n }\n }\n return top;\n}\n\n// --- Algorithm implementations ---\n\nfunction reconstructPath(cameFrom: Map<number, number>, from: number, to: number): number[] {\n const path: number[] = [to];\n let current = to;\n while (current !== from) {\n current = cameFrom.get(current)!;\n path.push(current);\n }\n path.reverse();\n return path;\n}\n\nfunction findPathDijkstra(graph: Graph, from: number, to: number): number[] | null {\n const path = graph.path(from.toString(), to.toString());\n const nodes = Array.isArray(path) ? path : path?.path;\n return nodes ? nodes.map((id: string) => Number(id)) : null;\n}\n\nfunction findPathAStar(\n adj: Map<number, Edge[]>,\n from: number,\n to: number,\n mapGraph: MapGraph,\n): number[] | null {\n const goalRoom = mapGraph.getRoom(to);\n if (!goalRoom) return null;\n const goalX = goalRoom.x;\n const goalY = goalRoom.y;\n const goalZ = goalRoom.z;\n const maxEdgeDistance = mapGraph.getMaxEdgeDistance();\n const minEdgeWeight = mapGraph.getMinEdgeWeight();\n\n const heuristic = (roomId: number): number => {\n const room = mapGraph.getRoom(roomId);\n if (!room) return 0;\n const dx = room.x - goalX;\n const dy = room.y - goalY;\n const dz = room.z - goalZ;\n return (Math.sqrt(dx * dx + dy * dy + dz * dz) / maxEdgeDistance) * minEdgeWeight;\n };\n\n const gScore = new Map<number, number>();\n const cameFrom = new Map<number, number>();\n const heap: HeapEntry[] = [];\n\n gScore.set(from, 0);\n heapPush(heap, {id: from, priority: heuristic(from)});\n\n while (heap.length > 0) {\n const {id: current} = heapPop(heap)!;\n if (current === to) return reconstructPath(cameFrom, from, to);\n\n const currentG = gScore.get(current) ?? Infinity;\n\n const edges = adj.get(current);\n if (!edges) continue;\n for (const edge of edges) {\n const nextG = currentG + edge.weight;\n if (nextG < (gScore.get(edge.id) ?? Infinity)) {\n gScore.set(edge.id, nextG);\n cameFrom.set(edge.id, current);\n heapPush(heap, {id: edge.id, priority: nextG + heuristic(edge.id)});\n }\n }\n }\n return null;\n}\n\n// --- PathFinder ---\n\nexport default class PathFinder {\n\n private readonly mapGraph: MapGraph;\n private readonly dijkstraGraph: Graph;\n private _algorithm: PathFindingAlgorithm;\n private readonly cache = new Map<string, number[] | null>();\n\n constructor(mapReader: IMapReader, algorithm: PathFindingAlgorithm = 'dijkstra') {\n this._algorithm = algorithm;\n this.mapGraph = new MapGraph(mapReader);\n this.dijkstraGraph = new Graph(this.mapGraph.getGraphDefinition());\n }\n\n get algorithm(): PathFindingAlgorithm {\n return this._algorithm;\n }\n\n setAlgorithm(algorithm: PathFindingAlgorithm): void {\n if (algorithm === this._algorithm) return;\n this._algorithm = algorithm;\n this.cache.clear();\n }\n\n findPath(from: number, to: number): number[] | null {\n const cacheKey = `${from}->${to}`;\n if (this.cache.has(cacheKey)) {\n return this.cache.get(cacheKey)!;\n }\n\n if (from === to) {\n const result = this.mapGraph.getRoom(from) ? [from] : null;\n this.cache.set(cacheKey, result);\n return result;\n }\n\n if (!this.mapGraph.getRoom(from) || !this.mapGraph.getRoom(to)) {\n this.cache.set(cacheKey, null);\n return null;\n }\n\n let result: number[] | null;\n switch (this._algorithm) {\n case 'dijkstra':\n result = findPathDijkstra(this.dijkstraGraph, from, to);\n break;\n case 'astar':\n result = findPathAStar(this.mapGraph.getAdj(), from, to, this.mapGraph);\n break;\n }\n\n this.cache.set(cacheKey, result);\n return result;\n }\n}\n","import type {RoomLens, ExitTreatment} from \"./RoomLens\";\nimport {defaultExitTreatment} from \"./RoomLens\";\nimport type IExit from \"../reader/Exit\";\n\n/**\n * How a composite lens reconciles disagreeing children when both expose\n * `getExitTreatment`.\n *\n * - `\"most-restrictive\"` (default): `hidden` > `stub` > `full`. A single\n * child that says hide wins. Safe default for stacked visibility filters.\n * - `\"least-restrictive\"`: `full` > `stub` > `hidden`. Useful when lenses are\n * additive scopes (\"show guild rooms OR quest rooms\").\n * - `\"first\"`: the first child whose `getExitTreatment` returns non-undefined\n * decides. Lets the caller pick a dominant lens by ordering.\n */\nexport type ExitConflictStrategy = \"most-restrictive\" | \"least-restrictive\" | \"first\";\n\nexport type ComposeOptions = {\n /** Defaults to `\"most-restrictive\"`. */\n exitStrategy?: ExitConflictStrategy;\n /**\n * AND (visibility must hold in every lens) vs OR (any lens may grant\n * visibility). Defaults to `\"and\"` — restrictive intersection.\n */\n visibility?: \"and\" | \"or\";\n};\n\nconst TREATMENT_ORDER: Record<ExitTreatment, number> = {\n hidden: 2,\n stub: 1,\n full: 0,\n};\n\nfunction pickTreatment(\n a: ExitTreatment,\n b: ExitTreatment,\n strategy: ExitConflictStrategy,\n): ExitTreatment {\n if (strategy === \"first\") return a;\n if (strategy === \"least-restrictive\") {\n return TREATMENT_ORDER[a] < TREATMENT_ORDER[b] ? a : b;\n }\n return TREATMENT_ORDER[a] > TREATMENT_ORDER[b] ? a : b;\n}\n\n/**\n * Combine multiple lenses into one. Visibility uses AND by default (a room\n * must pass every lens to render); exit treatment uses most-restrictive when\n * children disagree. Both behaviours are configurable via {@link ComposeOptions}.\n *\n * Composing zero lenses returns a no-op equivalent to `ALL_VISIBLE`.\n */\nexport function composeLenses(...lenses: RoomLens[]): RoomLens;\nexport function composeLenses(options: ComposeOptions, ...lenses: RoomLens[]): RoomLens;\nexport function composeLenses(\n first?: ComposeOptions | RoomLens,\n ...rest: RoomLens[]\n): RoomLens {\n let options: ComposeOptions = {};\n let lenses: RoomLens[];\n if (first && typeof (first as RoomLens).isVisible === \"function\") {\n lenses = [first as RoomLens, ...rest];\n } else {\n options = (first as ComposeOptions) ?? {};\n lenses = rest;\n }\n\n const strategy = options.exitStrategy ?? \"most-restrictive\";\n const visibilityMode = options.visibility ?? \"and\";\n\n if (lenses.length === 0) {\n return {isVisible: () => true, getVersion: () => 0};\n }\n if (lenses.length === 1) {\n return lenses[0];\n }\n\n return {\n isVisible(room: MapData.Room) {\n if (visibilityMode === \"or\") {\n return lenses.some(l => l.isVisible(room));\n }\n return lenses.every(l => l.isVisible(room));\n },\n getExitTreatment(exit: IExit, a: MapData.Room, b: MapData.Room): ExitTreatment {\n let chosen: ExitTreatment | undefined;\n for (const lens of lenses) {\n const t = lens.getExitTreatment\n ? lens.getExitTreatment(exit, a, b)\n : defaultExitTreatment(lens, exit, a, b);\n if (chosen === undefined) {\n chosen = t;\n } else {\n chosen = pickTreatment(chosen, t, strategy);\n if (strategy === \"first\") break;\n }\n }\n return chosen ?? \"full\";\n },\n getVersion() {\n // Sum of children's versions — any child bump bumps the composite.\n let v = 0;\n for (const lens of lenses) v += lens.getVersion();\n return v;\n },\n };\n}\n","import type {RoomLens} from \"./RoomLens\";\n\n/**\n * Fog-of-war lens: rooms render only when their id is in the visited set.\n *\n * Default exit treatment is left to {@link RoomLens}'s built-in derivation\n * (both visible → full, one visible → stub, neither → hidden), which produces\n * the \"explored frontier\" look without any extra wiring.\n *\n * Mutations bump {@link getVersion} so a composing renderer can detect the\n * change; the renderer still needs an explicit `refresh()` after mutation —\n * the lens doesn't emit events.\n */\nexport class ExplorationLens implements RoomLens {\n private readonly visited: Set<number>;\n private version = 0;\n\n constructor(visited?: Iterable<number>) {\n this.visited = visited instanceof Set\n ? new Set(visited)\n : new Set(visited ?? []);\n }\n\n isVisible(room: MapData.Room): boolean {\n return this.visited.has(room.id);\n }\n\n getVersion(): number {\n return this.version;\n }\n\n hasVisited(roomId: number): boolean {\n return this.visited.has(roomId);\n }\n\n getVisitedRoomIds(): number[] {\n return [...this.visited];\n }\n\n getVisitedCount(): number {\n return this.visited.size;\n }\n\n /** Returns true if the room was newly added. */\n addVisited(roomId: number): boolean {\n if (this.visited.has(roomId)) return false;\n this.visited.add(roomId);\n this.version++;\n return true;\n }\n\n /** Returns the number of newly added rooms. */\n addVisitedAll(roomIds: Iterable<number>): number {\n let added = 0;\n for (const id of roomIds) {\n if (!this.visited.has(id)) {\n this.visited.add(id);\n added++;\n }\n }\n if (added > 0) this.version++;\n return added;\n }\n\n /** Replace the visited set wholesale. */\n setVisited(roomIds: Iterable<number>): void {\n this.visited.clear();\n for (const id of roomIds) this.visited.add(id);\n this.version++;\n }\n\n clear(): void {\n if (this.visited.size === 0) return;\n this.visited.clear();\n this.version++;\n }\n}\n","import Konva from \"konva\";\nimport type {IMapReader} from \"./reader/MapReader\";\nimport {PlanarDirection, planarDirections, oppositeDirections} from \"./directions\";\n\nconst directionNumberToName: Record<number, MapData.direction> = {\n 1: \"north\",\n 2: \"northeast\",\n 3: \"northwest\",\n 4: \"east\",\n 5: \"west\",\n 6: \"south\",\n 7: \"southeast\",\n 8: \"southwest\",\n 9: \"up\",\n 10: \"down\",\n 11: \"in\",\n 12: \"out\",\n};\n\ntype AreaConnection = {\n fromAreaId: number;\n toAreaId: number;\n fromRoomId: number;\n toRoomId: number;\n direction: PlanarDirection | null;\n fromRoomPosition: {x: number; y: number};\n toRoomPosition: {x: number; y: number};\n};\n\ntype AreaNode = {\n areaId: number;\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n connections: AreaConnection[];\n roomCount: number;\n // Real world center position (from room coordinates)\n realCenterX: number;\n realCenterY: number;\n};\n\ntype ConnectionGroup = {\n fromAreaId: number;\n toAreaId: number;\n connections: AreaConnection[];\n primaryDirection: PlanarDirection | null;\n averageOffset: {x: number; y: number};\n};\n\nexport type AreaDomainInfo = {\n isIshtar: boolean;\n isEmpire: boolean;\n};\n\nexport type DomainFilter = \"ishtar\" | \"empire\" | \"interdomain\" | \"all\";\n\nexport type AreaMapSettings = {\n areaWidth: number;\n areaHeight: number;\n areaSpacing: number;\n fontSize: number;\n connectionLineWidth: number;\n areaFillColor: string;\n areaStrokeColor: string;\n textColor: string;\n connectionColor: string;\n highlightColor: string;\n};\n\nexport function createAreaMapSettings(): AreaMapSettings {\n return {\n areaWidth: 150,\n areaHeight: 80,\n areaSpacing: 50,\n fontSize: 14,\n connectionLineWidth: 2,\n areaFillColor: \"#2a2a3e\",\n areaStrokeColor: \"#4a4a6e\",\n textColor: \"#e0e0e0\",\n connectionColor: \"#6a6a8e\",\n highlightColor: \"#ff9900\",\n };\n}\n\nexport class AreaMapRenderer {\n private readonly stage: Konva.Stage;\n private readonly backgroundLayer: Konva.Layer;\n private readonly areaLayer: Konva.Layer;\n private readonly connectionLayer: Konva.Layer;\n private readonly mapReader: IMapReader;\n private readonly settings: AreaMapSettings;\n private areaNodes: Map<number, AreaNode> = new Map();\n private connectionGroups: ConnectionGroup[] = [];\n private currentZoom = 1;\n private highlightedArea?: number;\n private domainInfo: Map<number, AreaDomainInfo> = new Map();\n private domainFilter: DomainFilter = \"all\";\n private backgroundImage?: Konva.Image;\n private backgroundConfig?: {url: string; x: number; y: number; width: number; height: number; opacity: number};\n private dotsMode = false;\n\n constructor(container: HTMLDivElement, mapReader: IMapReader, settings?: AreaMapSettings) {\n this.settings = settings ?? createAreaMapSettings();\n this.stage = new Konva.Stage({\n container: container,\n width: container.clientWidth,\n height: container.clientHeight,\n draggable: true,\n });\n\n this.backgroundLayer = new Konva.Layer({listening: false});\n this.stage.add(this.backgroundLayer);\n\n this.connectionLayer = new Konva.Layer({listening: false});\n this.stage.add(this.connectionLayer);\n\n this.areaLayer = new Konva.Layer();\n this.stage.add(this.areaLayer);\n\n this.mapReader = mapReader;\n\n this.initScaling();\n this.initResize(container);\n }\n\n private initResize(container: HTMLDivElement) {\n const resizeObserver = new ResizeObserver(() => {\n this.stage.width(container.clientWidth);\n this.stage.height(container.clientHeight);\n this.stage.batchDraw();\n });\n resizeObserver.observe(container);\n }\n\n private initScaling() {\n const scaleBy = 1.1;\n\n this.stage.on(\"wheel\", (e) => {\n e.evt.preventDefault();\n\n const oldScale = this.stage.scaleX();\n const pointer = this.stage.getPointerPosition();\n if (!pointer) return;\n\n const mousePointTo = {\n x: (pointer.x - this.stage.x()) / oldScale,\n y: (pointer.y - this.stage.y()) / oldScale,\n };\n\n const direction = e.evt.deltaY > 0 ? -1 : 1;\n const newZoom = direction > 0 ? this.currentZoom * scaleBy : this.currentZoom / scaleBy;\n\n if (this.setZoom(newZoom)) {\n const newScale = this.stage.scaleX();\n const newPos = {\n x: pointer.x - mousePointTo.x * newScale,\n y: pointer.y - mousePointTo.y * newScale,\n };\n\n this.stage.position(newPos);\n this.stage.batchDraw();\n }\n });\n }\n\n setZoom(zoom: number): boolean {\n const clamped = Math.max(0.1, zoom);\n if (this.currentZoom === clamped) {\n return false;\n }\n this.currentZoom = clamped;\n this.stage.scale({x: this.currentZoom, y: this.currentZoom});\n return true;\n }\n\n getZoom() {\n return this.currentZoom;\n }\n\n setDomainInfo(domainInfo: Record<number, AreaDomainInfo>) {\n this.domainInfo.clear();\n for (const [areaId, info] of Object.entries(domainInfo)) {\n this.domainInfo.set(Number(areaId), info);\n }\n }\n\n setDomainFilter(filter: DomainFilter) {\n this.domainFilter = filter;\n }\n\n getDomainFilter() {\n return this.domainFilter;\n }\n\n setBackgroundImage(config: {url: string; x: number; y: number; width: number; height: number; opacity?: number}) {\n this.backgroundConfig = {\n url: config.url,\n x: config.x,\n y: config.y,\n width: config.width,\n height: config.height,\n opacity: config.opacity ?? 0.3,\n };\n }\n\n clearBackgroundImage() {\n this.backgroundConfig = undefined;\n this.backgroundImage = undefined;\n this.backgroundLayer.destroyChildren();\n this.backgroundLayer.batchDraw();\n }\n\n redrawBackground() {\n this.drawBackground();\n }\n\n setDotsMode(enabled: boolean) {\n this.dotsMode = enabled;\n }\n\n getDotsMode() {\n return this.dotsMode;\n }\n\n redraw() {\n this.drawBackground();\n this.drawConnections();\n this.drawAreas();\n this.stage.batchDraw();\n }\n\n private isAreaInDomain(areaId: number): boolean {\n const info = this.domainInfo.get(areaId);\n if (!info) {\n // If no domain info, only show in \"interdomain\" or \"all\"\n return this.domainFilter === \"interdomain\" || this.domainFilter === \"all\";\n }\n\n switch (this.domainFilter) {\n case \"ishtar\":\n return info.isIshtar;\n case \"empire\":\n return info.isEmpire;\n case \"interdomain\":\n return !info.isIshtar && !info.isEmpire;\n case \"all\":\n return true;\n }\n }\n\n private areAreasInSameDomain(areaId1: number, areaId2: number): boolean {\n const info1 = this.domainInfo.get(areaId1);\n const info2 = this.domainInfo.get(areaId2);\n\n if (!info1 || !info2) {\n return false;\n }\n\n // Both in Ishtar\n if (info1.isIshtar && info2.isIshtar) return true;\n // Both in Empire\n if (info1.isEmpire && info2.isEmpire) return true;\n // Both interdomain (neither Ishtar nor Empire)\n return !info1.isIshtar && !info1.isEmpire && !info2.isIshtar && !info2.isEmpire;\n }\n\n render() {\n this.analyzeConnections();\n this.layoutAreas();\n this.drawBackground();\n this.drawConnections();\n this.drawAreas();\n this.centerView();\n this.stage.batchDraw();\n }\n\n private drawBackground() {\n this.backgroundLayer.destroyChildren();\n\n if (!this.backgroundConfig) return;\n\n const imageObj = new Image();\n imageObj.crossOrigin = \"anonymous\";\n\n imageObj.onload = () => {\n this.backgroundImage = new Konva.Image({\n x: this.backgroundConfig!.x,\n y: this.backgroundConfig!.y,\n image: imageObj,\n width: this.backgroundConfig!.width,\n height: this.backgroundConfig!.height,\n opacity: this.backgroundConfig!.opacity,\n listening: false,\n });\n\n this.backgroundLayer.add(this.backgroundImage);\n this.backgroundLayer.batchDraw();\n };\n\n imageObj.onerror = () => {\n console.error(\"Failed to load background image:\", this.backgroundConfig?.url);\n };\n\n imageObj.src = this.backgroundConfig.url;\n }\n\n private analyzeConnections() {\n this.areaNodes.clear();\n this.connectionGroups = [];\n\n const rooms = this.mapReader.getRooms();\n const areas = this.mapReader.getAreas();\n\n // Calculate bounds and center for each area (only z=0 rooms) to determine proportional sizes and positions\n const areaBounds = new Map<number, {minX: number; maxX: number; minY: number; maxY: number; centerX: number; centerY: number; roomCount: number}>();\n for (const area of areas) {\n const areaId = area.getAreaId();\n const areaRooms = area.getRooms();\n if (areaRooms.length === 0) continue;\n\n let minX = Infinity, maxX = -Infinity;\n let minY = Infinity, maxY = -Infinity;\n let z0RoomCount = 0;\n\n for (const room of areaRooms) {\n // Only count rooms at z=0\n if (room.z !== 0) continue;\n z0RoomCount++;\n minX = Math.min(minX, room.x);\n maxX = Math.max(maxX, room.x);\n minY = Math.min(minY, room.y);\n maxY = Math.max(maxY, room.y);\n }\n\n // If no z=0 rooms, use all rooms\n if (z0RoomCount === 0) {\n for (const room of areaRooms) {\n minX = Math.min(minX, room.x);\n maxX = Math.max(maxX, room.x);\n minY = Math.min(minY, room.y);\n maxY = Math.max(maxY, room.y);\n }\n }\n\n // Calculate center of the area\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n\n areaBounds.set(areaId, {minX, maxX, minY, maxY, centerX, centerY, roomCount: areaRooms.length});\n }\n\n // Find the scale factor to normalize area sizes\n // Use the largest area dimension as reference\n let maxDimension = 1;\n for (const bounds of areaBounds.values()) {\n const width = bounds.maxX - bounds.minX;\n const height = bounds.maxY - bounds.minY;\n maxDimension = Math.max(maxDimension, width, height);\n }\n\n // Scale factor: map the largest area to max size, others proportionally\n const maxAreaSize = 200; // Maximum rectangle dimension\n const minAreaSize = 100; // Minimum rectangle dimension\n const scaleFactor = maxAreaSize / maxDimension;\n\n // Create area nodes (skip areas with no rooms and filter by domain)\n for (const area of areas) {\n const areaId = area.getAreaId();\n const bounds = areaBounds.get(areaId);\n if (!bounds) continue;\n\n // Skip areas not in selected domain\n if (!this.isAreaInDomain(areaId)) {\n continue;\n }\n\n // Calculate proportional size\n const boundsWidth = bounds.maxX - bounds.minX;\n const boundsHeight = bounds.maxY - bounds.minY;\n\n // Scale proportionally, with minimum size\n let width = Math.max(minAreaSize, boundsWidth * scaleFactor);\n let height = Math.max(minAreaSize, boundsHeight * scaleFactor);\n\n // Ensure reasonable aspect ratio (not too extreme)\n const aspectRatio = width / height;\n if (aspectRatio > 3) {\n height = width / 3;\n } else if (aspectRatio < 1/3) {\n width = height / 3;\n }\n\n this.areaNodes.set(areaId, {\n areaId,\n name: area.getAreaName(),\n x: 0,\n y: 0,\n width,\n height,\n connections: [],\n roomCount: bounds.roomCount,\n realCenterX: bounds.centerX,\n realCenterY: bounds.centerY,\n });\n }\n\n // Find cross-area connections\n const connectionMap = new Map<string, AreaConnection[]>();\n\n for (const room of rooms) {\n const fromAreaId = room.area;\n // Skip if source area has no rooms (not in our nodes)\n if (!this.areaNodes.has(fromAreaId)) continue;\n\n const lockedDirections = this.getLockedDirections(room);\n const lockedSpecialTargets = new Set(room.mSpecialExitLocks ?? []);\n\n // Check regular exits\n for (const [dir, targetRoomId] of Object.entries(room.exits ?? {})) {\n if (lockedDirections.has(dir as MapData.direction)) continue;\n\n const targetRoom = this.mapReader.getRoom(targetRoomId);\n if (!targetRoom || targetRoom.area === fromAreaId) continue;\n // Skip if target area has no rooms (not in our nodes)\n if (!this.areaNodes.has(targetRoom.area)) continue;\n // Skip cross-domain connections\n if (!this.areAreasInSameDomain(fromAreaId, targetRoom.area)) continue;\n\n const connection = this.createConnection(\n room, targetRoom, dir as MapData.direction\n );\n if (connection) {\n this.addConnection(connectionMap, connection);\n }\n }\n\n // Check special exits\n for (const [cmd, targetRoomId] of Object.entries(room.specialExits ?? {})) {\n if (lockedSpecialTargets.has(targetRoomId)) continue;\n\n const targetRoom = this.mapReader.getRoom(targetRoomId);\n if (!targetRoom || targetRoom.area === fromAreaId) continue;\n // Skip if target area has no rooms (not in our nodes)\n if (!this.areaNodes.has(targetRoom.area)) continue;\n // Skip cross-domain connections\n if (!this.areAreasInSameDomain(fromAreaId, targetRoom.area)) continue;\n\n const direction = this.parseDirection(cmd);\n const connection = this.createConnection(room, targetRoom, direction);\n if (connection) {\n this.addConnection(connectionMap, connection);\n }\n }\n }\n\n // Group connections between same area pairs\n for (const [key, connections] of connectionMap) {\n const [fromId, toId] = key.split(\"-\").map(Number);\n const group = this.createConnectionGroup(fromId, toId, connections);\n this.connectionGroups.push(group);\n\n const fromNode = this.areaNodes.get(fromId);\n const toNode = this.areaNodes.get(toId);\n if (fromNode) fromNode.connections.push(...connections);\n if (toNode) {\n // Add reverse connections\n for (const conn of connections) {\n toNode.connections.push({\n ...conn,\n fromAreaId: conn.toAreaId,\n toAreaId: conn.fromAreaId,\n fromRoomId: conn.toRoomId,\n toRoomId: conn.fromRoomId,\n direction: conn.direction ? oppositeDirections[conn.direction] : null,\n fromRoomPosition: conn.toRoomPosition,\n toRoomPosition: conn.fromRoomPosition,\n });\n }\n }\n }\n }\n\n private getLockedDirections(room: MapData.Room): Set<MapData.direction> {\n return new Set(\n (room.exitLocks ?? [])\n .map((lockId) => directionNumberToName[lockId])\n .filter((d): d is MapData.direction => Boolean(d))\n );\n }\n\n private createConnection(\n fromRoom: MapData.Room,\n toRoom: MapData.Room,\n direction: MapData.direction | null\n ): AreaConnection | null {\n const planarDir = this.toPlanarDirection(direction);\n\n return {\n fromAreaId: fromRoom.area,\n toAreaId: toRoom.area,\n fromRoomId: fromRoom.id,\n toRoomId: toRoom.id,\n direction: planarDir,\n fromRoomPosition: {x: fromRoom.x, y: fromRoom.y},\n toRoomPosition: {x: toRoom.x, y: toRoom.y},\n };\n }\n\n private addConnection(map: Map<string, AreaConnection[]>, conn: AreaConnection) {\n // Use consistent key ordering (smaller id first)\n const key = conn.fromAreaId < conn.toAreaId\n ? `${conn.fromAreaId}-${conn.toAreaId}`\n : `${conn.toAreaId}-${conn.fromAreaId}`;\n\n if (!map.has(key)) {\n map.set(key, []);\n }\n map.get(key)!.push(conn);\n }\n\n private createConnectionGroup(\n fromAreaId: number,\n toAreaId: number,\n connections: AreaConnection[]\n ): ConnectionGroup {\n // Calculate primary direction based on most common direction\n const directionCounts = new Map<PlanarDirection, number>();\n let totalOffsetX = 0;\n let totalOffsetY = 0;\n\n for (const conn of connections) {\n if (conn.direction) {\n directionCounts.set(\n conn.direction,\n (directionCounts.get(conn.direction) ?? 0) + 1\n );\n }\n // Calculate offset based on room positions\n totalOffsetX += conn.toRoomPosition.x - conn.fromRoomPosition.x;\n totalOffsetY += conn.toRoomPosition.y - conn.fromRoomPosition.y;\n }\n\n let primaryDirection: PlanarDirection | null = null;\n let maxCount = 0;\n for (const [dir, count] of directionCounts) {\n if (count > maxCount) {\n maxCount = count;\n primaryDirection = dir;\n }\n }\n\n return {\n fromAreaId,\n toAreaId,\n connections,\n primaryDirection,\n averageOffset: {\n x: totalOffsetX / connections.length,\n y: totalOffsetY / connections.length,\n },\n };\n }\n\n private parseDirection(cmd: string): MapData.direction | null {\n const lower = cmd.toLowerCase().trim();\n const dirMap: Record<string, MapData.direction> = {\n n: \"north\", north: \"north\",\n s: \"south\", south: \"south\",\n e: \"east\", east: \"east\",\n w: \"west\", west: \"west\",\n ne: \"northeast\", northeast: \"northeast\",\n nw: \"northwest\", northwest: \"northwest\",\n se: \"southeast\", southeast: \"southeast\",\n sw: \"southwest\", southwest: \"southwest\",\n u: \"up\", up: \"up\",\n d: \"down\", down: \"down\",\n i: \"in\", in: \"in\",\n o: \"out\", out: \"out\",\n };\n return dirMap[lower] ?? null;\n }\n\n private toPlanarDirection(dir: MapData.direction | null): PlanarDirection | null {\n if (!dir) return null;\n if (planarDirections.includes(dir as PlanarDirection)) {\n return dir as PlanarDirection;\n }\n return null;\n }\n\n private layoutAreas() {\n const nodes = Array.from(this.areaNodes.values());\n if (nodes.length === 0) return;\n\n // Find connected components\n const components = this.findConnectedComponents();\n\n // Layout each component separately\n let componentOffsetX = 0;\n const componentGap = this.settings.areaWidth * 3;\n\n for (const component of components) {\n if (component.length === 1) {\n // Single isolated area\n const node = this.areaNodes.get(component[0]);\n if (node) {\n node.x = componentOffsetX;\n node.y = 0;\n }\n componentOffsetX += this.settings.areaWidth + componentGap;\n } else {\n // Use force-directed layout for connected areas\n const bounds = this.forceDirectedLayout(component);\n\n // Shift component to start at componentOffsetX\n for (const areaId of component) {\n const node = this.areaNodes.get(areaId);\n if (node && isFinite(bounds.minX) && isFinite(bounds.minY)) {\n node.x -= bounds.minX;\n node.x += componentOffsetX;\n node.y -= bounds.minY;\n }\n }\n\n const width = isFinite(bounds.maxX - bounds.minX) ? bounds.maxX - bounds.minX : this.settings.areaWidth;\n componentOffsetX += width + componentGap;\n }\n }\n }\n\n private findConnectedComponents(): number[][] {\n const visited = new Set<number>();\n const components: number[][] = [];\n\n for (const [areaId] of this.areaNodes) {\n if (visited.has(areaId)) continue;\n\n const component: number[] = [];\n const queue = [areaId];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n if (visited.has(current)) continue;\n\n visited.add(current);\n component.push(current);\n\n const node = this.areaNodes.get(current);\n if (node) {\n for (const conn of node.connections) {\n if (!visited.has(conn.toAreaId)) {\n queue.push(conn.toAreaId);\n }\n }\n }\n }\n\n components.push(component);\n }\n\n return components;\n }\n\n private forceDirectedLayout(areaIds: number[]): {minX: number; minY: number; maxX: number; maxY: number} {\n // Find unique connected areas for each node\n const connectionCount = new Map<number, Set<number>>();\n for (const id of areaIds) {\n const node = this.areaNodes.get(id);\n if (!node) continue;\n const connected = new Set(node.connections.map(c => c.toAreaId));\n connectionCount.set(id, connected);\n }\n\n // Hub detection: A hub is a node with 3+ connections (branching point)\n // Nodes with 1-2 connections are potential chain nodes\n // We need to trace chains and find their terminal hubs\n const hubNodes = new Set<number>();\n const processedInChain = new Set<number>();\n\n // First pass: mark obvious hubs (3+ connections)\n for (const [id, connected] of connectionCount) {\n if (connected.size >= 3) {\n hubNodes.add(id);\n }\n }\n\n // Second pass: find chains and identify their terminal hubs\n // A chain is a sequence of nodes each with <=2 connections\n // The chain ends at a hub (3+ connections) or a dead end (1 connection)\n for (const [startId, connected] of connectionCount) {\n if (hubNodes.has(startId) || processedInChain.has(startId)) continue;\n if (connected.size > 2) continue; // Already a hub\n\n // This node has 1-2 connections - trace the chain in both directions\n const chainMembers: number[] = [startId];\n processedInChain.add(startId);\n\n // Trace in both directions from this node\n const toVisit = Array.from(connected);\n for (const nextId of toVisit) {\n let currentId = nextId;\n let prevId = startId;\n\n while (currentId && !processedInChain.has(currentId) && !hubNodes.has(currentId)) {\n const currentConnections = connectionCount.get(currentId);\n if (!currentConnections) break;\n\n if (currentConnections.size >= 3) {\n // Found a hub at the end\n hubNodes.add(currentId);\n break;\n }\n\n chainMembers.push(currentId);\n processedInChain.add(currentId);\n\n // Find next node (not the one we came from)\n let foundNext = false;\n for (const neighborId of currentConnections) {\n if (neighborId !== prevId && !processedInChain.has(neighborId)) {\n prevId = currentId;\n currentId = neighborId;\n foundNext = true;\n break;\n }\n }\n if (!foundNext) break;\n }\n }\n\n // If chain has no hub connection, pick the first node as a pseudo-hub\n // (isolated chain or chain of single-connection nodes)\n let hasHubConnection = false;\n for (const memberId of chainMembers) {\n const memberConns = connectionCount.get(memberId);\n if (memberConns) {\n for (const connId of memberConns) {\n if (hubNodes.has(connId)) {\n hasHubConnection = true;\n break;\n }\n }\n }\n if (hasHubConnection) break;\n }\n\n if (!hasHubConnection && chainMembers.length > 0) {\n // Find the node with most connections to be the pseudo-hub\n let bestHub = chainMembers[0];\n let maxConns = connectionCount.get(chainMembers[0])?.size ?? 0;\n for (const memberId of chainMembers) {\n const conns = connectionCount.get(memberId)?.size ?? 0;\n if (conns > maxConns) {\n maxConns = conns;\n bestHub = memberId;\n }\n }\n hubNodes.add(bestHub);\n processedInChain.delete(bestHub);\n }\n }\n\n // Also add any isolated nodes (0 connections) as their own hubs\n for (const id of areaIds) {\n const connected = connectionCount.get(id);\n if (!connected || connected.size === 0) {\n hubNodes.add(id);\n }\n }\n\n // Build chains from each hub\n const nodeToCluster = new Map<number, number>(); // maps node to its hub\n const clusterChains = new Map<number, number[][]>(); // hub -> array of chains\n\n for (const hubId of hubNodes) {\n nodeToCluster.set(hubId, hubId);\n clusterChains.set(hubId, []);\n }\n\n // For each hub, find chains emanating from it\n const assignedToChain = new Set<number>(hubNodes);\n\n for (const hubId of hubNodes) {\n const hubConnections = connectionCount.get(hubId) ?? new Set();\n\n for (const connectedId of hubConnections) {\n // If already assigned (another hub or already in a chain), skip\n if (assignedToChain.has(connectedId)) continue;\n\n // Build chain starting from this connection\n const chain: number[] = [];\n let currentId = connectedId;\n let prevId = hubId;\n\n while (currentId && !assignedToChain.has(currentId)) {\n chain.push(currentId);\n nodeToCluster.set(currentId, hubId);\n assignedToChain.add(currentId);\n\n // Find next in chain (the one connection that isn't where we came from)\n const currentConnections = connectionCount.get(currentId) ?? new Set();\n let nextId: number | undefined;\n for (const nextConnId of currentConnections) {\n if (nextConnId !== prevId && !assignedToChain.has(nextConnId)) {\n nextId = nextConnId;\n break;\n }\n }\n\n prevId = currentId;\n currentId = nextId!;\n }\n\n if (chain.length > 0) {\n clusterChains.get(hubId)!.push(chain);\n }\n }\n }\n\n // Position hubs first using BFS\n const positioned = new Set<number>();\n const queue: number[] = [];\n\n // Start with first hub (or first node if no hubs)\n const firstHub = hubNodes.values().next().value;\n const startId = firstHub !== undefined ? firstHub : areaIds[0];\n if (startId !== undefined) {\n const startNode = this.areaNodes.get(startId);\n if (startNode) {\n startNode.x = 0;\n startNode.y = 0;\n positioned.add(startId);\n queue.push(startId);\n }\n }\n\n // BFS to position hubs based on connection directions\n // Use real-world positions only to refine ordering when multiple areas share same direction\n while (queue.length > 0) {\n const currentId = queue.shift()!;\n const currentNode = this.areaNodes.get(currentId)!;\n\n // Group connections by direction\n const connectionsByDir = new Map<string, Array<{id: number; node: AreaNode; conn: AreaConnection}>>();\n\n for (const conn of currentNode.connections) {\n const targetId = conn.toAreaId;\n if (positioned.has(targetId)) continue;\n if (!hubNodes.has(targetId)) continue;\n\n const targetNode = this.areaNodes.get(targetId);\n if (!targetNode) continue;\n\n const dirKey = conn.direction ?? \"none\";\n if (!connectionsByDir.has(dirKey)) {\n connectionsByDir.set(dirKey, []);\n }\n connectionsByDir.get(dirKey)!.push({id: targetId, node: targetNode, conn});\n }\n\n // Position each group, sorting by real-world position within the group\n for (const [dirKey, targets] of connectionsByDir) {\n const dirOffset = this.getDirectionOffset(dirKey === \"none\" ? null : dirKey as PlanarDirection);\n\n // Sort targets by their real-world position perpendicular to the direction\n // This ensures proper ordering when multiple areas are in the same direction\n targets.sort((a, b) => {\n if (Math.abs(dirOffset.x) > Math.abs(dirOffset.y)) {\n // Primarily horizontal direction - sort by Y (perpendicular)\n return b.node.realCenterY - a.node.realCenterY; // Higher Y = more north = lower index\n } else {\n // Primarily vertical direction - sort by X (perpendicular)\n return a.node.realCenterX - b.node.realCenterX; // Lower X = more west = lower index\n }\n });\n\n const baseSpacingX = this.settings.areaWidth + this.settings.areaSpacing;\n const baseSpacingY = this.settings.areaHeight + this.settings.areaSpacing;\n\n // Position each target with offset for multiple in same direction\n targets.forEach((target, index) => {\n target.node.x = currentNode.x + dirOffset.x * baseSpacingX;\n target.node.y = currentNode.y + dirOffset.y * baseSpacingY;\n\n // Add perpendicular offset for multiple areas in same direction\n if (targets.length > 1) {\n const perpOffset = (index - (targets.length - 1) / 2) * 0.5;\n if (Math.abs(dirOffset.x) > Math.abs(dirOffset.y)) {\n // Horizontal - offset vertically\n target.node.y += perpOffset * baseSpacingY;\n } else {\n // Vertical - offset horizontally\n target.node.x += perpOffset * baseSpacingX;\n }\n }\n\n positioned.add(target.id);\n queue.push(target.id);\n });\n }\n }\n\n // Apply forces only to hub nodes\n const hubArray = Array.from(hubNodes);\n if (hubArray.length > 1) {\n this.applyForces(hubArray, 80);\n }\n\n // Position chain nodes with appropriate distance based on direction\n // Add extra spacing to avoid overlaps with connection lines\n const horizontalDistance = this.settings.areaWidth + this.settings.areaSpacing + 30; // For east/west (230px)\n const verticalDistance = this.settings.areaHeight + this.settings.areaSpacing + 30; // For north/south (160px)\n\n // Store chain offsets relative to hub so we can reposition after hub moves\n const chainOffsets = new Map<number, {dx: number; dy: number; hubId: number}>();\n\n // Fallback directions for chains without explicit direction\n const fallbackDirections: PlanarDirection[] = [\n \"east\", \"west\", \"north\", \"south\",\n \"northeast\", \"northwest\", \"southeast\", \"southwest\"\n ];\n\n for (const [hubId, chains] of clusterChains) {\n const hubNode = this.areaNodes.get(hubId);\n if (!hubNode) continue;\n\n // Track which fallback direction to use for null-direction chains\n let fallbackIndex = 0;\n\n // Track how many chains use each direction to offset duplicates\n const directionUsageCount = new Map<string, number>();\n\n for (const chain of chains) {\n let prevId = hubId;\n let prevX = hubNode.x;\n let prevY = hubNode.y;\n let chainFallbackDirection: PlanarDirection | null = null;\n\n for (const chainNodeId of chain) {\n const chainNode = this.areaNodes.get(chainNodeId);\n if (!chainNode) continue;\n\n // Find direction from prev to this chain node\n const prevNodeData = this.areaNodes.get(prevId);\n let direction: PlanarDirection | null = null;\n if (prevNodeData) {\n for (const conn of prevNodeData.connections) {\n if (conn.toAreaId === chainNodeId) {\n direction = conn.direction;\n break;\n }\n }\n }\n\n // If no direction and this is first node in chain, use fallback\n if (!direction && prevId === hubId) {\n chainFallbackDirection = fallbackDirections[fallbackIndex % fallbackDirections.length];\n fallbackIndex++;\n direction = chainFallbackDirection;\n } else if (!direction && chainFallbackDirection) {\n // Continue in same fallback direction for rest of chain\n direction = chainFallbackDirection;\n }\n\n // Position relative to previous node with direction-appropriate distance\n const offset = this.getDirectionOffset(direction);\n\n // Check if this is first node in chain from hub - apply stacking offset for same direction\n let stackOffset = 0;\n if (prevId === hubId && direction) {\n const dirKey = direction;\n const usageCount = directionUsageCount.get(dirKey) ?? 0;\n directionUsageCount.set(dirKey, usageCount + 1);\n stackOffset = usageCount;\n }\n\n // Use horizontal distance for x component, vertical for y component\n chainNode.x = prevX + offset.x * horizontalDistance;\n chainNode.y = prevY + offset.y * verticalDistance;\n\n // Apply perpendicular offset if multiple chains in same direction\n if (stackOffset > 0) {\n // Offset perpendicular to the direction - use full area size + extra spacing\n const extraPadding = 1.2; // 20% extra spacing for multi-satellite hubs\n if (offset.x !== 0 && offset.y === 0) {\n // Horizontal direction - offset vertically\n chainNode.y += stackOffset * verticalDistance * extraPadding;\n } else if (offset.y !== 0 && offset.x === 0) {\n // Vertical direction - offset horizontally\n chainNode.x += stackOffset * horizontalDistance * extraPadding;\n } else {\n // Diagonal - offset along the perpendicular diagonal (full spacing)\n chainNode.x += stackOffset * horizontalDistance * extraPadding;\n chainNode.y -= stackOffset * verticalDistance * extraPadding;\n }\n }\n\n // Store offset from hub for later repositioning\n chainOffsets.set(chainNodeId, {\n dx: chainNode.x - hubNode.x,\n dy: chainNode.y - hubNode.y,\n hubId\n });\n\n prevId = chainNodeId;\n prevX = chainNode.x;\n prevY = chainNode.y;\n }\n }\n }\n\n // Calculate cluster bounds for each hub (hub + all its chains)\n const clusterBounds = new Map<number, {minX: number; minY: number; maxX: number; maxY: number}>();\n const clusterBoundsOffsets = new Map<number, {dx: number; dy: number}>();\n\n for (const hubId of hubNodes) {\n const hubNode = this.areaNodes.get(hubId);\n if (!hubNode) continue;\n\n let minX = hubNode.x;\n let minY = hubNode.y;\n let maxX = hubNode.x + hubNode.width;\n let maxY = hubNode.y + hubNode.height;\n\n const chains = clusterChains.get(hubId) ?? [];\n for (const chain of chains) {\n for (const chainNodeId of chain) {\n const chainNode = this.areaNodes.get(chainNodeId);\n if (!chainNode) continue;\n minX = Math.min(minX, chainNode.x);\n minY = Math.min(minY, chainNode.y);\n maxX = Math.max(maxX, chainNode.x + chainNode.width);\n maxY = Math.max(maxY, chainNode.y + chainNode.height);\n }\n }\n\n clusterBounds.set(hubId, {minX, minY, maxX, maxY});\n // Store offset from hub origin to bounds origin\n clusterBoundsOffsets.set(hubId, {dx: minX - hubNode.x, dy: minY - hubNode.y});\n }\n\n // Apply forces only to hubs, using cluster bounds for collision\n this.applyClusterForces(hubArray, clusterBounds, clusterBoundsOffsets, 60);\n\n // Reposition chain nodes based on updated hub positions\n for (const [chainNodeId, offset] of chainOffsets) {\n const chainNode = this.areaNodes.get(chainNodeId);\n const hubNode = this.areaNodes.get(offset.hubId);\n if (!chainNode || !hubNode) continue;\n\n chainNode.x = hubNode.x + offset.dx;\n chainNode.y = hubNode.y + offset.dy;\n }\n\n // Calculate bounds\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const areaId of areaIds) {\n const node = this.areaNodes.get(areaId)!;\n minX = Math.min(minX, node.x);\n minY = Math.min(minY, node.y);\n maxX = Math.max(maxX, node.x + node.width);\n maxY = Math.max(maxY, node.y + node.height);\n }\n\n return {minX, minY, maxX, maxY};\n }\n\n private getDirectionOffset(direction: PlanarDirection | null): {x: number; y: number} {\n // Use 0.75 for diagonals to keep them compact but not overlapping\n const offsets: 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: 0.75, y: -0.75},\n northwest: {x: -0.75, y: -0.75},\n southeast: {x: 0.75, y: 0.75},\n southwest: {x: -0.75, y: 0.75},\n };\n\n if (direction && offsets[direction]) {\n return offsets[direction];\n }\n\n // Default: random-ish offset for non-directional connections\n return {x: 1, y: 0};\n }\n\n private applyClusterForces(\n hubIds: number[],\n clusterBounds: Map<number, {minX: number; minY: number; maxX: number; maxY: number}>,\n clusterOffsets: Map<number, {dx: number; dy: number}>, // offset from hub origin to cluster bounds origin\n iterations: number\n ) {\n const damping = 0.8;\n const padding = 50; // Extra space between clusters\n\n const velocities = new Map<number, {vx: number; vy: number}>();\n for (const id of hubIds) {\n velocities.set(id, {vx: 0, vy: 0});\n }\n\n for (let iter = 0; iter < iterations; iter++) {\n // Update cluster bounds based on current hub positions\n for (const hubId of hubIds) {\n const hubNode = this.areaNodes.get(hubId);\n const bounds = clusterBounds.get(hubId);\n const offset = clusterOffsets.get(hubId);\n if (!hubNode || !bounds || !offset) continue;\n\n const width = bounds.maxX - bounds.minX;\n const height = bounds.maxY - bounds.minY;\n\n bounds.minX = hubNode.x + offset.dx;\n bounds.minY = hubNode.y + offset.dy;\n bounds.maxX = bounds.minX + width;\n bounds.maxY = bounds.minY + height;\n }\n\n for (const id of hubIds) {\n const node = this.areaNodes.get(id);\n const vel = velocities.get(id);\n const bounds = clusterBounds.get(id);\n if (!node || !vel || !bounds) continue;\n\n // Repulsion from other clusters using AABB overlap check\n for (const otherId of hubIds) {\n if (id === otherId) continue;\n const otherBounds = clusterBounds.get(otherId);\n if (!otherBounds) continue;\n\n // Calculate centers\n const cx1 = (bounds.minX + bounds.maxX) / 2;\n const cy1 = (bounds.minY + bounds.maxY) / 2;\n const cx2 = (otherBounds.minX + otherBounds.maxX) / 2;\n const cy2 = (otherBounds.minY + otherBounds.maxY) / 2;\n\n const dx = cx1 - cx2;\n const dy = cy1 - cy2;\n\n // Required distance based on actual rectangles (half-widths and half-heights)\n const hw1 = (bounds.maxX - bounds.minX) / 2;\n const hh1 = (bounds.maxY - bounds.minY) / 2;\n const hw2 = (otherBounds.maxX - otherBounds.minX) / 2;\n const hh2 = (otherBounds.maxY - otherBounds.minY) / 2;\n\n // Minimum distance to avoid overlap (axis-aligned rectangles)\n const sepX = hw1 + hw2 + padding;\n const sepY = hh1 + hh2 + padding;\n\n // Check if overlapping\n const overlapX = sepX - Math.abs(dx);\n const overlapY = sepY - Math.abs(dy);\n\n if (overlapX > 0 && overlapY > 0) {\n // Overlapping - push apart along the axis with smaller overlap\n if (overlapX < overlapY) {\n const pushX = overlapX * (dx >= 0 ? 1 : -1) * 0.5;\n vel.vx += pushX;\n } else {\n const pushY = overlapY * (dy >= 0 ? 1 : -1) * 0.5;\n vel.vy += pushY;\n }\n }\n }\n\n // Spring force toward connected hubs (gentle)\n for (const conn of node.connections) {\n const other = this.areaNodes.get(conn.toAreaId);\n if (!other || !hubIds.includes(conn.toAreaId)) continue;\n\n const dx = other.x - node.x;\n const dy = other.y - node.y;\n const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n\n const idealDistance = this.settings.areaWidth + this.settings.areaSpacing + 50;\n const displacement = dist - idealDistance;\n const springForce = displacement * 0.015;\n vel.vx += (dx / dist) * springForce;\n vel.vy += (dy / dist) * springForce;\n }\n }\n\n // Apply velocities\n for (const id of hubIds) {\n const node = this.areaNodes.get(id);\n const vel = velocities.get(id);\n if (!node || !vel) continue;\n\n node.x += vel.vx;\n node.y += vel.vy;\n\n vel.vx *= damping;\n vel.vy *= damping;\n }\n }\n }\n\n private applyForces(areaIds: number[], iterations: number) {\n const damping = 0.8;\n const padding = 20; // Minimum gap between areas\n const idealDistance = this.settings.areaWidth + this.settings.areaSpacing; // 200px - same as chain spacing\n const lineAvoidanceDistance = this.settings.areaWidth / 2 + 60; // Increased to push nodes further from lines\n\n // Build list of edges for line avoidance (only between nodes in areaIds)\n const areaIdSet = new Set(areaIds);\n const edges: Array<{from: number; to: number}> = [];\n const edgeSet = new Set<string>();\n for (const id of areaIds) {\n const node = this.areaNodes.get(id);\n if (!node) continue;\n for (const conn of node.connections) {\n if (!areaIdSet.has(conn.toAreaId)) continue;\n const key = Math.min(id, conn.toAreaId) + \"-\" + Math.max(id, conn.toAreaId);\n if (!edgeSet.has(key)) {\n edgeSet.add(key);\n edges.push({from: id, to: conn.toAreaId});\n }\n }\n }\n\n const velocities = new Map<number, {vx: number; vy: number}>();\n for (const id of areaIds) {\n velocities.set(id, {vx: 0, vy: 0});\n }\n\n for (let iter = 0; iter < iterations; iter++) {\n // Calculate forces\n for (const id of areaIds) {\n const node = this.areaNodes.get(id);\n const vel = velocities.get(id);\n if (!node || !vel) continue;\n\n const nodeCenterX = node.x + node.width / 2;\n const nodeCenterY = node.y + node.height / 2;\n\n // Repulsion from other nodes using AABB overlap detection\n for (const otherId of areaIds) {\n if (id === otherId) continue;\n const other = this.areaNodes.get(otherId);\n if (!other) continue;\n\n // Calculate required separation (half-widths + half-heights + padding)\n const minSepX = (node.width + other.width) / 2 + padding;\n const minSepY = (node.height + other.height) / 2 + padding;\n\n // Calculate center-to-center distance\n const cx1 = node.x + node.width / 2;\n const cy1 = node.y + node.height / 2;\n const cx2 = other.x + other.width / 2;\n const cy2 = other.y + other.height / 2;\n\n const dx = cx1 - cx2;\n const dy = cy1 - cy2;\n\n // Check overlap on each axis\n const overlapX = minSepX - Math.abs(dx);\n const overlapY = minSepY - Math.abs(dy);\n\n if (overlapX > 0 && overlapY > 0) {\n // Overlapping - push apart along the axis with smaller overlap\n if (overlapX < overlapY) {\n vel.vx += (dx >= 0 ? 1 : -1) * overlapX * 0.5;\n } else {\n vel.vy += (dy >= 0 ? 1 : -1) * overlapY * 0.5;\n }\n }\n }\n\n // Repulsion from edges (lines) that this node is not part of\n for (const edge of edges) {\n if (edge.from === id || edge.to === id) continue;\n\n const fromNode = this.areaNodes.get(edge.from);\n const toNode = this.areaNodes.get(edge.to);\n if (!fromNode || !toNode) continue;\n\n const fromX = fromNode.x + fromNode.width / 2;\n const fromY = fromNode.y + fromNode.height / 2;\n const toX = toNode.x + toNode.width / 2;\n const toY = toNode.y + toNode.height / 2;\n\n const closest = this.closestPointOnSegment(\n nodeCenterX, nodeCenterY,\n fromX, fromY, toX, toY\n );\n\n const dx = nodeCenterX - closest.x;\n const dy = nodeCenterY - closest.y;\n const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n\n if (dist < lineAvoidanceDistance) {\n const repulsion = (lineAvoidanceDistance - dist) * 0.6; // Stronger push away from lines\n vel.vx += (dx / dist) * repulsion;\n vel.vy += (dy / dist) * repulsion;\n }\n }\n\n // Spring force for connected nodes\n for (const conn of node.connections) {\n if (!areaIdSet.has(conn.toAreaId)) continue;\n const other = this.areaNodes.get(conn.toAreaId);\n if (!other) continue;\n\n const dx = other.x - node.x;\n const dy = other.y - node.y;\n const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n\n const displacement = dist - idealDistance;\n const springForce = displacement * 0.03;\n vel.vx += (dx / dist) * springForce;\n vel.vy += (dy / dist) * springForce;\n\n // Directional hint\n const targetOffset = this.getDirectionOffset(conn.direction);\n const idealX = node.x + targetOffset.x * idealDistance;\n const idealY = node.y + targetOffset.y * idealDistance;\n\n const otherVel = velocities.get(conn.toAreaId);\n if (otherVel) {\n otherVel.vx += (idealX - other.x) * 0.01;\n otherVel.vy += (idealY - other.y) * 0.01;\n }\n }\n }\n\n // Apply velocities with damping\n for (const id of areaIds) {\n const node = this.areaNodes.get(id);\n const vel = velocities.get(id);\n if (!node || !vel) continue;\n\n node.x += vel.vx;\n node.y += vel.vy;\n\n vel.vx *= damping;\n vel.vy *= damping;\n }\n }\n }\n\n private closestPointOnSegment(\n px: number, py: number,\n ax: number, ay: number,\n bx: number, by: number\n ): {x: number; y: number} {\n const abx = bx - ax;\n const aby = by - ay;\n const apx = px - ax;\n const apy = py - ay;\n\n const abLenSq = abx * abx + aby * aby;\n if (abLenSq === 0) {\n return {x: ax, y: ay};\n }\n\n let t = (apx * abx + apy * aby) / abLenSq;\n t = Math.max(0, Math.min(1, t));\n\n return {\n x: ax + t * abx,\n y: ay + t * aby,\n };\n }\n\n private edgesIntersect(\n ax: number, ay: number, bx: number, by: number,\n cx: number, cy: number, dx: number, dy: number\n ): boolean {\n // Check if line segment AB intersects with line segment CD\n const ccw = (p1x: number, p1y: number, p2x: number, p2y: number, p3x: number, p3y: number) => {\n return (p3y - p1y) * (p2x - p1x) > (p2y - p1y) * (p3x - p1x);\n };\n\n return (\n ccw(ax, ay, cx, cy, dx, dy) !== ccw(bx, by, cx, cy, dx, dy) &&\n ccw(ax, ay, bx, by, cx, cy) !== ccw(ax, ay, bx, by, dx, dy)\n );\n }\n\n private drawConnections() {\n this.connectionLayer.destroyChildren();\n\n for (const group of this.connectionGroups) {\n const fromNode = this.areaNodes.get(group.fromAreaId);\n const toNode = this.areaNodes.get(group.toAreaId);\n if (!fromNode || !toNode) continue;\n\n // Skip if positions are invalid\n if (!isFinite(fromNode.x) || !isFinite(fromNode.y) ||\n !isFinite(toNode.x) || !isFinite(toNode.y)) {\n continue;\n }\n\n const fromCenter = {\n x: fromNode.x + fromNode.width / 2,\n y: fromNode.y + fromNode.height / 2,\n };\n const toCenter = {\n x: toNode.x + toNode.width / 2,\n y: toNode.y + toNode.height / 2,\n };\n\n // In dots mode, draw from center to center; otherwise edge to edge\n let fromPoint: {x: number; y: number};\n let toPoint: {x: number; y: number};\n\n if (this.dotsMode) {\n fromPoint = fromCenter;\n toPoint = toCenter;\n } else {\n fromPoint = this.getEdgePoint(fromNode, toCenter);\n toPoint = this.getEdgePoint(toNode, fromCenter);\n }\n\n // Skip if points are invalid\n if (!isFinite(fromPoint.x) || !isFinite(fromPoint.y) ||\n !isFinite(toPoint.x) || !isFinite(toPoint.y)) {\n continue;\n }\n\n const lineColor = this.dotsMode ? \"#ffffff\" : this.settings.connectionColor;\n const lineWidth = this.dotsMode ? 1 : Math.min(this.settings.connectionLineWidth, 1 + group.connections.length * 0.5);\n\n const line = new Konva.Line({\n points: [fromPoint.x, fromPoint.y, toPoint.x, toPoint.y],\n stroke: lineColor,\n strokeWidth: lineWidth,\n lineCap: \"round\",\n listening: false,\n });\n\n this.connectionLayer.add(line);\n }\n }\n\n private getEdgePoint(node: AreaNode, target: {x: number; y: number}): {x: number; y: number} {\n const cx = node.x + node.width / 2;\n const cy = node.y + node.height / 2;\n\n const dx = target.x - cx;\n const dy = target.y - cy;\n\n const hw = node.width / 2;\n const hh = node.height / 2;\n\n // Calculate intersection with rectangle edges\n if (dx === 0 && dy === 0) {\n return {x: cx, y: cy};\n }\n\n const absDx = Math.abs(dx);\n const absDy = Math.abs(dy);\n\n let scale: number;\n if (absDx / hw > absDy / hh) {\n scale = hw / absDx;\n } else {\n scale = hh / absDy;\n }\n\n return {\n x: cx + dx * scale,\n y: cy + dy * scale,\n };\n }\n\n private drawAreas() {\n this.areaLayer.destroyChildren();\n\n for (const [, node] of this.areaNodes) {\n // Skip if position is invalid\n if (!isFinite(node.x) || !isFinite(node.y)) {\n continue;\n }\n\n const isHighlighted = node.areaId === this.highlightedArea;\n\n if (this.dotsMode) {\n // Dots mode: small circle with text label\n const dotRadius = 6;\n const centerX = node.x + node.width / 2;\n const centerY = node.y + node.height / 2;\n\n const group = new Konva.Group({\n x: centerX,\n y: centerY,\n draggable: true,\n });\n\n const dot = new Konva.Circle({\n radius: dotRadius,\n fill: isHighlighted ? this.settings.highlightColor : \"#ffffff\",\n stroke: isHighlighted ? this.settings.highlightColor : \"#ffffff\",\n strokeWidth: 1,\n });\n\n const label = new Konva.Text({\n text: node.name,\n fontSize: 10,\n fill: \"#ffffff\",\n x: dotRadius + 4,\n y: -5,\n });\n\n group.add(dot);\n group.add(label);\n\n // Add click handler\n group.on(\"click tap\", () => {\n this.emitAreaClickEvent(node.areaId);\n });\n\n group.on(\"mouseenter\", () => {\n this.stage.container().style.cursor = \"pointer\";\n dot.fill(this.settings.highlightColor);\n dot.stroke(this.settings.highlightColor);\n this.areaLayer.batchDraw();\n });\n\n group.on(\"mouseleave\", () => {\n this.stage.container().style.cursor = \"auto\";\n dot.fill(isHighlighted ? this.settings.highlightColor : \"#ffffff\");\n dot.stroke(isHighlighted ? this.settings.highlightColor : \"#ffffff\");\n this.areaLayer.batchDraw();\n });\n\n group.on(\"dragmove\", () => {\n // Update node position (offset by center)\n node.x = group.x() - node.width / 2;\n node.y = group.y() - node.height / 2;\n this.drawConnections();\n this.connectionLayer.batchDraw();\n });\n\n group.on(\"dragstart\", () => {\n this.stage.container().style.cursor = \"grabbing\";\n });\n\n group.on(\"dragend\", () => {\n this.stage.container().style.cursor = \"pointer\";\n });\n\n this.areaLayer.add(group);\n } else {\n // Normal mode: rectangle with text\n const group = new Konva.Group({\n x: node.x,\n y: node.y,\n draggable: true,\n });\n\n const rect = new Konva.Rect({\n width: node.width,\n height: node.height,\n fill: this.settings.areaFillColor,\n stroke: isHighlighted ? this.settings.highlightColor : this.settings.areaStrokeColor,\n strokeWidth: isHighlighted ? 3 : 2,\n cornerRadius: 8,\n });\n\n const name = new Konva.Text({\n text: node.name,\n fontSize: this.settings.fontSize,\n fill: this.settings.textColor,\n width: node.width - 10,\n height: node.height - 20,\n x: 5,\n y: 10,\n align: \"center\",\n verticalAlign: \"middle\",\n ellipsis: true,\n wrap: \"word\",\n });\n\n const roomCount = new Konva.Text({\n text: `${node.roomCount} rooms`,\n fontSize: 10,\n fill: this.settings.connectionColor,\n width: node.width - 10,\n x: 5,\n y: node.height - 18,\n align: \"center\",\n });\n\n group.add(rect);\n group.add(name);\n group.add(roomCount);\n\n // Add click handler\n group.on(\"click tap\", () => {\n this.emitAreaClickEvent(node.areaId);\n });\n\n group.on(\"mouseenter\", () => {\n this.stage.container().style.cursor = \"pointer\";\n rect.stroke(this.settings.highlightColor);\n this.areaLayer.batchDraw();\n });\n\n group.on(\"mouseleave\", () => {\n this.stage.container().style.cursor = \"auto\";\n rect.stroke(isHighlighted ? this.settings.highlightColor : this.settings.areaStrokeColor);\n this.areaLayer.batchDraw();\n });\n\n group.on(\"dragmove\", () => {\n node.x = group.x();\n node.y = group.y();\n this.drawConnections();\n this.connectionLayer.batchDraw();\n });\n\n group.on(\"dragstart\", () => {\n this.stage.container().style.cursor = \"grabbing\";\n });\n\n group.on(\"dragend\", () => {\n this.stage.container().style.cursor = \"pointer\";\n });\n\n this.areaLayer.add(group);\n }\n }\n }\n\n private centerView() {\n // Calculate bounds of all areas\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n\n for (const [, node] of this.areaNodes) {\n minX = Math.min(minX, node.x);\n minY = Math.min(minY, node.y);\n maxX = Math.max(maxX, node.x + node.width);\n maxY = Math.max(maxY, node.y + node.height);\n }\n\n if (!isFinite(minX)) return;\n\n const contentWidth = maxX - minX;\n const contentHeight = maxY - minY;\n const centerX = minX + contentWidth / 2;\n const centerY = minY + contentHeight / 2;\n\n // Calculate zoom to fit\n const padding = 50;\n const scaleX = (this.stage.width() - padding * 2) / contentWidth;\n const scaleY = (this.stage.height() - padding * 2) / contentHeight;\n const fitZoom = Math.min(scaleX, scaleY, 1.5);\n\n this.setZoom(fitZoom);\n\n // Center the view\n const scale = this.stage.scaleX();\n this.stage.position({\n x: this.stage.width() / 2 - centerX * scale,\n y: this.stage.height() / 2 - centerY * scale,\n });\n }\n\n highlightArea(areaId: number | undefined) {\n this.highlightedArea = areaId;\n this.drawAreas();\n this.stage.batchDraw();\n }\n\n centerOnArea(areaId: number) {\n const node = this.areaNodes.get(areaId);\n if (!node) return;\n\n const scale = this.stage.scaleX();\n const centerX = node.x + node.width / 2;\n const centerY = node.y + node.height / 2;\n\n this.stage.position({\n x: this.stage.width() / 2 - centerX * scale,\n y: this.stage.height() / 2 - centerY * scale,\n });\n\n this.stage.batchDraw();\n }\n\n private emitAreaClickEvent(areaId: number) {\n const event = new CustomEvent<{areaId: number}>(\"areaclick\", {\n detail: {areaId},\n });\n this.stage.container().dispatchEvent(event);\n }\n\n getAreaNode(areaId: number): AreaNode | undefined {\n return this.areaNodes.get(areaId);\n }\n\n getConnectionGroups(): ConnectionGroup[] {\n return this.connectionGroups;\n }\n\n destroy() {\n this.stage.destroy();\n }\n}\n"],"x_google_ignoreList":[57,58,59,60,61],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/types/Settings.ts","../src/utils/color.ts","../src/TypedEventEmitter.ts","../src/lens/RoomLens.ts","../src/MapState.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/ScenePipeline.ts","../src/scene/elements/GridLayout.ts","../src/export/clipSceneToViewport.ts","../src/rendering/SceneManager.ts","../src/InteractionHandler.ts","../src/render/canvasGradient.ts","../src/render/RecordingLayer.ts","../src/scene/Shape.ts","../src/render/shapeToRecording.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/rendering/KonvaRenderBackend.ts","../src/style/shape/paintMap.ts","../src/style/shape/ParchmentStyle.ts","../src/style/shape/BlueprintStyle.ts","../src/style/shape/NeonStyle.ts","../src/style/shape/wobble.ts","../src/style/shape/SketchyStyle.ts","../src/style/shape/IsometricStyle.ts","../src/style/shape/ConstructionStyle.ts","../src/style/shape/SciFiStyle.ts","../src/style/shape/GradientRoomsStyle.ts","../src/style/index.ts","../src/rendering/MapRenderer.ts","../src/draw/DrawCommandBuilder.ts","../src/render/SvgRenderer.ts","../src/render/CanvasRenderer.ts","../src/export/flushSceneShapes.ts","../src/export/sceneBounds.ts","../src/export/SvgExporter.ts","../src/export/PngExporter.ts","../src/export/canvasToBytes.ts","../src/export/CanvasExporter.ts","../src/scene/AmbientLightStyle.ts","../src/overlay/AmbientLightOverlay.ts","../node_modules/node-dijkstra/libs/PriorityQueue.js","../node_modules/node-dijkstra/libs/removeDeepFromMap.js","../node_modules/node-dijkstra/libs/toDeepMap.js","../node_modules/node-dijkstra/libs/validateDeep.js","../node_modules/node-dijkstra/libs/Graph.js","../src/MapGraph.ts","../src/PathFinder.ts","../src/lens/composeLenses.ts","../src/lens/ExplorationLens.ts","../src/AreaMapRenderer.ts"],"sourcesContent":["const defaultRoomSize = 0.6;\nconst defaultLineWidth = 0.025;\nconst lineColor = 'rgb(225, 255, 225)';\n\nexport type LabelRenderMode = \"image\" | \"data\" | \"none\";\n\nexport type CullingMode = \"none\" | \"basic\" | \"indexed\";\n\nexport type RoomShape = \"rectangle\" | \"circle\" | \"roundedRectangle\";\n\nexport type RoomContextMenuEventDetail = {\n roomId: number;\n position: { x: number; y: number };\n};\n\nexport type RoomClickEventDetail = {\n roomId: number;\n position: { x: number; y: number };\n};\n\nexport type ZoomChangeEventDetail = {\n zoom: number;\n};\n\nexport type AreaExitClickEventDetail = {\n targetRoomId: number;\n position: { x: number; y: number };\n};\n\nexport type ViewportBounds = {\n minX: number;\n maxX: number;\n minY: number;\n maxY: number;\n};\n\nexport type PanEventDetail = ViewportBounds;\n\nexport type RendererEventMap = {\n roomclick: RoomClickEventDetail;\n roomcontextmenu: RoomContextMenuEventDetail;\n areaexitclick: AreaExitClickEventDetail;\n mapclick: undefined;\n pan: PanEventDetail;\n zoom: ZoomChangeEventDetail;\n};\n\n/**\n * Style configuration for the player position marker.\n * The player marker is a circle that indicates the current player position on the map.\n */\nexport type PlayerMarkerStyle = {\n /**\n * Hex color for the marker's stroke/border (e.g., \"#00e5b2\" for cyan-green).\n */\n strokeColor: string;\n\n /**\n * Opacity for the stroke/border (0.0 = fully transparent, 1.0 = fully opaque).\n */\n strokeAlpha: number;\n\n /**\n * Hex color for the marker's fill (e.g., \"#00e5b2\" for cyan-green).\n */\n fillColor: string;\n\n /**\n * Opacity for the fill (0.0 = fully transparent, 1.0 = fully opaque).\n * Setting this to 0 creates a hollow circle effect.\n */\n fillAlpha: number;\n\n /**\n * Width of the marker's stroke/border in map units (typically 0.01-0.3).\n */\n strokeWidth: number;\n\n /**\n * Size multiplier relative to the room size.\n * - 1.0 = marker radius equals room radius (matches room size)\n * - Values > 1.0 make the marker larger than rooms\n * - Values < 1.0 make the marker smaller than rooms\n *\n * Note: Room circles have radius = roomSize / 2, so sizeFactor is applied to that radius.\n */\n sizeFactor: number;\n\n /**\n * Dash pattern for the stroke as an array of [dash length, gap length].\n * Example: [0.05, 0.05] creates evenly spaced dashes.\n * Only applied when dashEnabled is true.\n */\n dash?: number[];\n\n /**\n * Whether to apply the dash pattern to the stroke.\n * When false, the stroke is solid regardless of the dash property.\n */\n dashEnabled: boolean;\n\n /**\n * When true, the marker shape matches the current roomShape setting\n * (rectangle, circle, or roundedRectangle) instead of always being a circle.\n */\n matchRoomShape: boolean;\n};\n\n/**\n * Style configuration for room highlights.\n * Highlights are rings drawn around rooms registered via {@link MapRenderer.renderHighlight}.\n * The highlight's color is supplied per-call; this style controls everything else.\n */\nexport type HighlightStyle = {\n /**\n * Opacity for the highlight's stroke/ring (0.0 = fully transparent, 1.0 = fully opaque).\n */\n strokeAlpha: number;\n\n /**\n * Opacity for the fill (0.0 = fully transparent / hollow, 1.0 = fully opaque).\n * The fill uses the per-highlight color. Defaults to 0 to preserve the hollow ring look.\n */\n fillAlpha: number;\n\n /**\n * Width of the highlight stroke in map units (typically 0.01-0.3).\n */\n strokeWidth: number;\n\n /**\n * Size multiplier relative to the room size.\n * - 1.0 = highlight matches room size\n * - Values > 1.0 produce a ring outside the room\n * - Values < 1.0 produce a smaller marker inside the room\n */\n sizeFactor: number;\n\n /**\n * Dash pattern for the stroke as an array of [dash length, gap length].\n * Only applied when dashEnabled is true.\n */\n dash?: number[];\n\n /**\n * Whether to apply the dash pattern to the stroke.\n * When false, the stroke is solid regardless of the dash property.\n */\n dashEnabled: boolean;\n\n /**\n * When true, the highlight shape matches the current roomShape setting\n * (rectangle, circle, or roundedRectangle). When false, the highlight is always a circle.\n */\n matchRoomShape: boolean;\n};\n\n\n/**\n * Settings for map rendering.\n * All properties can be modified at runtime to change the map's appearance and behavior.\n * Create with {@link createSettings} and pass to the renderer constructor.\n */\nexport type Settings = {\n /** Size of each room in map units (width/height for rectangles, diameter for circles). Default: 0.6 */\n roomSize: number;\n /** Width of lines (exit connections, room borders) in map units. Default: 0.025 */\n lineWidth: number;\n /** Color of exit connection lines as RGB string. Default: 'rgb(225, 255, 225)' */\n lineColor: string;\n /** Background color of the map container. Default: '#000000' */\n backgroundColor: string;\n /** When true, map instantly jumps to new position on room change. Default: false */\n instantMapMove: boolean;\n /** When true, highlights the current room and its exits with an overlay. Default: true */\n highlightCurrentRoom: boolean;\n /** Legacy flag for enabling/disabling culling (prefer cullingMode). Default: true */\n cullingEnabled: boolean;\n /** How off-screen elements are culled: \"none\" | \"basic\" | \"indexed\". Default: \"indexed\" */\n cullingMode: CullingMode;\n /** Custom culling bounds in map coordinates, or null for viewport bounds. Default: null */\n cullingBounds: { x: number; y: number; width: number; height: number } | null;\n /** How to render room labels: \"image\" | \"data\". Default: \"image\" */\n labelRenderMode: LabelRenderMode;\n /** When true, room labels have transparent backgrounds. Default: false */\n transparentLabels: boolean;\n /** Shape used to render rooms: \"rectangle\" | \"circle\" | \"roundedRectangle\". Default: \"rectangle\" */\n roomShape: RoomShape;\n /** Style configuration for the player position marker. */\n playerMarker: PlayerMarkerStyle;\n /** Style configuration for room highlights (added via {@link MapRenderer.renderHighlight}). */\n highlight: HighlightStyle;\n /** Whether to render a background grid. Default: false */\n gridEnabled: boolean;\n /** Grid line spacing in map units. Default: 1 */\n gridSize: number;\n /** Color of grid lines as CSS color string. Default: 'rgba(255, 255, 255, 0.07)' */\n gridColor: string;\n /** Width of grid lines in map units. Default: 0.02 */\n gridLineWidth: number;\n /** Whether to draw borders (strokes) on rooms. Default: true */\n borders: boolean;\n /** When true, rooms use frame rendering: fill=backgroundColor, stroke=envColor. Default: false */\n frameMode: boolean;\n /** When true, rooms use colored rendering: fill=envColor darkened 30%, stroke=envColor. Default: false */\n coloredMode: boolean;\n /** When true, rooms display a 3D emboss effect (rectangle/roundedRectangle only). Default: false */\n emboss: boolean;\n /** When true, displays the area name as a header text on the map. Default: false */\n areaName: boolean;\n /** Font family for the area name header. Default: 'sans-serif' */\n fontFamily: string;\n /** When true, uses bounds from all z-levels for viewport sizing, not just the current level. Default: false */\n uniformLevelSize: boolean;\n /** When true, renders a small text label next to area-exit arrows showing the target area name.\n * Exits leading to the same target area are grouped — one label per area at the cluster centroid. Default: false */\n areaExitLabels: boolean;\n /** Font size (in map units) for area-exit labels. Padding, corner radius, and stroke\n * scale proportionally. Default: 0.3 */\n areaExitLabelFontSize: number;\n};\n\n/** Creates a new Settings object with default values. */\nexport function createSettings(): Settings {\n return {\n roomSize: defaultRoomSize,\n lineWidth: defaultLineWidth,\n lineColor: lineColor,\n backgroundColor: '#000000',\n instantMapMove: false,\n highlightCurrentRoom: true,\n cullingEnabled: true,\n cullingMode: \"indexed\",\n cullingBounds: null,\n labelRenderMode: \"image\",\n transparentLabels: false,\n roomShape: \"rectangle\",\n playerMarker: {\n strokeColor: \"#00e5b2\",\n strokeAlpha: 1.0,\n fillColor: \"#00e5b2\",\n fillAlpha: 0.0,\n strokeWidth: 0.1,\n sizeFactor: 1.7,\n dash: [0.05, 0.05],\n dashEnabled: true,\n matchRoomShape: false,\n },\n highlight: {\n strokeAlpha: 1.0,\n fillAlpha: 0.0,\n strokeWidth: 0.1,\n sizeFactor: 1.425,\n dash: [0.05, 0.05],\n dashEnabled: true,\n matchRoomShape: true,\n },\n gridEnabled: false,\n gridSize: 1,\n gridColor: 'rgba(200, 200, 200, 0.15)',\n gridLineWidth: 0.03,\n borders: true,\n frameMode: false,\n coloredMode: false,\n emboss: false,\n areaName: true,\n fontFamily: 'sans-serif',\n uniformLevelSize: false,\n areaExitLabels: false,\n areaExitLabelFontSize: 0.3,\n };\n}\n","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\nexport function darkenColor(color: string, factor: number): string {\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]);\n g = parseInt(rgbMatch[2]);\n b = parseInt(rgbMatch[3]);\n } else if (color.startsWith('#') && color.length >= 7) {\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 } else {\n return color;\n }\n r = Math.round(r * (1 - factor));\n g = Math.round(g * (1 - factor));\n b = Math.round(b * (1 - factor));\n return `rgb(${r}, ${g}, ${b})`;\n}\n\nexport function lightenColor(color: string, factor: number): string {\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]);\n g = parseInt(rgbMatch[2]);\n b = parseInt(rgbMatch[3]);\n } else if (color.startsWith('#') && color.length >= 7) {\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 } else {\n return color;\n }\n r = Math.min(255, Math.round(r + (255 - r) * factor));\n g = Math.min(255, Math.round(g + (255 - g) * factor));\n b = Math.min(255, Math.round(b + (255 - b) * factor));\n return `rgb(${r}, ${g}, ${b})`;\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 * 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 {IMapReader} from \"./reader/MapReader\";\nimport type {IArea} from \"./reader/Area\";\nimport type {IPlane} from \"./reader/Plane\";\nimport type {Settings} from \"./types/Settings\";\nimport type {SvgOverlays} from \"./SvgTypes\";\nimport {TypedEventEmitter} from \"./TypedEventEmitter\";\nimport type {RoomLens} from \"./lens/RoomLens\";\nimport {ALL_VISIBLE} from \"./lens/RoomLens\";\n\nexport type HighlightEntry = { color: string; area: number; z: number };\nexport type PathEntry = { locations: number[]; color: string };\n\nexport type MapStateEventMap = {\n area: { area: IArea; zIndex: number };\n position: { roomId: number | undefined; center: boolean; areaChanged: boolean };\n center: { roomId: number; instant: boolean };\n highlight: { roomId: number; color: string | undefined };\n path: undefined;\n clear: undefined;\n lens: { lens: RoomLens };\n};\n\n/**\n * Pure data + events layer for map state.\n * No Konva, no DOM — just tracks what area is displayed,\n * where the player is, which rooms are highlighted, and active paths.\n *\n * Rendering backends subscribe to events and sync their visual state.\n */\nexport class MapState {\n readonly mapReader: IMapReader;\n readonly settings: Settings;\n readonly events = new TypedEventEmitter<MapStateEventMap>();\n\n currentArea?: number;\n currentAreaInstance?: IArea;\n currentZIndex?: number;\n currentAreaVersion?: number;\n positionRoomId?: number;\n centerRoomId?: number;\n highlights: Map<number, HighlightEntry> = new Map();\n paths: PathEntry[] = [];\n lens: RoomLens = ALL_VISIBLE;\n\n constructor(mapReader: IMapReader, settings: Settings) {\n this.mapReader = mapReader;\n this.settings = settings;\n }\n\n setLens(lens: RoomLens) {\n this.lens = lens;\n this.events.emit('lens', {lens});\n }\n\n /**\n * Set the displayed area and z-level.\n * Returns true if the area/z actually changed (or area version bumped).\n */\n setArea(id: number, zIndex: number): boolean {\n const area = this.mapReader.getArea(id);\n if (!area) return false;\n\n this.currentArea = id;\n this.currentAreaInstance = area;\n this.currentZIndex = zIndex;\n this.currentAreaVersion = area.getVersion();\n\n this.events.emit('area', {area, zIndex});\n return true;\n }\n\n setPosition(roomId: number, center: boolean = true): boolean {\n const room = this.mapReader.getRoom(roomId);\n if (!room) return false;\n\n // Switch area/z if needed\n const area = this.mapReader.getArea(room.area);\n const areaVersion = area?.getVersion();\n const areaChanged =\n this.currentArea !== room.area ||\n this.currentZIndex !== room.z ||\n (areaVersion !== undefined && this.currentAreaVersion !== areaVersion) ||\n (area !== undefined && this.currentAreaInstance !== area);\n\n if (areaChanged) {\n this.setArea(room.area, room.z);\n }\n\n this.positionRoomId = roomId;\n this.events.emit('position', {roomId, center, areaChanged});\n return true;\n }\n\n /**\n * Update position marker without centering or switching area.\n * Used when you want to show where the player is without moving the viewport.\n */\n updatePositionMarker(roomId: number) {\n this.positionRoomId = roomId;\n this.events.emit('position', {roomId, center: false, areaChanged: false});\n }\n\n /**\n * Re-emit position for the current room (e.g. after settings change).\n */\n refreshPosition() {\n if (this.positionRoomId !== undefined) {\n this.events.emit('position', {roomId: this.positionRoomId, center: false, areaChanged: false});\n }\n }\n\n clearPosition() {\n this.positionRoomId = undefined;\n this.events.emit('position', {roomId: undefined, center: false, areaChanged: false});\n }\n\n setCenterRoom(roomId: number, instant?: boolean): boolean {\n const room = this.mapReader.getRoom(roomId);\n if (!room) return false;\n\n const areaChanged =\n this.currentArea !== room.area ||\n this.currentZIndex !== room.z;\n\n if (areaChanged || this.needsAreaRedraw(room)) {\n this.setArea(room.area, room.z);\n }\n\n this.centerRoomId = roomId;\n this.events.emit('center', {roomId, instant: instant ?? areaChanged});\n return true;\n }\n\n needsAreaRedraw(room: MapData.Room): boolean {\n const area = this.mapReader.getArea(room.area);\n const areaVersion = area?.getVersion();\n return (\n (areaVersion !== undefined && this.currentAreaVersion !== areaVersion) ||\n (area !== undefined && this.currentAreaInstance !== area)\n );\n }\n\n addHighlight(roomId: number, color: string): boolean {\n const room = this.mapReader.getRoom(roomId);\n if (!room) return false;\n this.highlights.set(roomId, {color, area: room.area, z: room.z});\n this.events.emit('highlight', {roomId, color});\n return true;\n }\n\n removeHighlight(roomId: number) {\n if (!this.highlights.has(roomId)) return;\n this.highlights.delete(roomId);\n this.events.emit('highlight', {roomId, color: undefined});\n }\n\n hasHighlight(roomId: number): boolean {\n return this.highlights.has(roomId);\n }\n\n clearHighlights() {\n this.highlights.clear();\n this.events.emit('clear', undefined);\n }\n\n addPath(locations: number[], color: string = '#66E64D') {\n this.paths.push({locations, color});\n this.events.emit('path', undefined);\n }\n\n clearPaths() {\n this.paths = [];\n this.events.emit('path', undefined);\n }\n\n /**\n * Build the overlay descriptor for the current area/z,\n * merging stateful overlays with any extra overlays passed in.\n */\n getOverlaysForArea(extra?: SvgOverlays): SvgOverlays {\n const overlays: SvgOverlays = {...extra};\n\n // Position\n if (this.positionRoomId !== undefined) {\n const room = this.mapReader.getRoom(this.positionRoomId);\n if (room && room.area === this.currentArea && room.z === this.currentZIndex) {\n overlays.position = {roomId: this.positionRoomId};\n }\n }\n\n // Highlights (only for current area/z)\n const highlights: Array<{ roomId: number; color: string }> = [...(extra?.highlights ?? [])];\n for (const [roomId, entry] of this.highlights) {\n if (entry.area === this.currentArea && entry.z === this.currentZIndex) {\n highlights.push({roomId, color: entry.color});\n }\n }\n if (highlights.length > 0) overlays.highlights = highlights;\n\n // Paths\n const paths: Array<{ locations: number[]; color: string }> = [...(extra?.paths ?? [])];\n paths.push(...this.paths);\n if (paths.length > 0) overlays.paths = paths;\n\n return overlays;\n }\n\n /**\n * Get the effective plane bounds (respects uniformLevelSize setting).\n */\n getEffectiveBounds(area: IArea, plane: IPlane) {\n return this.settings.uniformLevelSize ? area.getFullBounds() : plane.getBounds();\n }\n\n /**\n * Compute export bounds for a given area/plane, optionally centered on a room.\n */\n computeExportBounds(area: IArea, plane: IPlane, roomId: number | undefined, padding: number) {\n if (roomId !== undefined) {\n const room = this.mapReader.getRoom(roomId);\n if (!room) throw new Error(`Room ${roomId} not found`);\n return {x: room.x - padding, y: room.y - padding, w: padding * 2, h: padding * 2};\n }\n const b = this.getEffectiveBounds(area, plane);\n const areaName = this.settings.areaName ? area.getAreaName() : undefined;\n const nameOverhead = areaName ? 7 : 0;\n const nameLeftOffset = areaName ? 3.5 : 0;\n const minX = b.minX - nameLeftOffset;\n const minY = b.minY - nameOverhead;\n const nameRight = areaName ? (b.minX - 3.5 + areaName.length * 2.5 * 0.6) : -Infinity;\n const maxX = Math.max(b.maxX, nameRight);\n return {x: minX - padding, y: minY - padding, w: (maxX - minX) + padding * 2, h: (b.maxY - minY) + padding * 2};\n }\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\";\n\nexport type RoomColors = {\n fillColor: string;\n strokeColor: string;\n borderWidth: number;\n symbolColor: string;\n envColor: string;\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 const strokeColor = strokeOverride\n ? ((settings.frameMode || settings.coloredMode) ? brightEnvColor : strokeOverride)\n : ((settings.frameMode || settings.coloredMode) ? brightEnvColor : settings.lineColor);\n const borderWidth = settings.borders ? settings.lineWidth : 0;\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};\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} 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\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 {fillColor, strokeColor, borderWidth, symbolColor} = computeRoomColors(\n room, mapReader, settings, options.strokeOverride,\n );\n\n const rs = settings.roomSize;\n const children: Shape[] = [];\n\n const emboss = computeEmboss(fillColor, settings);\n // When emboss is active, skip the regular border — the emboss lines serve as the border.\n const drawBorder = emboss ? 0 : borderWidth;\n\n const multiRing = 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 },\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 },\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 {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 {movePoint, movePointCircle, movePointRoundedRect} from \"./directions\";\nimport {longToShort, regularExits} from \"./reader/Exit\";\n\ntype Bounds = { x: number; y: number; width: number; height: number };\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\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): SceneBuildResult {\n this.linkShapes = [];\n this.roomShapes = [];\n this.topLabelShapes = [];\n this.labelShapeRefs = [];\n this.specialExitShapeRefs = [];\n this.stubShapeRefs = [];\n this.areaExitLabelShapeRefs = [];\n\n // Labels\n this.renderLabels(plane.getLabels(), area.getAreaId());\n\n // Link exits (two-way connections)\n const exitResult = this.renderLinkExits(area.getLinkExits(zIndex), zIndex, lens);\n\n // Visible rooms only — the lens (e.g. exploration) drops the rest.\n const visibleRooms = (plane.getRooms() ?? []).filter(r => lens.isVisible(r));\n\n // Rooms (with stubs, special exits, inner exits)\n const roomResult = this.renderRooms(visibleRooms, zIndex);\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, {flatPipeline: true});\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 from special exits (custom lines to other areas)\n this.exitRenderer.getSpecialExitAreaTargets(room).forEach(zone => {\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 // Area exit hit zones from inner exits (up/down/in/out to other areas)\n this.exitRenderer.getInnerExitAreaTargets(room).forEach(zone => {\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 // 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 // --- 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 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 {Settings, ViewportBounds} from \"../../types/Settings\";\nimport type {LineShape, Shape} from \"../Shape\";\n\nexport interface GridLayoutOptions {\n /** Inverse coordinate transform (rendered → Cartesian) for warped pipelines. */\n inverseTransform?: (x: number, y: number) => {x: number; y: number};\n}\n\n/**\n * Pure layout for the background grid. Returns the line shapes that span the\n * given viewport bounds with one extra grid step of buffer on every edge.\n *\n * Returns an empty array when grids are disabled. Caching is the caller's\n * responsibility (the previous renderer cached by snapped bounds).\n */\nexport function layoutGrid(\n viewportBounds: ViewportBounds,\n settings: Settings,\n options: GridLayoutOptions = {},\n): LineShape[] {\n if (!settings.gridEnabled) return [];\n\n const inv = options.inverseTransform ?? ((x, y) => ({x, y}));\n const {minX: vMinX, maxX: vMaxX, minY: vMinY, maxY: vMaxY} = viewportBounds;\n const c1 = inv(vMinX, vMinY);\n const c2 = inv(vMaxX, vMinY);\n const c3 = inv(vMaxX, vMaxY);\n const c4 = inv(vMinX, vMaxY);\n const cartMinX = Math.min(c1.x, c2.x, c3.x, c4.x);\n const cartMaxX = Math.max(c1.x, c2.x, c3.x, c4.x);\n const cartMinY = Math.min(c1.y, c2.y, c3.y, c4.y);\n const cartMaxY = Math.max(c1.y, c2.y, c3.y, c4.y);\n\n const buffer = settings.gridSize * 2;\n const left = Math.floor((cartMinX - buffer) / settings.gridSize) * settings.gridSize;\n const right = Math.ceil((cartMaxX + buffer) / settings.gridSize) * settings.gridSize;\n const top = Math.floor((cartMinY - buffer) / settings.gridSize) * settings.gridSize;\n const bottom = Math.ceil((cartMaxY + buffer) / settings.gridSize) * settings.gridSize;\n\n const lines: LineShape[] = [];\n const paint = {stroke: settings.gridColor, strokeWidth: settings.gridLineWidth};\n\n for (let x = left; x <= right; x += settings.gridSize) {\n lines.push({\n type: \"line\",\n points: [x, top, x, bottom],\n paint,\n grid: true,\n layer: \"grid\",\n });\n }\n for (let y = top; y <= bottom; y += settings.gridSize) {\n lines.push({\n type: \"line\",\n points: [left, y, right, y],\n paint,\n grid: true,\n layer: \"grid\",\n });\n }\n return lines;\n}\n\n/** Snapped bounds — for cache-key comparison by callers. */\nexport function gridSnappedBounds(\n viewportBounds: ViewportBounds,\n settings: Settings,\n options: GridLayoutOptions = {},\n): {left: number; right: number; top: number; bottom: number} | null {\n if (!settings.gridEnabled) return null;\n\n const inv = options.inverseTransform ?? ((x, y) => ({x, y}));\n const {minX: vMinX, maxX: vMaxX, minY: vMinY, maxY: vMaxY} = viewportBounds;\n const c1 = inv(vMinX, vMinY);\n const c2 = inv(vMaxX, vMinY);\n const c3 = inv(vMaxX, vMaxY);\n const c4 = inv(vMinX, vMaxY);\n const cartMinX = Math.min(c1.x, c2.x, c3.x, c4.x);\n const cartMaxX = Math.max(c1.x, c2.x, c3.x, c4.x);\n const cartMinY = Math.min(c1.y, c2.y, c3.y, c4.y);\n const cartMaxY = Math.max(c1.y, c2.y, c3.y, c4.y);\n\n const buffer = settings.gridSize * 2;\n return {\n left: Math.floor((cartMinX - buffer) / settings.gridSize) * settings.gridSize,\n right: Math.ceil((cartMaxX + buffer) / settings.gridSize) * settings.gridSize,\n top: Math.floor((cartMinY - buffer) / settings.gridSize) * settings.gridSize,\n bottom: Math.ceil((cartMaxY + buffer) / settings.gridSize) * settings.gridSize,\n };\n}\n","import type {SceneBuildResult, SceneShapesByLayer} from \"../ScenePipeline\";\nimport type {ViewportBounds, Settings} from \"../types/Settings\";\nimport type {Shape} from \"../scene/Shape\";\nimport {layoutGrid} from \"../scene/elements/GridLayout\";\n\n/**\n * Interactive-path optimised alternative to {@link clipSceneToViewport}.\n *\n * Returns a single `Map<Shape, boolean>` where `true` means the shape is\n * inside the viewport and `false` means it should be hidden. Shapes absent\n * from the map are \"unmanaged\" pass-throughs (noScaling labels, overlays) and\n * should be treated as visible.\n *\n * Allocation budget per frame: 1 Map instead of the 12 Sets + 2 filtered\n * arrays that {@link clipSceneToViewport} produces.\n */\nexport function buildCullingVisibilityMap(\n result: SceneBuildResult,\n viewportBounds: ViewportBounds,\n settings: Settings,\n transforms?: SceneTransforms,\n): Map<Shape, boolean> {\n if (!settings.cullingEnabled) return new Map();\n\n const {minX, maxX, minY, maxY} = viewportBounds;\n const half = settings.roomSize / 2;\n const fn = transforms?.forward;\n\n const inView = (bMinX: number, bMinY: number, bMaxX: number, bMaxY: number): boolean => {\n if (fn) {\n const tb = transformedBbox(bMinX, bMinY, bMaxX, bMaxY, fn);\n return tb.maxX >= minX && tb.minX <= maxX && tb.maxY >= minY && tb.minY <= maxY;\n }\n return bMaxX >= minX && bMinX <= maxX && bMaxY >= minY && bMinY <= maxY;\n };\n\n const visibility = new Map<Shape, boolean>();\n\n for (const {room, shape} of result.roomShapeRefs.values()) {\n visibility.set(shape, inView(room.x - half, room.y - half, room.x + half, room.y + half));\n }\n for (const {shape, bounds: b} of result.standaloneExitShapeRefs) {\n visibility.set(shape, inView(b.x, b.y, b.x + b.width, b.y + b.height));\n }\n for (const {shape, bounds: b} of result.labelShapeRefs) {\n visibility.set(shape, inView(b.x, b.y, b.x + b.width, b.y + b.height));\n }\n for (const {shape, bounds: b} of result.specialExitShapeRefs) {\n visibility.set(shape, inView(b.x, b.y, b.x + b.width, b.y + b.height));\n }\n for (const {shape, bounds: b} of result.stubShapeRefs) {\n visibility.set(shape, inView(b.x, b.y, b.x + b.width, b.y + b.height));\n }\n for (const {shape, bounds: b} of result.areaExitLabelShapeRefs) {\n visibility.set(shape, inView(b.x, b.y, b.x + b.width, b.y + b.height));\n }\n\n return visibility;\n}\n\ntype TransformFn = (x: number, y: number) => {x: number; y: number};\n\n/** Forward and inverse coordinate transforms for styles that warp space (e.g. Isometric). */\nexport type SceneTransforms = {\n /** World → scene (for culling bounding-box projection). */\n forward?: TransformFn;\n /** Scene → world (for grid-line Cartesian-bounds computation). */\n inverse?: TransformFn;\n};\n\nfunction transformedBbox(\n minX: number, minY: number, maxX: number, maxY: number,\n fn: TransformFn,\n): {minX: number; minY: number; maxX: number; maxY: number} {\n const c1 = fn(minX, minY);\n const c2 = fn(maxX, minY);\n const c3 = fn(maxX, maxY);\n const c4 = fn(minX, maxY);\n return {\n minX: Math.min(c1.x, c2.x, c3.x, c4.x),\n minY: Math.min(c1.y, c2.y, c3.y, c4.y),\n maxX: Math.max(c1.x, c2.x, c3.x, c4.x),\n maxY: Math.max(c1.y, c2.y, c3.y, c4.y),\n };\n}\n\n/**\n * Filter scene shapes to only those that intersect the given viewport, and\n * generate the grid for that viewport — the shared cull step used by export\n * paths (SVG, PNG).\n *\n * Uses {@link buildCullingVisibilityMap} for the predicate so culling logic\n * lives in one place. Shapes absent from the map are unmanaged pass-throughs\n * (noScaling labels, overlays) and are always included.\n *\n * Interactive path uses {@link buildCullingVisibilityMap} directly and skips\n * building these filtered arrays.\n *\n * When `settings.cullingEnabled` is false the original {@link SceneShapesByLayer}\n * is returned (with grid appended).\n *\n * `transforms.forward` projects world → rendered space for styles like\n * Isometric that warp coordinates (pass `style.worldToScene` when available).\n * `transforms.inverse` projects rendered → world space (pass `style.sceneToWorld`)\n * for correct grid-line Cartesian-bounds computation under warped styles.\n */\nexport function clipSceneToViewport(\n result: SceneBuildResult,\n viewportBounds: ViewportBounds,\n settings: Settings,\n transforms?: SceneTransforms,\n): SceneShapesByLayer {\n const grid = layoutGrid(viewportBounds, settings, {inverseTransform: transforms?.inverse});\n\n if (!settings.cullingEnabled) {\n return {...result.sceneShapes, grid};\n }\n\n const visibility = buildCullingVisibilityMap(result, viewportBounds, settings, transforms);\n return {\n grid,\n link: result.sceneShapes.link.filter(s => visibility.get(s) ?? true),\n room: result.sceneShapes.room.filter(s => visibility.get(s) ?? true),\n topLabel: result.sceneShapes.topLabel,\n };\n}\n","import type {IArea} from \"../reader/Area\";\nimport type {IPlane} from \"../reader/Plane\";\nimport type {Settings} from \"../types/Settings\";\nimport {ScenePipeline} from \"../ScenePipeline\";\nimport type {\n SceneBuildResult,\n SceneShapesByLayer,\n AreaExitHitZone,\n DrawnExitEntry,\n DrawnSpecialExitEntry,\n DrawnStubEntry,\n} from \"../ScenePipeline\";\nimport {clipSceneToViewport, buildCullingVisibilityMap} from \"../export/clipSceneToViewport\";\nimport type {Camera} from \"../camera/Camera\";\nimport type {CoordFn} from \"../coord/CoordFn\";\nimport {IDENTITY_TRANSFORM} from \"../coord/CoordFn\";\nimport type {SceneTransforms} from \"../export/clipSceneToViewport\";\nimport type {GroupShape, Shape} from \"../scene/Shape\";\nimport type {IMapReader} from \"../reader/MapReader\";\nimport type {RoomLens} from \"../lens/RoomLens\";\nimport type {ExitDrawData} from \"../ExitRenderer\";\n\nexport interface CullStats {\n visibleRooms: number;\n totalRooms: number;\n visibleExits: number;\n}\n\nexport interface CullOutput {\n shapes: SceneShapesByLayer;\n stats: CullStats;\n}\n\nconst EMPTY_SCENE: SceneShapesByLayer = {grid: [], link: [], room: [], topLabel: []};\n\n/**\n * Encapsulates the scene build + cull pipeline for the interactive renderer.\n *\n * Owns {@link ScenePipeline} and the last {@link SceneBuildResult}. Backends\n * call {@link rebuild} when the area/plane changes, then {@link cull} on each\n * viewport update to get the currently-visible shapes. Neither the pipeline\n * nor {@link clipSceneToViewport} need to be referenced outside this class.\n */\nexport class SceneManager {\n private pipeline: ScenePipeline;\n private lastBuildResult?: SceneBuildResult;\n private standaloneExitShapeSet: Set<Shape> = new Set();\n\n constructor(\n private readonly camera: Camera,\n private readonly settings: Settings,\n mapReader: IMapReader,\n ) {\n this.pipeline = new ScenePipeline(mapReader, settings);\n }\n\n get exitRenderer() {\n return this.pipeline.exitRenderer;\n }\n\n get lastResult(): SceneBuildResult | undefined {\n return this.lastBuildResult;\n }\n\n get drawnExits(): readonly DrawnExitEntry[] {\n return this.lastBuildResult?.drawnExits ?? [];\n }\n\n get drawnSpecialExits(): readonly DrawnSpecialExitEntry[] {\n return this.lastBuildResult?.drawnSpecialExits ?? [];\n }\n\n get drawnStubs(): readonly DrawnStubEntry[] {\n return this.lastBuildResult?.drawnStubs ?? [];\n }\n\n get areaExitHitZones(): readonly AreaExitHitZone[] {\n return this.lastBuildResult?.areaExitHitZones ?? [];\n }\n\n get hitShapes(): readonly Shape[] {\n return this.lastBuildResult?.hitShapes ?? [];\n }\n\n rebuild(area: IArea, plane: IPlane, zIndex: number, lens?: RoomLens): SceneBuildResult {\n this.lastBuildResult = this.pipeline.buildScene(area, plane, zIndex, lens);\n this.standaloneExitShapeSet = new Set(\n this.lastBuildResult.standaloneExitShapeRefs.map(r => r.shape),\n );\n return this.lastBuildResult;\n }\n\n buildExitShape(data: ExitDrawData): GroupShape {\n return this.pipeline.buildExitShape(data);\n }\n\n reset(): void {\n this.lastBuildResult = undefined;\n this.standaloneExitShapeSet = new Set();\n }\n\n resetPipeline(mapReader: IMapReader): void {\n this.pipeline = new ScenePipeline(mapReader, this.settings);\n this.reset();\n }\n\n /**\n * Lightweight cull for the interactive render path. Returns a\n * `Map<Shape, boolean>` where absent shapes are unmanaged pass-throughs\n * (always visible). Avoids the 12 Sets + 2 filtered arrays produced by\n * the full {@link cull} path.\n */\n cullInteractive(coordinateTransform: CoordFn = IDENTITY_TRANSFORM): Map<Shape, boolean> {\n if (!this.lastBuildResult) return new Map();\n const viewport = this.camera.getCullingViewport(this.settings.cullingBounds);\n const transforms: SceneTransforms | undefined = coordinateTransform !== IDENTITY_TRANSFORM\n ? {forward: coordinateTransform as (x: number, y: number) => {x: number; y: number}}\n : undefined;\n return buildCullingVisibilityMap(this.lastBuildResult, viewport, this.settings, transforms);\n }\n\n cull(coordinateTransform: CoordFn = IDENTITY_TRANSFORM): CullOutput {\n if (!this.lastBuildResult) {\n return {shapes: EMPTY_SCENE, stats: {visibleRooms: 0, totalRooms: 0, visibleExits: 0}};\n }\n\n const viewport = this.camera.getCullingViewport(this.settings.cullingBounds);\n const transforms: SceneTransforms | undefined = coordinateTransform !== IDENTITY_TRANSFORM\n ? {forward: coordinateTransform as (x: number, y: number) => {x: number; y: number}}\n : undefined;\n const shapes = clipSceneToViewport(this.lastBuildResult, viewport, this.settings, transforms);\n\n return {\n shapes,\n stats: {\n visibleRooms: shapes.room.length,\n totalRooms: this.lastBuildResult.roomShapeRefs.size,\n visibleExits: shapes.link.filter(s => this.standaloneExitShapeSet.has(s)).length,\n },\n };\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","/**\n * Shared Canvas2D fill resolver — turns a {@link FillStyle} (string or\n * gradient) into something assignable to `ctx.fillStyle`.\n *\n * Used by both {@link RecordingLayerNode} (interactive Konva replay) and\n * {@link renderToCanvas} (PNG export) so gradient semantics stay identical\n * across pipelines.\n */\n\nimport type {FillStyle} from \"../scene/Shape\";\n\nexport function resolveFill(\n ctx: CanvasRenderingContext2D,\n fill: FillStyle,\n): string | CanvasGradient {\n if (typeof fill === \"string\") return fill;\n if (fill.type === \"linear\") {\n const g = ctx.createLinearGradient(fill.x0, fill.y0, fill.x1, fill.y1);\n for (const s of fill.stops) g.addColorStop(s.offset, s.color);\n return g;\n }\n const fx = fill.fx ?? fill.cx;\n const fy = fill.fy ?? fill.cy;\n const fr = fill.fr ?? 0;\n const g = ctx.createRadialGradient(fx, fy, fr, fill.cx, fill.cy, fill.r);\n for (const s of fill.stops) g.addColorStop(s.offset, s.color);\n return g;\n}\n","/**\n * Konva-side replay infrastructure for {@link Shape}-derived draw commands.\n *\n * A {@link RecordingGroupNode} captures one positional group of low-level\n * draw commands (rect / circle / line / polygon / text / image, optionally\n * wrapped by an affine transform). It stays as plain data until either\n * - `materialize()` makes it into a single `Konva.Group` containing one\n * `Konva.Shape` whose `sceneFunc` replays the captured commands, or\n * - it is added to a {@link RecordingLayerNode} / {@link DrawCommandLayerNode}\n * that batches many groups into one shared `Konva.Shape` per layer.\n *\n * The single-`sceneFunc` per layer is the fast path used by the main scene\n * because it lets culling toggle individual entries (`DrawEntry.visible`)\n * without paying Konva's per-node overhead.\n */\n\nimport Konva from \"konva\";\nimport {BASE_SCALE} from \"../camera/Camera\";\nimport type {FillStyle} from \"../scene/Shape\";\nimport {resolveFill} from \"./canvasGradient\";\n\n// --- Internal draw command types ---\n// These are the low-level Canvas2D-shaped commands captured per\n// RecordingGroupNode. They differ from src/draw/DrawCommand.ts (which is\n// camera-transformed render-space output for export pipelines): commands\n// here stay in group-local coordinates and are replayed under whatever\n// transform Konva has already applied to the layer.\n\ntype RectCommand = { type: 'rect'; x: number; y: number; w: number; h: number; fill?: FillStyle; stroke?: string; sw: number; cr: number; dash?: number[] };\ntype CircleCommand = { type: 'circle'; cx: number; cy: number; r: number; fill?: FillStyle; stroke?: string; sw: number; dash?: number[] };\ntype LineCommand = { type: 'line'; points: number[]; stroke?: string; sw: number; dash?: number[]; lineCap?: string; lineJoin?: string; alpha?: number };\ntype PolygonCommand = { type: 'polygon'; vertices: number[]; fill?: FillStyle; stroke?: string; sw: number };\ntype TextCommand = { type: 'text'; x: number; y: number; text: string; fontSize: number; fontFamily: string; fontStyle: string; fill: string; stroke?: string; sw: number; align: string; vAlign: string; w: number; h: number; baselineRatio?: number; transform?: [number, number, number, number, number, number] };\ntype ImageCommand = { type: 'image'; x: number; y: number; w: number; h: number; image: HTMLImageElement | any; transform?: [number, number, number, number, number, number] };\n\nexport type RecordingDrawCommand =\n | RectCommand\n | CircleCommand\n | LineCommand\n | PolygonCommand\n | TextCommand\n | ImageCommand;\n\n// --- Canvas2D replay ---\n\nfunction replayCommand(ctx: CanvasRenderingContext2D, cmd: RecordingDrawCommand) {\n switch (cmd.type) {\n case 'rect': {\n ctx.beginPath();\n if (cmd.cr > 0 && typeof ctx.roundRect === 'function') {\n ctx.roundRect(cmd.x, cmd.y, cmd.w, cmd.h, cmd.cr);\n } else {\n ctx.rect(cmd.x, cmd.y, cmd.w, cmd.h);\n }\n if (cmd.fill) {\n ctx.fillStyle = resolveFill(ctx, cmd.fill);\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n if (cmd.dash) ctx.setLineDash(cmd.dash); else ctx.setLineDash([]);\n ctx.stroke();\n }\n break;\n }\n case 'circle': {\n ctx.beginPath();\n ctx.arc(cmd.cx, cmd.cy, cmd.r, 0, Math.PI * 2);\n if (cmd.fill) {\n ctx.fillStyle = resolveFill(ctx, cmd.fill);\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n if (cmd.dash) ctx.setLineDash(cmd.dash); else ctx.setLineDash([]);\n ctx.stroke();\n }\n break;\n }\n case 'line': {\n if (cmd.points.length < 4) break;\n const savedAlpha = ctx.globalAlpha;\n if (cmd.alpha !== undefined) ctx.globalAlpha = cmd.alpha;\n ctx.beginPath();\n ctx.moveTo(cmd.points[0], cmd.points[1]);\n for (let i = 2; i < cmd.points.length; i += 2) {\n ctx.lineTo(cmd.points[i], cmd.points[i + 1]);\n }\n if (cmd.stroke) ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n if (cmd.dash) ctx.setLineDash(cmd.dash); else ctx.setLineDash([]);\n if (cmd.lineCap) ctx.lineCap = cmd.lineCap as CanvasLineCap;\n if (cmd.lineJoin) ctx.lineJoin = cmd.lineJoin as CanvasLineJoin;\n ctx.stroke();\n if (cmd.alpha !== undefined) ctx.globalAlpha = savedAlpha;\n break;\n }\n case 'polygon': {\n if (cmd.vertices.length < 4) break;\n ctx.beginPath();\n ctx.moveTo(cmd.vertices[0], cmd.vertices[1]);\n for (let i = 2; i < cmd.vertices.length; i += 2) {\n ctx.lineTo(cmd.vertices[i], cmd.vertices[i + 1]);\n }\n ctx.closePath();\n if (cmd.fill) {\n ctx.fillStyle = resolveFill(ctx, cmd.fill);\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n ctx.setLineDash([]);\n ctx.stroke();\n }\n break;\n }\n case 'text': {\n // Sub-pixel font sizes break Canvas2D text metrics on some\n // engines (notably node-canvas). Render at TEXT_SCALE × the\n // requested size and counter-scale so output stays pixel-correct.\n const TEXT_SCALE = 100;\n const scaledSize = cmd.fontSize * TEXT_SCALE;\n const font = `${cmd.fontStyle} ${scaledSize}px ${cmd.fontFamily}`;\n ctx.save();\n ctx.font = font;\n ctx.fillStyle = cmd.fill;\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw * TEXT_SCALE;\n ctx.lineJoin = 'round';\n }\n const hasBaselineRatio = cmd.baselineRatio !== undefined;\n if (cmd.transform) {\n ctx.transform(...cmd.transform);\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n ctx.textAlign = 'center';\n if (hasBaselineRatio) {\n ctx.textBaseline = 'alphabetic';\n const by = (cmd.h / 2 + cmd.baselineRatio! * cmd.fontSize) * TEXT_SCALE;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, cmd.w * TEXT_SCALE / 2, by);\n ctx.fillText(cmd.text, cmd.w * TEXT_SCALE / 2, by);\n } else {\n ctx.textBaseline = 'middle';\n const mx = cmd.w * TEXT_SCALE / 2;\n const my = cmd.h * TEXT_SCALE / 2;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, mx, my);\n ctx.fillText(cmd.text, mx, my);\n }\n } else if (cmd.w > 0 && cmd.h > 0) {\n ctx.textAlign = (cmd.align || 'left') as CanvasTextAlign;\n const tx = cmd.align === 'center' ? cmd.x + cmd.w / 2 : cmd.x;\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n if (cmd.vAlign === 'middle' && hasBaselineRatio) {\n ctx.textBaseline = 'alphabetic';\n const ty = cmd.y + cmd.h / 2 + cmd.baselineRatio! * cmd.fontSize;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n ctx.fillText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n } else {\n ctx.textBaseline = cmd.vAlign === 'middle' ? 'middle' : 'top';\n const ty = cmd.vAlign === 'middle' ? cmd.y + cmd.h / 2 : cmd.y;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n ctx.fillText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n }\n } else {\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, cmd.x * TEXT_SCALE, cmd.y * TEXT_SCALE);\n ctx.fillText(cmd.text, cmd.x * TEXT_SCALE, cmd.y * TEXT_SCALE);\n }\n ctx.restore();\n break;\n }\n case 'image': {\n if (!cmd.image) break;\n if (cmd.transform) {\n ctx.save();\n ctx.transform(...cmd.transform);\n ctx.drawImage(cmd.image, 0, 0, cmd.w, cmd.h);\n ctx.restore();\n } else {\n ctx.drawImage(cmd.image, cmd.x, cmd.y, cmd.w, cmd.h);\n }\n break;\n }\n }\n}\n\n// --- Recording group node ---\n\n/**\n * One positional group of recorded draw commands. Used as both the unit a\n * shape walker emits and the cull-tracking handle inside\n * {@link DrawCommandLayerNode}. Lazily materializes into a `Konva.Group` /\n * `Konva.Shape` pair when added directly to a Konva layer.\n */\nexport class RecordingGroupNode {\n x: number;\n y: number;\n _visible = true;\n noScaling = false;\n readonly commands: RecordingDrawCommand[] = [];\n /** Lazily created when this group is materialized for a Konva.Layer. */\n _konvaGroup?: Konva.Group;\n\n constructor(x: number, y: number) {\n this.x = x;\n this.y = y;\n }\n\n setVisible(visible: boolean) {\n this._visible = visible;\n this._konvaGroup?.visible(visible);\n }\n\n isVisible(): boolean {\n return this._visible;\n }\n\n destroy() {\n this._konvaGroup?.destroy();\n this._konvaGroup = undefined;\n this.commands.length = 0;\n }\n\n setPosition(x: number, y: number) {\n this.x = x;\n this.y = y;\n this._konvaGroup?.position({x, y});\n }\n\n getPosition() {\n return {x: this.x, y: this.y};\n }\n\n moveToTop() {\n this._konvaGroup?.moveToTop();\n }\n\n /**\n * Materialize this recording as a Konva.Group + Konva.Shape. Used by\n * {@link MaterializingLayerNode} for layers that want individual nodes\n * (overlay, position) rather than a single bulk replay.\n */\n materialize(): Konva.Group {\n if (this._konvaGroup) return this._konvaGroup;\n const group = new Konva.Group({\n x: this.x, y: this.y,\n listening: false,\n visible: this._visible,\n });\n const cmds = this.commands;\n group.add(new Konva.Shape({\n listening: false,\n perfectDrawEnabled: false,\n sceneFunc: (context) => {\n const ctx = context._context as CanvasRenderingContext2D;\n for (const cmd of cmds) {\n replayCommand(ctx, cmd);\n }\n },\n }));\n this._konvaGroup = group;\n return group;\n }\n}\n\n// --- Pure-data draw entry ---\n\n/**\n * Lightweight record extracted from a {@link RecordingGroupNode} when added\n * to a {@link DrawCommandLayerNode}. Contains only plain data so culling\n * callbacks can toggle visibility without a Konva node reference.\n */\nexport type DrawEntry = {\n x: number;\n y: number;\n noScaling: boolean;\n readonly commands: RecordingDrawCommand[];\n visible: boolean;\n};\n\n// --- DrawCommandLayerNode ---\n\n/**\n * LayerNode backed by a single Konva.Shape whose sceneFunc replays\n * {@link DrawEntry} objects. Used for the main scene layer where individual\n * shapes need cull toggling.\n */\nexport class DrawCommandLayerNode {\n private readonly entries: DrawEntry[] = [];\n private readonly nodeToEntry = new Map<RecordingGroupNode, DrawEntry>();\n private readonly konvaLayer: Konva.Layer;\n private konvaShape: Konva.Shape;\n\n constructor(konvaLayer: Konva.Layer) {\n this.konvaLayer = konvaLayer;\n konvaLayer.destroyChildren();\n const self = this;\n this.konvaShape = new Konva.Shape({\n listening: false,\n perfectDrawEnabled: false,\n sceneFunc: (context) => {\n const ctx = context._context as CanvasRenderingContext2D;\n const base = ctx.getTransform();\n const a = base.a, b = base.b, c = base.c, d = base.d;\n for (const entry of self.entries) {\n if (!entry.visible) continue;\n const tx = a * entry.x + c * entry.y + base.e;\n const ty = b * entry.x + d * entry.y + base.f;\n if (entry.noScaling) {\n ctx.setTransform(BASE_SCALE, 0, 0, BASE_SCALE, tx, ty);\n } else {\n ctx.setTransform(a, b, c, d, tx, ty);\n }\n for (const cmd of entry.commands) {\n replayCommand(ctx, cmd);\n }\n }\n ctx.setTransform(base);\n },\n });\n konvaLayer.add(this.konvaShape);\n }\n\n addNode(node: RecordingGroupNode): void {\n const entry: DrawEntry = {\n x: node.x,\n y: node.y,\n noScaling: node.noScaling,\n commands: node.commands,\n visible: node._visible,\n };\n this.entries.push(entry);\n this.nodeToEntry.set(node, entry);\n this.ensureShape();\n }\n\n /** Return the DrawEntry created when `node` was added, or undefined if not found. */\n getEntry(node: RecordingGroupNode): DrawEntry | undefined {\n return this.nodeToEntry.get(node);\n }\n\n destroyChildren(): void {\n this.entries.length = 0;\n this.nodeToEntry.clear();\n }\n\n batchDraw(): void {\n this.konvaLayer.batchDraw();\n }\n\n private ensureShape(): void {\n if (!this.konvaShape.getParent()) {\n this.konvaLayer.add(this.konvaShape);\n }\n }\n}\n\n// --- RecordingLayerNode ---\n\n/**\n * Layer backed by a single Konva.Shape whose sceneFunc replays every added\n * {@link RecordingGroupNode} in order. Used for layers that don't need\n * per-shape cull toggling (grid, top-label).\n */\nexport class RecordingLayerNode {\n private groups: RecordingGroupNode[] = [];\n private readonly konvaLayer: Konva.Layer;\n private konvaShape: Konva.Shape;\n\n constructor(konvaLayer: Konva.Layer) {\n this.konvaLayer = konvaLayer;\n konvaLayer.destroyChildren();\n const self = this;\n this.konvaShape = new Konva.Shape({\n listening: false,\n perfectDrawEnabled: false,\n sceneFunc: (context) => {\n const ctx = context._context as CanvasRenderingContext2D;\n const base = ctx.getTransform();\n const a = base.a, b = base.b, c = base.c, d = base.d;\n for (const group of self.groups) {\n if (!group._visible) continue;\n const tx = a * group.x + c * group.y + base.e;\n const ty = b * group.x + d * group.y + base.f;\n if (group.noScaling) {\n ctx.setTransform(BASE_SCALE, 0, 0, BASE_SCALE, tx, ty);\n } else {\n ctx.setTransform(a, b, c, d, tx, ty);\n }\n for (const cmd of group.commands) {\n replayCommand(ctx, cmd);\n }\n }\n ctx.setTransform(base);\n },\n });\n konvaLayer.add(this.konvaShape);\n }\n\n addNode(node: RecordingGroupNode) {\n this.groups.push(node);\n this.ensureShape();\n }\n\n destroyChildren() {\n this.groups.length = 0;\n }\n\n batchDraw() {\n this.konvaLayer.batchDraw();\n }\n\n private ensureShape() {\n if (!this.konvaShape.getParent()) {\n this.konvaLayer.add(this.konvaShape);\n }\n }\n}\n\n// --- MaterializingLayerNode ---\n\n/**\n * Layer that materializes each added {@link RecordingGroupNode} as its own\n * `Konva.Group`. Used for layers where the renderer keeps a reference to\n * each node (overlay highlights, position marker) so it can destroy them\n * individually.\n */\nexport class MaterializingLayerNode {\n constructor(private readonly konvaLayer: Konva.Layer) {}\n\n addNode(node: RecordingGroupNode) {\n this.konvaLayer.add(node.materialize());\n }\n\n destroyChildren() {\n this.konvaLayer.destroyChildren();\n }\n\n batchDraw() {\n this.konvaLayer.batchDraw();\n }\n}\n","/**\n * SceneIR — pure data shapes that {@link ScenePipeline} produces.\n *\n * Shapes carry world-space coordinates and engine-agnostic paint information.\n * They are consumed by:\n * - {@link CullingManager} (visibility queries against a camera viewport)\n * - {@link HitTester} (point→shape lookup)\n * - {@link buildDrawCommands} (translation to engine {@link DrawCommand}s)\n *\n * Shapes know nothing about Konva, SVG, or Canvas2D.\n */\n\n/** Logical layer for stacking. Mapped to engine-specific layers by renderers. */\nexport type LayerId =\n | \"grid\"\n | \"link\"\n | \"room\"\n | \"position\"\n | \"overlay\"\n | \"top\";\n\n/** One gradient stop. `offset` is in 0..1; `color` is any CSS colour string. */\nexport interface GradientStop {\n offset: number;\n color: string;\n}\n\n/**\n * Linear gradient between two points in the shape's LOCAL coordinate space\n * (same frame as shape.x/y). Stop colours are sampled along the line\n * (x0,y0) → (x1,y1).\n */\nexport interface LinearGradient {\n type: \"linear\";\n x0: number;\n y0: number;\n x1: number;\n y1: number;\n stops: GradientStop[];\n}\n\n/**\n * Radial gradient in the shape's LOCAL coordinate space. The end circle is\n * (cx, cy, r); the optional start circle is (fx, fy, fr) — defaults to a\n * zero-radius circle at (cx, cy) when omitted.\n */\nexport interface RadialGradient {\n type: \"radial\";\n cx: number;\n cy: number;\n r: number;\n fx?: number;\n fy?: number;\n fr?: number;\n stops: GradientStop[];\n}\n\n/** A fill is either a flat colour string or a gradient. */\nexport type FillStyle = string | LinearGradient | RadialGradient;\n\n/** Engine-agnostic paint description. */\nexport interface Paint {\n /**\n * Colour string (`#rrggbb`, `rgb(...)`, named colour) or a gradient.\n * Gradient coords are in the same local frame as the shape's geometry.\n */\n fill?: FillStyle;\n stroke?: string;\n strokeWidth?: number;\n dash?: number[];\n dashEnabled?: boolean;\n /** 0..1 multiplier on fill+stroke. */\n alpha?: number;\n}\n\n/** True when `fill` is a gradient object rather than a colour string. */\nexport function isGradientFill(fill: FillStyle | undefined): fill is LinearGradient | RadialGradient {\n return typeof fill === \"object\" && fill !== null;\n}\n\n/**\n * Apply a camera-style affine (translate by world origin, scale uniformly,\n * translate by render offset) to a fill. Strings pass through unchanged.\n * Gradient stops are colour-only and never transformed.\n */\nexport function transformFill(\n fill: FillStyle | undefined,\n worldX: number,\n worldY: number,\n scale: number,\n offsetX: number,\n offsetY: number,\n): FillStyle | undefined {\n if (!isGradientFill(fill)) return fill;\n if (fill.type === \"linear\") {\n return {\n type: \"linear\",\n x0: (worldX + fill.x0) * scale + offsetX,\n y0: (worldY + fill.y0) * scale + offsetY,\n x1: (worldX + fill.x1) * scale + offsetX,\n y1: (worldY + fill.y1) * scale + offsetY,\n stops: fill.stops,\n };\n }\n return {\n type: \"radial\",\n cx: (worldX + fill.cx) * scale + offsetX,\n cy: (worldY + fill.cy) * scale + offsetY,\n r: fill.r * scale,\n fx: fill.fx !== undefined ? (worldX + fill.fx) * scale + offsetX : undefined,\n fy: fill.fy !== undefined ? (worldY + fill.fy) * scale + offsetY : undefined,\n fr: fill.fr !== undefined ? fill.fr * scale : undefined,\n stops: fill.stops,\n };\n}\n\n/** Hit-test annotation. Set on shapes that should be pickable. */\nexport interface HitInfo {\n /** What the shape represents at the model layer. */\n kind: \"room\" | \"exit\" | \"specialExit\" | \"stub\" | \"label\" | \"areaExit\" | string;\n /** Identifier of the owning model entity (room id, exit id, …). */\n id?: number | string;\n /** Free-form payload returned to callers from {@link HitTester}. */\n payload?: unknown;\n /**\n * Multiplier on the current `roomSize` for the maximum pick distance.\n * Smaller = stricter (used for thin geometry like exits and stubs).\n * Defaults to a per-kind value when omitted (rooms 1.0, exits 0.35, stubs 0.3, …).\n */\n margin?: number;\n /**\n * Tiebreaker when multiple hits are within range. Higher = on top.\n * Defaults to a per-kind value when omitted (rooms over exits over stubs, …).\n */\n priority?: number;\n}\n\n/** Common fields on every shape. */\nexport interface ShapeBase {\n /** Logical layer; defaults to \"room\" when omitted. */\n layer?: LayerId;\n /** Set on shapes that participate in hit testing. */\n hit?: HitInfo;\n /**\n * If true, the shape renders at a fixed pixel size regardless of camera\n * zoom. Mirrors the legacy `GroupNode.noScaling` flag.\n */\n noScale?: boolean;\n}\n\nexport interface RectShape extends ShapeBase {\n type: \"rect\";\n x: number;\n y: number;\n width: number;\n height: number;\n cornerRadius?: number;\n paint: Paint;\n}\n\nexport interface CircleShape extends ShapeBase {\n type: \"circle\";\n cx: number;\n cy: number;\n radius: number;\n paint: Paint;\n}\n\nexport interface LineShape extends ShapeBase {\n type: \"line\";\n /** Flat list of [x0, y0, x1, y1, …]. */\n points: number[];\n paint: Paint;\n lineCap?: \"butt\" | \"round\" | \"square\";\n lineJoin?: \"miter\" | \"round\" | \"bevel\";\n /**\n * Marks lines that are infrastructure (grid). Styles use this to skip\n * decoration on grid (cheaper rendering) while still recolouring it.\n */\n grid?: boolean;\n}\n\nexport interface PolygonShape extends ShapeBase {\n type: \"polygon\";\n /** Flat list of [x0, y0, x1, y1, …]. Closed implicitly. */\n vertices: number[];\n paint: Paint;\n}\n\nexport interface TextShape extends ShapeBase {\n type: \"text\";\n x: number;\n y: number;\n text: string;\n fontSize: number;\n fontFamily?: string;\n fontStyle?: string;\n fill?: string;\n stroke?: string;\n strokeWidth?: number;\n align?: \"left\" | \"center\" | \"right\";\n verticalAlign?: \"top\" | \"middle\" | \"bottom\";\n width?: number;\n height?: number;\n /** Fraction of fontSize from top to baseline (SVG positioning). */\n baselineRatio?: number;\n /** Konva offsetY ratio applied to centre text vertically. */\n konvaCorrectionRatio?: number;\n /** Optional 2D affine transform [a, b, c, d, e, f]. */\n transform?: [number, number, number, number, number, number];\n}\n\nexport interface ImageShape extends ShapeBase {\n type: \"image\";\n x: number;\n y: number;\n width: number;\n height: number;\n src: string;\n /** Optional 2D affine transform [a, b, c, d, e, f]. */\n transform?: [number, number, number, number, number, number];\n}\n\n/**\n * Positional container. Children render relative to {@link x}, {@link y}.\n * Groups can nest; the final world position of a child is the cumulative\n * sum of group origins plus the child's own offset.\n */\nexport interface GroupShape extends ShapeBase {\n type: \"group\";\n x: number;\n y: number;\n children: Shape[];\n}\n\nexport type Shape =\n | RectShape\n | CircleShape\n | LineShape\n | PolygonShape\n | TextShape\n | ImageShape\n | GroupShape;\n\n/** Axis-aligned bounding box in world space. */\nexport interface Bbox {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n}\n\n/**\n * Full output of one {@link ScenePipeline} build pass. Consumers slice this by\n * layer and feed the relevant subset into culling, hit-testing, and rendering.\n */\nexport interface SceneIR {\n /** All shapes for the current scene, world-space. */\n shapes: Shape[];\n /** Optional pre-computed bbox for whole-scene framing. */\n bounds?: Bbox;\n}\n","/**\n * Walk a {@link GroupShape} into a {@link RecordingGroupNode} — the unit the\n * Konva layer infrastructure consumes for replay. Same shape → command map\n * the legacy DrawingBackend → CanvasBackend pair did, but in one step with\n * no backend interface.\n *\n * Returns the node without attaching it to a layer; the caller decides which\n * layer to push it into (overlay layer materializes one Konva.Group per\n * recording, scene/grid/topLabel batch many recordings into one shared\n * sceneFunc).\n */\n\nimport Konva from \"konva\";\nimport {RecordingGroupNode} from \"./RecordingLayer\";\nimport {transformFill, type GroupShape, type Shape} from \"../scene/Shape\";\n\nexport function shapeToRecording(group: GroupShape): RecordingGroupNode {\n const node = new RecordingGroupNode(group.x, group.y);\n if (group.noScale) node.noScaling = true;\n addChildren(node, group.children, 0, 0);\n return node;\n}\n\nfunction addChildren(\n node: RecordingGroupNode,\n children: Shape[],\n ox: number,\n oy: number,\n): void {\n for (const child of children) {\n addShape(node, child, ox, oy);\n }\n}\n\nfunction addShape(\n node: RecordingGroupNode,\n shape: Shape,\n ox: number,\n oy: number,\n): void {\n switch (shape.type) {\n case \"rect\":\n node.commands.push({\n type: \"rect\",\n x: shape.x + ox,\n y: shape.y + oy,\n w: shape.width,\n h: shape.height,\n fill: transformFill(shape.paint.fill, ox, oy, 1, 0, 0),\n stroke: shape.paint.stroke,\n sw: shape.paint.strokeWidth ?? 0,\n cr: shape.cornerRadius ?? 0,\n dash: (shape.paint.dashEnabled !== false && shape.paint.dash) ? shape.paint.dash : undefined,\n });\n return;\n case \"circle\":\n node.commands.push({\n type: \"circle\",\n cx: shape.cx + ox,\n cy: shape.cy + oy,\n r: shape.radius,\n fill: transformFill(shape.paint.fill, ox, oy, 1, 0, 0),\n stroke: shape.paint.stroke,\n sw: shape.paint.strokeWidth ?? 0,\n dash: (shape.paint.dashEnabled !== false && shape.paint.dash) ? shape.paint.dash : undefined,\n });\n return;\n case \"line\": {\n const points = (ox === 0 && oy === 0)\n ? shape.points\n : translatePoints(shape.points, ox, oy);\n node.commands.push({\n type: \"line\",\n points,\n stroke: shape.paint.stroke,\n sw: shape.paint.strokeWidth ?? 0,\n dash: shape.paint.dash,\n lineCap: shape.lineCap,\n lineJoin: shape.lineJoin,\n alpha: shape.paint.alpha,\n });\n return;\n }\n case \"polygon\":\n node.commands.push({\n type: \"polygon\",\n vertices: (ox === 0 && oy === 0)\n ? shape.vertices\n : translatePoints(shape.vertices, ox, oy),\n fill: transformFill(shape.paint.fill, ox, oy, 1, 0, 0),\n stroke: shape.paint.stroke,\n sw: shape.paint.strokeWidth ?? 0,\n });\n return;\n case \"text\":\n node.commands.push({\n type: \"text\",\n x: shape.x + ox,\n y: shape.y + oy,\n text: shape.text,\n fontSize: shape.fontSize,\n fontFamily: shape.fontFamily ?? \"sans-serif\",\n fontStyle: shape.fontStyle ?? \"normal\",\n fill: shape.fill ?? \"black\",\n stroke: shape.stroke,\n sw: shape.strokeWidth ?? 0,\n align: shape.align ?? \"left\",\n vAlign: shape.verticalAlign ?? \"top\",\n w: shape.width ?? 0,\n h: shape.height ?? 0,\n baselineRatio: shape.baselineRatio,\n transform: shape.transform,\n });\n return;\n case \"image\": {\n const image = createImageElement(shape.src);\n node.commands.push({\n type: \"image\",\n x: shape.x + ox,\n y: shape.y + oy,\n w: shape.width,\n h: shape.height,\n image,\n transform: shape.transform,\n });\n return;\n }\n case \"group\":\n addChildren(node, shape.children, ox + shape.x, oy + shape.y);\n return;\n }\n}\n\nfunction translatePoints(points: number[], ox: number, oy: number): number[] {\n const out = new Array<number>(points.length);\n for (let i = 0; i < points.length; i += 2) {\n out[i] = points[i] + ox;\n out[i + 1] = points[i + 1] + oy;\n }\n return out;\n}\n\nfunction createImageElement(src: string): HTMLImageElement | any {\n const image = typeof Konva !== \"undefined\"\n ? Konva.Util.createImageElement()\n : (typeof Image !== \"undefined\" ? new Image() : null);\n if (image) image.src = src;\n return image;\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 strokeColor: string;\n strokeAlpha: number;\n strokeWidth: number;\n fillColor: string;\n fillAlpha: number;\n dash?: number[];\n dashEnabled: boolean;\n};\n\nexport function computeHighlight(room: MapData.Room, color: string, settings: Settings): HighlightData {\n const hl = settings.highlight;\n const rs = settings.roomSize;\n const factor = hl.sizeFactor;\n const useRoomShape = hl.matchRoomShape && settings.roomShape !== \"circle\";\n return {\n shape: useRoomShape ? 'rect' : 'circle',\n cx: room.x,\n cy: room.y,\n size: rs / 2 * factor,\n cornerRadius: useRoomShape && settings.roomShape === \"roundedRectangle\" ? rs * factor * 0.2 : 0,\n strokeColor: color,\n strokeAlpha: hl.strokeAlpha,\n strokeWidth: hl.strokeWidth,\n fillColor: color,\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 */\nexport function highlightToShape(data: HighlightData): 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 // 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/** 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 * - `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/** 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\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 */\n build(shapes: Shape[], roomSize: number, coordTransform?: CoordTransform): void {\n this.clear();\n this.roomSize = roomSize;\n this.bucketSize = Math.max(roomSize * 10, 5);\n this.transform = coordTransform ?? identity;\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.transform, 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 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/** 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}\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 Konva from \"konva\";\nimport type {IArea} from \"../reader/Area\";\nimport type {IPlane} from \"../reader/Plane\";\nimport type {RendererEventMap, ViewportBounds} from \"../types/Settings\";\nimport type {DrawnExitEntry, DrawnSpecialExitEntry, DrawnStubEntry} from \"../ScenePipeline\";\nimport type {MapState} from \"../MapState\";\nimport {Camera} from \"../camera/Camera\";\nimport {CullingManager} from \"../CullingManager\";\nimport {SceneManager} from \"./SceneManager\";\nimport {InteractionHandler} from \"../InteractionHandler\";\nimport {TypedEventEmitter} from \"../TypedEventEmitter\";\nimport {\n DrawCommandLayerNode,\n MaterializingLayerNode,\n RecordingGroupNode,\n RecordingLayerNode,\n type DrawEntry,\n} from \"../render/RecordingLayer\";\nimport {shapeToRecording} from \"../render/shapeToRecording\";\nimport type {InteractiveBackend} from \"./MapRenderer\";\nimport type {CoordFn} from \"../coord/CoordFn\";\nimport {IDENTITY_TRANSFORM} from \"../coord/CoordFn\";\nimport {computeHighlight, computePositionMarker, computePathOverlay} from \"../scene/OverlayStyle\";\nimport {computeStubs} from \"../scene/StubStyle\";\nimport {computeSpecialExits} from \"../scene/SpecialExitStyle\";\nimport {layoutRoom} from \"../scene/elements/RoomLayout\";\nimport {layoutInnerExits} from \"../scene/elements/ExitLayout\";\nimport {layoutGrid} from \"../scene/elements/GridLayout\";\nimport {specialExitToShape} from \"../scene/elements/SpecialExitLayout\";\nimport {stubToShape} from \"../scene/elements/StubLayout\";\nimport {\n highlightToShape, positionMarkerToShape, pathToShapes,\n} from \"../scene/elements/OverlayLayout\";\nimport type {LiveEffect} from \"../overlay/LiveEffect\";\nimport type {SceneOverlay, SceneOverlayContext} from \"../overlay/SceneOverlay\";\nimport type {ExportCanvas} from \"../export/Exporter\";\nimport {HitTester} from \"../hit/HitTester\";\nimport type {GroupShape, Shape} from \"../scene/Shape\";\nimport type {Style, StyleContext} from \"../style/Style\";\nimport {identityStyle} from \"../style/Style\";\nimport {applyStyleToShapes} from \"../style/applyStyle\";\n\nconst currentRoomColor = 'rgb(120, 72, 0)';\n\n/**\n * Konva rendering engine. Owns the full rendering pipeline:\n * stage, layers, scene builder, culling, overlays.\n *\n * Camera is the source of truth for transform state.\n * This backend subscribes to the camera's `change` event and applies state to the Konva stage.\n *\n * Works identically in both modes:\n * - DOM container → stage attached to DOM, mouse/touch → camera\n * - No container → headless stage, same camera/culling, no input\n */\nexport class KonvaRenderBackend implements InteractiveBackend {\n readonly stage: Konva.Stage;\n readonly gridLayer: Konva.Layer;\n readonly linkLayer: Konva.Layer;\n readonly roomLayer: Konva.Layer;\n readonly topLabelLayer: Konva.Layer;\n readonly overlayLayer: Konva.Layer;\n readonly positionLayer: Konva.Layer;\n\n readonly camera: Camera;\n readonly culling: CullingManager;\n readonly events: TypedEventEmitter<RendererEventMap>;\n\n private readonly state: MapState;\n private readonly container?: HTMLDivElement;\n private readonly positionLayerNode: MaterializingLayerNode;\n private readonly overlayLayerNode: MaterializingLayerNode;\n private gridLayerNode!: RecordingLayerNode;\n private topLabelLayerNode!: RecordingLayerNode;\n /** Snapped grid bounds last rendered onto gridLayerNode. */\n private gridCachedBounds: {left: number; right: number; top: number; bottom: number} | null = null;\n private sceneManager: SceneManager;\n private currentStyle: Style = identityStyle;\n\n readonly hitTester: HitTester = new HitTester();\n private lastHitShapes: Shape[] = [];\n\n // Shape → DrawEntry map rebuilt on each buildScene; used by applyClipping\n // to toggle DrawEntry.visible without knowing about CullEntry or Konva internals.\n private shapeToDrawEntry: Map<Shape, DrawEntry> = new Map();\n private sceneNode!: DrawCommandLayerNode;\n /** Lookup from a pipeline-emitted shape to its recording node; rebuilt per buildScene. */\n private shapeToGroup: Map<Shape, RecordingGroupNode> = new Map();\n\n private positionMarker?: RecordingGroupNode;\n private highlightShapes: Map<number, RecordingGroupNode> = new Map();\n private pathShapes: RecordingGroupNode[] = [];\n private currentRoomOverlay: RecordingGroupNode[] = [];\n private interactionHandler?: InteractionHandler;\n private origSetSize?: (w: number, h: number) => void;\n private cameraChangeHandler?: () => void;\n private destroyed = false;\n private _coordinateTransform: CoordFn = IDENTITY_TRANSFORM;\n private coordinateInverse: CoordFn = IDENTITY_TRANSFORM;\n\n get coordinateTransform(): CoordFn {\n return this._coordinateTransform;\n }\n private liveEffects: Map<string, LiveEffect> = new Map();\n private sceneOverlays: Map<string, SceneOverlay> = new Map();\n private sceneOverlayNodes: Map<string, RecordingGroupNode[]> = new Map();\n private viewportSubscribers: Set<() => void> = new Set();\n\n constructor(state: MapState, container?: HTMLDivElement) {\n this.state = state;\n this.container = container;\n\n if (container) {\n this.stage = new Konva.Stage({\n container,\n width: container.clientWidth,\n height: container.clientHeight,\n draggable: false,\n });\n container.style.backgroundColor = state.settings.backgroundColor;\n this.camera = new Camera(container.clientWidth, container.clientHeight);\n } else {\n this.stage = new Konva.Stage({width: 1, height: 1});\n this.camera = new Camera(1, 1);\n }\n\n this.gridLayer = new Konva.Layer({listening: false});\n this.stage.add(this.gridLayer);\n // linkLayer and roomLayer share one physical Konva.Layer to stay under\n // Konva's recommended layer count. Z-order between link exits and rooms\n // is preserved by insertion order within the shared RecordingLayerNode.\n const sceneLayer = new Konva.Layer({listening: false});\n this.linkLayer = sceneLayer;\n this.roomLayer = sceneLayer;\n this.stage.add(sceneLayer);\n this.positionLayer = new Konva.Layer({listening: false});\n this.stage.add(this.positionLayer);\n this.overlayLayer = new Konva.Layer({listening: false});\n this.stage.add(this.overlayLayer);\n this.topLabelLayer = new Konva.Layer({listening: false});\n this.stage.add(this.topLabelLayer);\n\n this.positionLayerNode = new MaterializingLayerNode(this.positionLayer);\n this.overlayLayerNode = new MaterializingLayerNode(this.overlayLayer);\n\n this.sceneNode = new DrawCommandLayerNode(sceneLayer);\n this.gridLayerNode = new RecordingLayerNode(this.gridLayer);\n this.topLabelLayerNode = new RecordingLayerNode(this.topLabelLayer);\n this.sceneManager = new SceneManager(this.camera, state.settings, state.mapReader);\n\n this.events = new TypedEventEmitter<RendererEventMap>(container);\n\n // linkLayer and roomLayer share sceneNode; one batchDraw covers both.\n this.culling = new CullingManager(state.settings, () => this.applyClipping());\n\n // Camera drives the stage\n this.cameraChangeHandler = () => this.applyViewportToStage();\n this.camera.on('change', this.cameraChangeHandler);\n\n if (container) {\n // Sync stage size when camera resizes\n this.origSetSize = this.camera.setSize.bind(this.camera);\n const origSetSize = this.origSetSize;\n this.camera.setSize = (w: number, h: number) => {\n origSetSize(w, h);\n this.stage.width(w);\n this.stage.height(h);\n };\n\n this.interactionHandler = new InteractionHandler(container, this.camera, state, {\n clientToMapPoint: (cx, cy) => this.camera.clientToMapPoint(cx, cy, container.getBoundingClientRect()),\n pickAtPoint: (x, y) => this.hitTester.pick(x, y),\n }, this.events);\n }\n\n this.applyStyleTransforms();\n this.subscribeToState(state);\n }\n\n setStyle(style: Style) {\n this.currentStyle = style;\n this.sceneNode = new DrawCommandLayerNode(this.linkLayer);\n this.gridLayerNode = new RecordingLayerNode(this.gridLayer);\n this.topLabelLayerNode = new RecordingLayerNode(this.topLabelLayer);\n this.gridCachedBounds = null;\n this.sceneManager.resetPipeline(this.state.mapReader);\n\n // Drop scene state pinned to the now-destroyed sceneNode/layers.\n this.shapeToDrawEntry = new Map();\n this.shapeToGroup.clear();\n this.lastHitShapes = [];\n this.hitTester.clear();\n\n this.applyStyleTransforms();\n this.refresh();\n }\n\n /** Pull forward/inverse coord transforms from the active shape Style. */\n private applyStyleTransforms() {\n // Capture the style pointer locally: the arrows below outlive setStyle\n // calls (held by TerrainOverlay via updateViewport, by `oldInverse`\n // below, etc.), so they must not dereference `this.currentStyle` at\n // invocation time — that would resolve against whatever style was\n // installed *later*, which may not implement worldToScene/sceneToWorld.\n const style = this.currentStyle;\n const forward: CoordFn = style.worldToScene\n ? (x, y) => style.worldToScene!(x, y)\n : IDENTITY_TRANSFORM;\n const newInverse: CoordFn = style.sceneToWorld\n ? (x, y) => style.sceneToWorld!(x, y)\n : IDENTITY_TRANSFORM;\n const oldInverse = this.coordinateInverse;\n\n this._coordinateTransform = forward;\n this.coordinateInverse = newInverse;\n this.culling.setCoordinateTransform(forward);\n this.gridCachedBounds = null;\n if (this.lastHitShapes.length > 0) {\n this.hitTester.build(this.lastHitShapes, this.state.settings.roomSize, forward);\n }\n\n // Reposition camera so the same map point stays at screen center\n const scale = this.camera.getScale();\n const screenCX = this.camera.width / 2;\n const screenCY = this.camera.height / 2;\n const oldRX = (screenCX - this.camera.position.x) / scale;\n const oldRY = (screenCY - this.camera.position.y) / scale;\n const map = oldInverse(oldRX, oldRY);\n const nr = forward(map.x, map.y);\n\n this.camera.position = {\n x: screenCX - nr.x * scale,\n y: screenCY - nr.y * scale,\n };\n this.applyViewportToStage();\n }\n\n private styleContext(): StyleContext {\n return {\n scale: this.camera.getScale(),\n roomSize: this.state.settings.roomSize,\n };\n }\n\n private mapPoint(x: number, y: number): { x: number; y: number } {\n return this._coordinateTransform(x, y);\n }\n\n get exitRenderer() {\n return this.sceneManager.exitRenderer;\n }\n\n getDrawnExits(): readonly DrawnExitEntry[] {\n return this.sceneManager.drawnExits;\n }\n\n getDrawnSpecialExits(): readonly DrawnSpecialExitEntry[] {\n return this.sceneManager.drawnSpecialExits;\n }\n\n getDrawnStubs(): readonly DrawnStubEntry[] {\n return this.sceneManager.drawnStubs;\n }\n\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n\n // Remove all MapState event subscriptions\n this.state.events.removeAllListeners();\n\n // Destroy interaction handler (removes DOM listeners)\n this.interactionHandler?.destroy();\n\n // Stop overlay plugins\n for (const plugin of this.liveEffects.values()) plugin.destroy();\n this.liveEffects.clear();\n\n // Detach scene overlays so they can unsubscribe from events\n for (const overlay of this.sceneOverlays.values()) overlay.detach?.();\n this.sceneOverlays.clear();\n for (const nodes of this.sceneOverlayNodes.values()) {\n for (const node of nodes) node.destroy();\n }\n this.sceneOverlayNodes.clear();\n this.viewportSubscribers.clear();\n\n // Cancel any running camera animation\n this.camera.cancelAnimation();\n\n // Restore monkey-patched setSize\n if (this.origSetSize) {\n this.camera.setSize = this.origSetSize;\n }\n\n // Disconnect camera from stage\n if (this.cameraChangeHandler) {\n this.camera.off('change', this.cameraChangeHandler);\n this.cameraChangeHandler = undefined;\n }\n\n // Destroy all Konva nodes and the stage\n this.clearOverlayShapes();\n this.clearCurrentRoomOverlay();\n if (this.positionMarker) {\n this.positionMarker.destroy();\n this.positionMarker = undefined;\n }\n this.stage.destroy();\n\n // Clear renderer events\n this.events.removeAllListeners();\n }\n\n updateBackground() {\n if (this.container) {\n this.container.style.backgroundColor = this.state.settings.backgroundColor;\n }\n }\n\n exportCanvas(options?: { pixelRatio?: number }): ExportCanvas | undefined {\n if (this.state.currentArea === undefined || this.state.currentZIndex === undefined) return;\n const stageCanvas = this.stage.toCanvas({ pixelRatio: options?.pixelRatio ?? 1 });\n const composite = document.createElement('canvas');\n composite.width = stageCanvas.width;\n composite.height = stageCanvas.height;\n const ctx = composite.getContext('2d')!;\n ctx.fillStyle = this.state.settings.backgroundColor;\n ctx.fillRect(0, 0, composite.width, composite.height);\n ctx.drawImage(stageCanvas, 0, 0);\n return composite;\n }\n\n // --- Camera → Stage (one-way, called from the camera's change event) ---\n\n private applyViewportToStage() {\n const scale = this.camera.getScale();\n this.stage.scale({x: scale, y: scale});\n this.stage.position(this.camera.position);\n this.stage.batchDraw();\n const vpBounds = this.camera.getViewportBounds();\n this.refreshGrid(vpBounds);\n this.culling.scheduleCulling();\n this.events.emit('pan', vpBounds);\n for (const cb of this.viewportSubscribers) cb();\n for (const plugin of this.liveEffects.values()) {\n plugin.updateViewport(vpBounds, scale, this.coordinateTransform);\n }\n }\n\n // --- State event handlers ---\n\n refresh() {\n const {currentAreaInstance, currentZIndex, positionRoomId} = this.state;\n if (!currentAreaInstance || currentZIndex === undefined) return;\n const plane = currentAreaInstance.getPlane(currentZIndex);\n if (!plane) {\n this.shapeToDrawEntry = new Map();\n this.sceneManager.reset();\n this.hitTester.clear();\n this.lastHitShapes = [];\n this.gridLayer.destroyChildren();\n this.linkLayer.destroyChildren();\n this.positionLayer.destroyChildren();\n this.positionMarker = undefined;\n this.clearOverlayShapes();\n this.currentRoomOverlay = [];\n this.stage.batchDraw();\n return;\n }\n this.updateBackground();\n this.buildScene(currentAreaInstance, plane, currentZIndex);\n this.onSceneBuilt();\n this.syncHighlights();\n this.syncPaths();\n if (positionRoomId !== undefined) {\n this.onPositionChanged(positionRoomId, false, false);\n }\n for (const [id, overlay] of this.sceneOverlays) {\n this.renderSceneOverlay(id, overlay);\n }\n }\n\n addLiveEffect(id: string, effect: LiveEffect) {\n this.removeLiveEffect(id);\n effect.attach(this.overlayLayer);\n this.liveEffects.set(id, effect);\n effect.updateViewport(this.camera.getViewportBounds(), this.camera.getScale(), this.coordinateTransform);\n }\n\n removeLiveEffect(id: string) {\n const existing = this.liveEffects.get(id);\n if (existing) {\n existing.destroy();\n this.liveEffects.delete(id);\n }\n }\n\n addSceneOverlay(id: string, overlay: SceneOverlay) {\n const existing = this.sceneOverlays.get(id);\n if (existing) {\n existing.detach?.();\n this.clearSceneOverlayNodes(id);\n }\n this.sceneOverlays.set(id, overlay);\n overlay.attach?.(this.createOverlayContext(id, overlay));\n this.renderSceneOverlay(id, overlay);\n }\n\n removeSceneOverlay(id: string) {\n const overlay = this.sceneOverlays.get(id);\n if (!overlay) return;\n overlay.detach?.();\n this.sceneOverlays.delete(id);\n this.clearSceneOverlayNodes(id);\n this.overlayLayer.batchDraw();\n }\n\n /** Iterable of scene overlays — used by exporters to apply them over static outputs. */\n getSceneOverlays(): Iterable<SceneOverlay> {\n return this.sceneOverlays.values();\n }\n\n private createOverlayContext(id: string, overlay: SceneOverlay): SceneOverlayContext {\n return {\n state: this.state,\n onViewportChange: (cb) => {\n this.viewportSubscribers.add(cb);\n return () => this.viewportSubscribers.delete(cb);\n },\n invalidate: () => {\n // Skip if the overlay was removed/replaced since attach.\n if (this.sceneOverlays.get(id) !== overlay) return;\n this.renderSceneOverlay(id, overlay);\n },\n };\n }\n\n private renderSceneOverlay(id: string, overlay: SceneOverlay) {\n this.clearSceneOverlayNodes(id);\n const bounds = this.camera.getViewportBounds();\n const out = overlay.render(this.state, bounds);\n if (out) {\n const shapes = Array.isArray(out) ? out : [out];\n const stored: RecordingGroupNode[] = [];\n for (const shape of shapes) {\n const node = this.addStyledShape(shape, this.overlayLayerNode);\n if (node) stored.push(node);\n }\n this.sceneOverlayNodes.set(id, stored);\n }\n this.overlayLayer.batchDraw();\n }\n\n private clearSceneOverlayNodes(id: string) {\n const nodes = this.sceneOverlayNodes.get(id);\n if (!nodes) return;\n for (const node of nodes) node.destroy();\n this.sceneOverlayNodes.delete(id);\n }\n\n private subscribeToState(state: MapState) {\n state.events.on('area', () => {\n this.refresh();\n });\n\n state.events.on('position', ({roomId, center, areaChanged}) => {\n this.onPositionChanged(roomId, center, areaChanged);\n });\n\n state.events.on('center', ({roomId, instant}) => {\n const room = state.mapReader.getRoom(roomId);\n if (room) {\n const p = this.mapPoint(room.x, room.y);\n this.camera.panToMapPointAnimated(p.x, p.y,\n instant || this.state.settings.instantMapMove);\n }\n });\n\n state.events.on('highlight', ({roomId, color}) => {\n this.syncHighlight(roomId, color);\n });\n\n state.events.on('path', () => {\n this.syncPaths();\n });\n\n state.events.on('clear', () => {\n this.syncHighlights();\n });\n\n state.events.on('lens', () => {\n this.refresh();\n });\n }\n\n // --- Scene lifecycle ---\n\n private buildScene(area: IArea, plane: IPlane, zIndex: number): void {\n this.positionLayer.destroyChildren();\n this.positionMarker = undefined;\n this.clearOverlayShapes();\n this.currentRoomOverlay = [];\n\n // Reset the scene-driving layer nodes; sceneManager.rebuild only\n // produces shapes now, so this renderer is responsible for walking\n // them onto the Konva layers as RecordingGroupNodes.\n this.sceneNode.destroyChildren();\n this.gridLayerNode.destroyChildren();\n this.gridCachedBounds = null;\n this.topLabelLayerNode.destroyChildren();\n\n const result = this.sceneManager.rebuild(area, plane, zIndex, this.state.lens);\n\n // Track ORIGINAL shape (pre-style) → recording node so onSceneBuilt\n // can find each room/exit's DrawEntry inside `sceneNode` for culling.\n // When style.transform fans a shape into multiple, we record the first\n // resulting node against the original — cull toggles that one.\n this.shapeToGroup = new Map();\n\n // Seed the grid for the first paint; subsequent updates are driven by\n // refreshGrid() on every camera change.\n this.refreshGrid(this.camera.getViewportBounds());\n\n for (const shape of result.sceneShapes.link) {\n const node = this.addStyledShape(shape, this.sceneNode);\n if (node) this.shapeToGroup.set(shape, node);\n }\n for (const shape of result.sceneShapes.room) {\n const node = this.addStyledShape(shape, this.sceneNode);\n if (node) this.shapeToGroup.set(shape, node);\n }\n for (const shape of result.sceneShapes.topLabel) {\n this.addStyledShape(shape, this.topLabelLayerNode);\n }\n }\n\n /**\n * Run the active {@link Style} over `shape`, walk the (possibly multi-shape)\n * result into a single {@link RecordingGroupNode}, and add it to `layerNode`.\n *\n * Styles like Isometric expand one input shape into several (cube top + side\n * faces + edges); wrapping the whole expansion into one node keeps a 1:1\n * relationship between input shape and tracked handle, so callers that cache\n * the return value (position marker, scene overlays, highlights, current-room\n * overlay, paths) can destroy the entire expansion through that one handle.\n */\n private addStyledShape(\n shape: Shape,\n layerNode: {addNode(node: RecordingGroupNode): void},\n ): RecordingGroupNode | undefined {\n const styled = this.currentStyle === identityStyle\n ? [shape]\n : applyStyleToShapes([shape], this.currentStyle, this.styleContext());\n if (styled.length === 0) return undefined;\n\n let groupShape: GroupShape;\n if (styled.length === 1 && styled[0].type === \"group\") {\n groupShape = styled[0];\n } else {\n groupShape = {\n type: \"group\",\n x: 0, y: 0,\n children: styled,\n layer: shape.layer,\n noScale: shape.noScale,\n };\n }\n const node = shapeToRecording(groupShape);\n layerNode.addNode(node);\n return node;\n }\n\n /**\n * Recompute and re-publish the grid lines for the given viewport.\n * Cached against snapped grid bounds so per-frame redraws are cheap when\n * the viewport hasn't crossed a grid step.\n */\n private refreshGrid(bounds: ViewportBounds): void {\n const settings = this.state.settings;\n if (!settings.gridEnabled) {\n if (this.gridCachedBounds !== null) {\n this.gridLayerNode.destroyChildren();\n this.gridLayerNode.batchDraw();\n this.gridCachedBounds = null;\n }\n return;\n }\n\n const inv = this.coordinateInverse;\n const c1 = inv(bounds.minX, bounds.minY);\n const c2 = inv(bounds.maxX, bounds.minY);\n const c3 = inv(bounds.maxX, bounds.maxY);\n const c4 = inv(bounds.minX, bounds.maxY);\n const cartMinX = Math.min(c1.x, c2.x, c3.x, c4.x);\n const cartMaxX = Math.max(c1.x, c2.x, c3.x, c4.x);\n const cartMinY = Math.min(c1.y, c2.y, c3.y, c4.y);\n const cartMaxY = Math.max(c1.y, c2.y, c3.y, c4.y);\n const buffer = settings.gridSize * 2;\n const left = Math.floor((cartMinX - buffer) / settings.gridSize) * settings.gridSize;\n const right = Math.ceil((cartMaxX + buffer) / settings.gridSize) * settings.gridSize;\n const top = Math.floor((cartMinY - buffer) / settings.gridSize) * settings.gridSize;\n const bottom = Math.ceil((cartMaxY + buffer) / settings.gridSize) * settings.gridSize;\n\n const cached = this.gridCachedBounds;\n if (cached && cached.left === left && cached.right === right && cached.top === top && cached.bottom === bottom) {\n return;\n }\n\n this.gridLayerNode.destroyChildren();\n const lines = layoutGrid(bounds, settings, {inverseTransform: this.coordinateInverse});\n for (const line of lines) {\n this.addStyledShape(line, this.gridLayerNode);\n }\n this.gridCachedBounds = {left, right, top, bottom};\n this.gridLayerNode.batchDraw();\n }\n\n private onSceneBuilt() {\n this.lastHitShapes = this.sceneManager.hitShapes as Shape[];\n this.hitTester.build(this.lastHitShapes, this.state.settings.roomSize, this._coordinateTransform);\n\n const scale = this.camera.getScale();\n this.stage.scale({x: scale, y: scale});\n\n // Build shape → DrawEntry map for all scene shapes (link + room layers).\n // TopLabel shapes live on a separate RecordingLayerNode and are always visible.\n this.shapeToDrawEntry = new Map();\n for (const [shape, node] of this.shapeToGroup) {\n const drawEntry = this.sceneNode.getEntry(node);\n if (drawEntry) this.shapeToDrawEntry.set(shape, drawEntry);\n }\n this.applyClipping();\n this.stage.batchDraw();\n }\n\n private applyClipping(): void {\n if (!this.sceneManager.lastResult) return;\n\n const visibilityMap = this.sceneManager.cullInteractive(this._coordinateTransform);\n let changed = false;\n for (const [shape, entry] of this.shapeToDrawEntry) {\n const vis = visibilityMap.get(shape) ?? true;\n if (entry.visible !== vis) {\n entry.visible = vis;\n changed = true;\n }\n }\n if (changed) this.sceneNode.batchDraw();\n }\n\n // --- Position & overlay ---\n\n private onPositionChanged(roomId: number | undefined, center: boolean, instant: boolean) {\n if (roomId === undefined) {\n if (this.positionMarker) {\n this.positionMarker.destroy();\n this.positionMarker = undefined;\n }\n this.positionLayerNode.batchDraw();\n this.clearCurrentRoomOverlay();\n this.overlayLayerNode.batchDraw();\n return;\n }\n\n const room = this.state.mapReader.getRoom(roomId);\n if (!room) return;\n\n if (center) {\n const p = this.mapPoint(room.x, room.y);\n this.camera.panToMapPointAnimated(p.x, p.y,\n instant || this.state.settings.instantMapMove);\n }\n\n this.updateCurrentRoomOverlay(room);\n this.applyPositionMarker(room);\n }\n\n private applyPositionMarker(room: MapData.Room) {\n if (this.positionMarker) {\n this.positionMarker.destroy();\n this.positionMarker = undefined;\n }\n // Mirrors the contract in MapState.getOverlaysForArea: the position\n // marker is only visible when the player room is on the displayed area/z.\n if (room.area !== this.state.currentArea || room.z !== this.state.currentZIndex) {\n this.positionLayerNode.batchDraw();\n return;\n }\n const data = computePositionMarker(room, this.state.settings);\n this.positionMarker = this.addStyledShape(positionMarkerToShape(data), this.positionLayerNode);\n }\n\n private clearCurrentRoomOverlay() {\n this.currentRoomOverlay.forEach(node => node.destroy());\n this.currentRoomOverlay = [];\n this.positionLayerNode.batchDraw();\n }\n\n private updateCurrentRoomOverlay(room: MapData.Room) {\n this.clearCurrentRoomOverlay();\n\n if (room.area !== this.state.currentArea || room.z !== this.state.currentZIndex) {\n this.positionLayerNode.batchDraw();\n return;\n }\n\n const settings = this.state.settings;\n\n if (!settings.highlightCurrentRoom) {\n if (this.positionMarker) this.positionMarker.moveToTop();\n this.positionLayerNode.batchDraw();\n return;\n }\n\n const roomsToRedraw = new Map<number, MapData.Room>();\n roomsToRedraw.set(room.id, room);\n\n const preRoomShapes: Shape[] = [];\n const exitRenderer = this.sceneManager.exitRenderer;\n const lens = this.state.lens;\n\n // Link exits for this room → rendered as ExitDrawData through DrawingBackend\n if (this.state.currentAreaInstance && this.state.currentZIndex !== undefined) {\n const exits = this.state.currentAreaInstance\n .getLinkExits(this.state.currentZIndex)\n .filter(exit => exit.a === room.id || exit.b === room.id);\n exits.forEach(exit => {\n const data = exitRenderer.renderDataWithColor(exit, currentRoomColor, this.state.currentZIndex!);\n if (data) {\n preRoomShapes.push(this.sceneManager.buildExitShape(data));\n }\n });\n }\n\n // Special exits\n for (const se of computeSpecialExits(room, settings, currentRoomColor)) {\n preRoomShapes.push(specialExitToShape(se, room.id));\n }\n\n // Stubs\n for (const stub of computeStubs(room, settings, currentRoomColor)) {\n preRoomShapes.push(stubToShape(stub));\n }\n\n [...Object.values(room.exits), ...Object.values(room.specialExits)].forEach(id => {\n const otherRoom = this.state.mapReader.getRoom(id);\n if (\n otherRoom &&\n otherRoom.area === this.state.currentArea &&\n otherRoom.z === this.state.currentZIndex &&\n lens.isVisible(otherRoom)\n ) {\n roomsToRedraw.set(id, otherRoom);\n }\n });\n\n preRoomShapes.forEach(shape => {\n const node = this.addStyledShape(shape, this.positionLayerNode);\n if (node) this.currentRoomOverlay.push(node);\n });\n\n roomsToRedraw.forEach((roomToRedraw, id) => {\n const isCurrent = id === room.id;\n const overlayShape = layoutRoom(\n roomToRedraw,\n this.state.mapReader,\n settings,\n {\n strokeOverride: isCurrent ? currentRoomColor : settings.lineColor,\n flatPipeline: true,\n },\n );\n overlayShape.children.push(...layoutInnerExits(roomToRedraw, this.state.mapReader, settings));\n const node = this.addStyledShape(overlayShape, this.positionLayerNode);\n if (node) this.currentRoomOverlay.push(node);\n });\n\n if (this.positionMarker) {\n this.positionMarker.moveToTop();\n }\n\n this.positionLayerNode.batchDraw();\n }\n\n // --- Highlight & path sync ---\n\n syncHighlight(roomId: number, color: string | undefined) {\n const existing = this.highlightShapes.get(roomId);\n if (existing) {\n existing.destroy();\n this.highlightShapes.delete(roomId);\n }\n if (color !== undefined) {\n const room = this.state.mapReader.getRoom(roomId);\n if (room && room.area === this.state.currentArea && room.z === this.state.currentZIndex) {\n const data = computeHighlight(room, color, this.state.settings);\n const node = this.addStyledShape(highlightToShape(data), this.overlayLayerNode);\n if (node) this.highlightShapes.set(roomId, node);\n }\n }\n this.overlayLayerNode.batchDraw();\n }\n\n syncHighlights() {\n for (const node of this.highlightShapes.values()) node.destroy();\n this.highlightShapes.clear();\n\n for (const [roomId, entry] of this.state.highlights) {\n if (entry.area !== this.state.currentArea || entry.z !== this.state.currentZIndex) continue;\n const room = this.state.mapReader.getRoom(roomId);\n if (!room) continue;\n const data = computeHighlight(room, entry.color, this.state.settings);\n const node = this.addStyledShape(highlightToShape(data), this.overlayLayerNode);\n if (node) this.highlightShapes.set(roomId, node);\n }\n this.overlayLayerNode.batchDraw();\n }\n\n syncPaths() {\n this.clearPathShapes();\n const {currentArea, currentZIndex} = this.state;\n if (currentArea === undefined || currentZIndex === undefined) return;\n\n for (const path of this.state.paths) {\n const data = computePathOverlay(\n this.state.mapReader, this.state.settings,\n path.locations, path.color,\n currentArea, currentZIndex,\n );\n for (const s of pathToShapes(data)) {\n const node = this.addStyledShape(s, this.overlayLayerNode);\n if (node) this.pathShapes.push(node);\n }\n }\n this.overlayLayerNode.batchDraw();\n }\n\n // --- Private helpers ---\n\n private clearOverlayShapes() {\n for (const node of this.highlightShapes.values()) node.destroy();\n this.highlightShapes.clear();\n this.clearPathShapes();\n }\n\n private clearPathShapes() {\n for (const shape of this.pathShapes) {\n shape.destroy();\n }\n this.pathShapes = [];\n }\n}\n","/**\n * Shared colour-parsing + paint-rewriting helpers used by the shape-based\n * Style implementations (Parchment, Blueprint, Neon).\n *\n * The old BaseStyle decorator versions in `../ParchmentStyle.ts`,\n * `../BlueprintStyle.ts`, and `../NeonStyle.ts` keep their own copies for\n * now; they're deleted in step 11.\n */\n\nimport type {FillStyle} from \"../../scene/Shape\";\n\n/**\n * Apply a colour-mapping function to a {@link FillStyle}. Strings pass\n * through the mapper directly; gradients recolour every stop, leaving\n * geometry untouched. Lets styles tint gradient-filled rooms (ambient\n * lighting, palette swaps) without losing the gradient.\n */\nexport function mapFill(\n fill: FillStyle | undefined,\n mapper: (color: string) => string,\n): FillStyle | undefined {\n if (fill === undefined) return undefined;\n if (typeof fill === \"string\") return mapper(fill);\n return {\n ...fill,\n stops: fill.stops.map(s => ({offset: s.offset, color: mapper(s.color)})),\n };\n}\n\nexport interface ParsedRgb {\n r: number;\n g: number;\n b: number;\n /** 0..1 alpha — defaults to 1 when not present. */\n a: number;\n}\n\n/**\n * Parse a colour string in `rgb(r, g, b)`, `rgba(r, g, b, a)`, or\n * `#rrggbb` form. Returns `null` for anything else (named colours,\n * `hsl(...)`, etc.) so callers can fall back to a default.\n */\nexport function parseRgb(color: string): ParsedRgb | null {\n const rgbaMatch = color.match(/rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)(?:\\s*,\\s*([\\d.]+))?\\s*\\)/);\n if (rgbaMatch) {\n return {\n r: parseInt(rgbaMatch[1]),\n g: parseInt(rgbaMatch[2]),\n b: parseInt(rgbaMatch[3]),\n a: rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1,\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 a: 1,\n };\n }\n return null;\n}\n\n/** Format an `rgb(r, g, b)` or `rgba(r, g, b, a)` string. */\nexport function formatRgb(r: number, g: number, b: number, a = 1): string {\n if (a < 1) return `rgba(${r}, ${g}, ${b}, ${a})`;\n return `rgb(${r}, ${g}, ${b})`;\n}\n\n/** Perceived luminance in [0, 1] using ITU-R BT.601 weights. */\nexport function luminance(c: ParsedRgb): number {\n return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) / 255;\n}\n","import type {Shape, Paint} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {formatRgb, luminance, mapFill, parseRgb} from \"./paintMap\";\n\n/** Dark brown ink used for strokes and outlines. */\nconst INK = \"#4a3728\";\n/** Very dark brown used for text. */\nconst INK_TEXT = \"#3b2a1a\";\n\n// Parchment luminance range endpoints (dark brown → light parchment).\nconst DARK_R = 139, DARK_G = 115, DARK_B = 85;\nconst LIGHT_R = 244, LIGHT_G = 228, LIGHT_B = 193;\n\n/**\n * Map a colour to the parchment palette based on its perceived luminance.\n * Dark rooms become dark warm brown, light rooms become light parchment.\n */\nfunction toParchment(color: string): string {\n const c = parseRgb(color);\n if (!c) return formatRgb(LIGHT_R, LIGHT_G, LIGHT_B);\n const lum = luminance(c);\n return formatRgb(\n Math.round(DARK_R + (LIGHT_R - DARK_R) * lum),\n Math.round(DARK_G + (LIGHT_G - DARK_G) * lum),\n Math.round(DARK_B + (LIGHT_B - DARK_B) * lum),\n c.a,\n );\n}\n\n/** Convert a colour to dark ink, preserving any alpha channel. */\nfunction toInk(color: string): string {\n const c = parseRgb(color);\n if (!c || c.a >= 1) return INK;\n const ink = parseRgb(INK)!;\n return formatRgb(ink.r, ink.g, ink.b, c.a);\n}\n\n/** Apply paint rewrites to a single Paint record. */\nfunction rewriteFillStroke(paint: Paint): Paint {\n return {\n ...paint,\n fill: mapFill(paint.fill, toParchment),\n stroke: paint.stroke ? toInk(paint.stroke) : paint.stroke,\n };\n}\n\n/**\n * Warm sepia / old-parchment palette as a {@link Style}.\n *\n * - Fills are mapped through a luminance-based parchment gradient\n * (dark brown → light parchment) so different environment colours stay\n * visually distinct while keeping the aged-paper look.\n * - Strokes become dark ink brown.\n * - Text uses a very dark brown for legibility.\n * - Images and groups pass through unchanged (the caller walks group\n * children separately).\n */\nexport const parchmentShapeStyle: Style = {\n transform(shape: Shape): Shape {\n switch (shape.type) {\n case \"rect\":\n case \"circle\":\n case \"polygon\":\n return {...shape, paint: rewriteFillStroke(shape.paint)};\n case \"line\":\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: toInk(shape.paint.stroke)}\n : shape.paint,\n };\n case \"text\":\n return {...shape, fill: INK_TEXT};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","import type {Shape, Paint} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {formatRgb, luminance, mapFill, parseRgb} from \"./paintMap\";\n\n/** Light cyan used for strokes and lines. */\nconst LINE_COLOR = \"#c0deff\";\n/** Near-white blue used for text. */\nconst TEXT_COLOR = \"#e0f0ff\";\n\n// Blueprint luminance range endpoints (deep blue → lighter blue).\nconst DARK_R = 20, DARK_G = 40, DARK_B = 80;\nconst LIGHT_R = 60, LIGHT_G = 100, LIGHT_B = 160;\n\n/**\n * Map a colour to the blueprint palette based on its perceived luminance.\n * Dark rooms become deep blueprint blue, light rooms lighter blueprint blue.\n */\nfunction toBlueprint(color: string): string {\n const c = parseRgb(color);\n if (!c) return formatRgb(LIGHT_R, LIGHT_G, LIGHT_B);\n const lum = luminance(c);\n return formatRgb(\n Math.round(DARK_R + (LIGHT_R - DARK_R) * lum),\n Math.round(DARK_G + (LIGHT_G - DARK_G) * lum),\n Math.round(DARK_B + (LIGHT_B - DARK_B) * lum),\n c.a,\n );\n}\n\n/** Convert a colour to the blueprint line colour, preserving any alpha. */\nfunction toLine(color: string): string {\n const c = parseRgb(color);\n if (!c || c.a >= 1) return LINE_COLOR;\n const line = parseRgb(LINE_COLOR)!;\n return formatRgb(line.r, line.g, line.b, c.a);\n}\n\nfunction rewriteFillStroke(paint: Paint): Paint {\n return {\n ...paint,\n fill: mapFill(paint.fill, toBlueprint),\n stroke: paint.stroke ? toLine(paint.stroke) : paint.stroke,\n };\n}\n\n/**\n * Technical blueprint aesthetic as a {@link Style} — white lines on deep blue.\n *\n * - Fills are mapped through a luminance-based blue gradient (deep blue →\n * lighter blue) so environment differences stay visible.\n * - Strokes become light cyan-white.\n * - Text uses near-white blue for legibility.\n * - Images and groups pass through unchanged.\n */\nexport const blueprintShapeStyle: Style = {\n transform(shape: Shape): Shape {\n switch (shape.type) {\n case \"rect\":\n case \"circle\":\n case \"polygon\":\n return {...shape, paint: rewriteFillStroke(shape.paint)};\n case \"line\":\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: toLine(shape.paint.stroke)}\n : shape.paint,\n };\n case \"text\":\n return {...shape, fill: TEXT_COLOR};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","import type {Shape, Paint, RectShape, CircleShape, LineShape} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {mapFill, parseRgb} from \"./paintMap\";\n\n/** Bright cyan-green used for text. */\nconst TEXT_COLOR = \"#00ffd0\";\n\n/** Default glow alpha. */\nconst GLOW_ALPHA = 0.3;\n\n/** Glow stroke width multiplier (relative to the original stroke). */\nconst GLOW_WIDTH_MULT = 3;\n\nfunction rgbToHsl(r: number, g: number, b: number): [number, number, number] {\n r /= 255; g /= 255; b /= 255;\n const max = Math.max(r, g, b), min = Math.min(r, g, b);\n const l = (max + min) / 2;\n if (max === min) return [0, 0, l];\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h: number;\n if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n else if (max === g) h = ((b - r) / d + 2) / 6;\n else h = ((r - g) / d + 4) / 6;\n return [h * 360, s, l];\n}\n\nfunction hslToRgbString(h: number, s: number, l: number, a = 1): string {\n h = ((h % 360) + 360) % 360;\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n let r: number, g: number, b: number;\n if (h < 60) { r = c; g = x; b = 0; }\n else if (h < 120) { r = x; g = c; b = 0; }\n else if (h < 180) { r = 0; g = c; b = x; }\n else if (h < 240) { r = 0; g = x; b = c; }\n else if (h < 300) { r = x; g = 0; b = c; }\n else { r = c; g = 0; b = x; }\n const ri = Math.round((r + m) * 255);\n const gi = Math.round((g + m) * 255);\n const bi = Math.round((b + m) * 255);\n if (a < 1) return `rgba(${ri}, ${gi}, ${bi}, ${a})`;\n return `rgb(${ri}, ${gi}, ${bi})`;\n}\n\nfunction neonHueAlpha(color: string): {h: number; a: number} {\n const c = parseRgb(color);\n if (!c) return {h: 150, a: 1};\n const [h] = rgbToHsl(c.r, c.g, c.b);\n return {h, a: c.a};\n}\n\nfunction toNeonStroke(color: string): string {\n const {h, a} = neonHueAlpha(color);\n return hslToRgbString(h, 0.95, 0.65, a);\n}\n\nfunction toNeonFill(color: string): string {\n const c = parseRgb(color);\n if (!c) return \"rgb(8, 12, 10)\";\n const [h] = rgbToHsl(c.r, c.g, c.b);\n return hslToRgbString(h, 0.4, 0.1, c.a);\n}\n\nfunction toGlowStroke(color: string): string {\n const {h, a} = neonHueAlpha(color);\n return hslToRgbString(h, 0.95, 0.65, Math.min(a, GLOW_ALPHA));\n}\n\nfunction neonPaint(paint: Paint): Paint {\n return {\n ...paint,\n fill: mapFill(paint.fill, toNeonFill),\n stroke: paint.stroke ? toNeonStroke(paint.stroke) : paint.stroke,\n };\n}\n\nfunction glowPaint(paint: Paint, withAlpha = false): Paint {\n if (!paint.stroke) return paint;\n const glowWidth = (paint.strokeWidth ?? 1) * GLOW_WIDTH_MULT;\n return {\n stroke: toGlowStroke(paint.stroke),\n strokeWidth: glowWidth,\n dash: paint.dash,\n dashEnabled: paint.dashEnabled,\n alpha: withAlpha ? GLOW_ALPHA : paint.alpha,\n };\n}\n\n/**\n * Cyberpunk / neon aesthetic as a {@link Style} — glowing coloured outlines\n * on a dark background.\n *\n * - Fills become very dark with a subtle tint of the original hue.\n * - Strokes become bright neon (hue-preserved, saturation/lightness boosted).\n * - A glow effect is added for stroked rect / circle / line shapes by emitting\n * an extra wider translucent shape **before** the main one, so callers see\n * `[glow, main]` (the order in which they should be rendered).\n * - Polygons get the neon repaint without a glow pass — same as the legacy\n * decorator, since polygons in the pipeline are inner-exit triangles and\n * exit arrowheads where a glow halo reads as visual noise.\n * - Text uses bright cyan-green.\n * - Images and groups pass through unchanged.\n */\nexport const neonShapeStyle: Style = {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n const main: RectShape = {...shape, paint: neonPaint(shape.paint)};\n if (!shape.paint.stroke) return main;\n const glow: RectShape = {\n ...shape,\n paint: {...glowPaint(shape.paint), fill: undefined},\n };\n return [glow, main];\n }\n case \"circle\": {\n const main: CircleShape = {...shape, paint: neonPaint(shape.paint)};\n if (!shape.paint.stroke) return main;\n const glow: CircleShape = {\n ...shape,\n paint: {...glowPaint(shape.paint), fill: undefined},\n };\n return [glow, main];\n }\n case \"line\": {\n const main: LineShape = {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: toNeonStroke(shape.paint.stroke)}\n : shape.paint,\n };\n if (!shape.paint.stroke) return main;\n const glow: LineShape = {...shape, paint: glowPaint(shape.paint, true)};\n return [glow, main];\n }\n case \"polygon\":\n return {...shape, paint: neonPaint(shape.paint)};\n case \"text\":\n return {...shape, fill: TEXT_COLOR};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","/**\n * Hand-drawn wobble math used by the shape-based SketchyStyle.\n *\n * Identical algorithms to `../SketchyStyle.ts` so cross-style snapshots\n * stay byte-identical between old and new pipelines. When step 11 deletes\n * the legacy decorator, this module becomes the single source of truth.\n */\n\n/** Simple seeded LCG. Returns values in `[0, 1)`. */\nexport function createRng(seed: number): () => number {\n let s = seed | 0 || 1;\n return () => {\n s = (s * 1664525 + 1013904223) | 0;\n return (s >>> 0) / 4294967296;\n };\n}\n\n/** Hash a set of numeric values into a stable integer seed. */\nexport function hashCoords(...values: number[]): number {\n let h = 0x9e3779b9;\n for (const v of values) {\n h ^= ((v * 1000) | 0) + 0x9e3779b9 + (h << 6) + (h >> 2);\n }\n return h;\n}\n\n/**\n * Subdivide a straight segment into several sub-segments with slight\n * perpendicular displacement, producing a hand-drawn wobble.\n */\nexport function wobbleSegment(\n x1: number, y1: number, x2: number, y2: number,\n jitter: number, rng: () => number,\n): number[] {\n const dx = x2 - x1;\n const dy = y2 - y1;\n const len = Math.sqrt(dx * dx + dy * dy);\n if (len < 0.001) return [x1, y1, x2, y2];\n\n const nx = -dy / len;\n const ny = dx / len;\n\n const subdivisions = Math.max(2, Math.min(6, Math.ceil(len / 0.15)));\n const points: number[] = [\n x1 + nx * (rng() - 0.5) * jitter * 0.3,\n y1 + ny * (rng() - 0.5) * jitter * 0.3,\n ];\n\n for (let i = 1; i < subdivisions; i++) {\n const t = i / subdivisions;\n const px = x1 + dx * t;\n const py = y1 + dy * t;\n const offset = (rng() - 0.5) * 2 * jitter;\n points.push(px + nx * offset, py + ny * offset);\n }\n\n points.push(\n x2 + nx * (rng() - 0.5) * jitter * 0.3,\n y2 + ny * (rng() - 0.5) * jitter * 0.3,\n );\n return points;\n}\n\n/** Wobble an open polyline. */\nexport function wobblePolyline(points: number[], jitter: number, rng: () => number): number[] {\n if (points.length < 4) return points;\n const result: number[] = [];\n for (let i = 0; i < points.length - 2; i += 2) {\n const seg = wobbleSegment(\n points[i], points[i + 1],\n points[i + 2], points[i + 3],\n jitter, rng,\n );\n if (i === 0) {\n result.push(...seg);\n } else {\n for (let j = 2; j < seg.length; j++) result.push(seg[j]);\n }\n }\n return result;\n}\n\n/** Convert a rectangle to a closed wobbly polygon. */\nexport function wobbleRect(\n x: number, y: number, w: number, h: number,\n jitter: number, rng: () => number,\n): number[] {\n const corners = [x, y, x + w, y, x + w, y + h, x, y + h];\n const result: number[] = [];\n for (let i = 0; i < 4; i++) {\n const x1 = corners[i * 2], y1 = corners[i * 2 + 1];\n const ni = (i + 1) % 4;\n const x2 = corners[ni * 2], y2 = corners[ni * 2 + 1];\n const seg = wobbleSegment(x1, y1, x2, y2, jitter, rng);\n for (let j = 0; j < seg.length - 2; j++) result.push(seg[j]);\n }\n return result;\n}\n\n/** Convert a circle to a wobbly polygon with slight radius variation. */\nexport function wobbleCircle(\n cx: number, cy: number, radius: number,\n jitter: number, rng: () => number,\n): number[] {\n const segments = 24;\n const points: number[] = [];\n for (let i = 0; i < segments; i++) {\n const angle = (i / segments) * Math.PI * 2;\n const rJitter = radius + (rng() - 0.5) * 2 * jitter;\n points.push(cx + Math.cos(angle) * rJitter, cy + Math.sin(angle) * rJitter);\n }\n return points;\n}\n\n/** Wobble a closed polygon by subdividing each edge. */\nexport function wobblePolygonEdges(vertices: number[], jitter: number, rng: () => number): number[] {\n const n = vertices.length / 2;\n if (n < 2) return vertices;\n const result: number[] = [];\n for (let i = 0; i < n; i++) {\n const x1 = vertices[i * 2], y1 = vertices[i * 2 + 1];\n const ni = (i + 1) % n;\n const x2 = vertices[ni * 2], y2 = vertices[ni * 2 + 1];\n const seg = wobbleSegment(x1, y1, x2, y2, jitter, rng);\n for (let j = 0; j < seg.length - 2; j++) result.push(seg[j]);\n }\n return result;\n}\n","import type {Shape, PolygonShape, Paint} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {\n createRng,\n hashCoords,\n wobbleCircle,\n wobblePolygonEdges,\n wobblePolyline,\n wobbleRect,\n} from \"./wobble\";\n\nexport interface SketchyOptions {\n /** Jitter amount in map units (e.g. 0.015). */\n jitter: number;\n /** Pencil colour for all strokes/fills (e.g. `#444444`). */\n color: string;\n}\n\n/** Apply pencil colour to a stroke, preserving any alpha from the original. */\nfunction applyPencilWithAlpha(pencil: string, original: string | undefined): string {\n if (!original) return pencil;\n const alphaMatch = original.match(/^rgba\\(.+,\\s*([\\d.]+)\\s*\\)$/);\n if (!alphaMatch) return pencil;\n const alpha = parseFloat(alphaMatch[1]);\n if (alpha >= 1) return pencil;\n const r = parseInt(pencil.slice(1, 3), 16);\n const g = parseInt(pencil.slice(3, 5), 16);\n const b = parseInt(pencil.slice(5, 7), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n/**\n * Hand-drawn pencil wobble as a {@link Style}. Subdivides edges into wobbly\n * segments with seeded perpendicular jitter, so re-renders produce the same\n * pattern.\n *\n * - Rectangles → wobbly polygons (paper-white fill on parchment, pencil stroke).\n * Rounded-corner rectangles render as a paper-white fill rect plus a wobbly\n * polygon outline, so the corners stay rounded but the silhouette wobbles.\n * - Circles → wobbly 24-segment polygons.\n * - Lines → wobbled polylines.\n * - Polygons → wobbled edge subdivisions.\n * - Text → repainted in pencil colour.\n * - Images and groups pass through unchanged.\n */\nexport function sketchyShapeStyle(options: SketchyOptions): Style {\n const {jitter, color: pencil} = options;\n\n return {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n const rng = createRng(hashCoords(shape.x, shape.y, shape.width, shape.height));\n const fill = shape.paint.fill ? \"#ffffff\" : undefined;\n const stroke = shape.paint.stroke ? pencil : undefined;\n\n if (shape.cornerRadius && shape.cornerRadius > 0) {\n const out: Shape[] = [];\n if (fill) {\n // Keep rounded fill rect; only the outline wobbles.\n out.push({...shape, paint: {fill, stroke: undefined, strokeWidth: 0}});\n }\n if (stroke && shape.paint.strokeWidth) {\n const verts = wobbleRect(shape.x, shape.y, shape.width, shape.height, jitter, rng);\n const wobbly: PolygonShape = {\n type: \"polygon\",\n vertices: verts,\n paint: {stroke, strokeWidth: shape.paint.strokeWidth},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n out.push(wobbly);\n }\n if (out.length === 1) return out[0];\n return out;\n }\n\n const verts = wobbleRect(shape.x, shape.y, shape.width, shape.height, jitter, rng);\n return {\n type: \"polygon\",\n vertices: verts,\n paint: {fill, stroke, strokeWidth: shape.paint.strokeWidth ?? 0},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n }\n case \"circle\": {\n const rng = createRng(hashCoords(shape.cx, shape.cy, shape.radius));\n const verts = wobbleCircle(shape.cx, shape.cy, shape.radius, jitter, rng);\n return {\n type: \"polygon\",\n vertices: verts,\n paint: {\n fill: shape.paint.fill ? \"#ffffff\" : undefined,\n stroke: shape.paint.stroke ? pencil : undefined,\n strokeWidth: shape.paint.strokeWidth ?? 0,\n },\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n }\n case \"line\": {\n const rng = createRng(hashCoords(...shape.points.slice(0, 4)));\n const wobbly = wobblePolyline(shape.points, jitter, rng);\n const stroke = applyPencilWithAlpha(pencil, shape.paint.stroke);\n const paint: Paint = {...shape.paint, stroke};\n return {...shape, points: wobbly, paint};\n }\n case \"polygon\": {\n const rng = createRng(hashCoords(...shape.vertices.slice(0, 4)));\n const wobbly = wobblePolygonEdges(shape.vertices, jitter, rng);\n return {\n ...shape,\n vertices: wobbly,\n paint: {\n ...shape.paint,\n fill: shape.paint.fill ? pencil : undefined,\n stroke: shape.paint.stroke ? pencil : undefined,\n },\n };\n }\n case \"text\":\n return {...shape, fill: pencil};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n };\n}\n","import type {Shape, FillStyle, LineShape, PolygonShape} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {darkenColor} from \"../../utils/color\";\nimport {mapFill} from \"./paintMap\";\n\n/** Darken a {@link FillStyle} by recolouring each gradient stop (or the flat colour). */\nfunction darkenFill(fill: FillStyle, factor: number): FillStyle {\n return mapFill(fill, c => darkenColor(c, factor))!;\n}\n\n/**\n * Project gradient geometry through the iso transform. The fill's endpoints /\n * focal points are in world coords; after iso has warped the shape's vertices\n * the gradient line has to follow or the diamond samples colours from a\n * region that doesn't overlap its pixels (the user sees a flat border instead\n * of the gradient).\n *\n * Radial gradients become ellipses under the non-uniform iso transform, but\n * Canvas2D / SVG radial gradients are circles. We project the centres and\n * leave the radius alone — that is the best faithful representation\n * available without ellipse support.\n */\nfunction projectFill(iso: IsoFn, fill: FillStyle | undefined): FillStyle | undefined {\n if (!fill || typeof fill === \"string\") return fill;\n if (fill.type === \"linear\") {\n const [x0, y0] = iso(fill.x0, fill.y0);\n const [x1, y1] = iso(fill.x1, fill.y1);\n return {...fill, x0, y0, x1, y1};\n }\n const [cx, cy] = iso(fill.cx, fill.cy);\n const out = {...fill, cx, cy};\n if (fill.fx !== undefined && fill.fy !== undefined) {\n const [fx, fy] = iso(fill.fx, fill.fy);\n out.fx = fx;\n out.fy = fy;\n }\n return out;\n}\n\nexport type IsometricRotation = number;\n\nexport interface IsometricOptions {\n /** Cube side face height. Defaults to 0.18. */\n depth?: number;\n /** Rotation angle in degrees. Defaults to 0. */\n rotation?: IsometricRotation;\n}\n\ntype IsoFn = (x: number, y: number) => [number, number];\n\nfunction buildTransform(angleDeg: number): IsoFn {\n const rad = (angleDeg * Math.PI) / 180;\n const cosA = Math.cos(rad);\n const sinA = Math.sin(rad);\n return (x, y) => [\n x * cosA - y * sinA,\n (x * sinA + y * cosA) * 0.5,\n ];\n}\n\nfunction buildInverseTransform(angleDeg: number): IsoFn {\n const rad = (angleDeg * Math.PI) / 180;\n const cosA = Math.cos(rad);\n const sinA = Math.sin(rad);\n return (u, v) => [\n u * cosA + 2 * v * sinA,\n -u * sinA + 2 * v * cosA,\n ];\n}\n\nconst CIRCLE_SEGMENTS = 32;\n\nfunction projectPoints(iso: IsoFn, pts: number[]): number[] {\n const out = new Array<number>(pts.length);\n for (let i = 0; i < pts.length; i += 2) {\n const [ox, oy] = iso(pts[i], pts[i + 1]);\n out[i] = ox;\n out[i + 1] = oy;\n }\n return out;\n}\n\nfunction rectToDiamond(iso: IsoFn, x: number, y: number, w: number, h: number): number[] {\n const [t0x, t0y] = iso(x, y);\n const [t1x, t1y] = iso(x + w, y);\n const [t2x, t2y] = iso(x + w, y + h);\n const [t3x, t3y] = iso(x, y + h);\n return [t0x, t0y, t1x, t1y, t2x, t2y, t3x, t3y];\n}\n\n/**\n * 2:1 isometric projection as a {@link Style}.\n *\n * - Rectangles → diamond (rhombus) polygons. Dashed rects decompose to four\n * dashed line shapes (one per edge). With `depth > 0` and a fill, rooms gain\n * right + left side faces (darker shades of the fill) drawn beneath the top\n * diamond plus six outline edges, producing a 3D cube look.\n * - Circles → ellipse polygons (32 segments). Dashed circles decompose to\n * line segments. With `depth > 0` and a fill, the lower half extrudes into\n * left + right side faces.\n * - Lines / polygons / grid lines have their coordinates projected.\n * - Text / images keep their position and gain an affine `transform` field\n * that maps their bounding box to the iso parallelogram (text stays\n * upright; readers consume `transform` directly).\n * - Group origins are projected; children are projected separately. Because\n * iso is linear, `iso(g) + iso(c) === iso(g + c)`, so the final positions\n * line up exactly.\n *\n * Provides `worldToScene` / `sceneToWorld` so `HitTester` and `Camera` can\n * round-trip clicks through the projection. Link-layer groups (exits,\n * special exits, stubs) are shifted down by the cube depth in render space\n * so connectors attach at the cube base instead of the top face.\n */\nexport function isometricShapeStyle(options: IsometricOptions = {}): Style {\n const rotation = options.rotation ?? 0;\n const depth = options.depth ?? 0.18;\n const iso = buildTransform(rotation);\n const isoInv = buildInverseTransform(rotation);\n\n function isoAffine(x: number, y: number, w: number, h: number): [number, number, number, number, number, number] {\n const [tlX, tlY] = iso(x, y);\n const [trX, trY] = iso(x + w, y);\n const [blX, blY] = iso(x, y + h);\n return [\n (trX - tlX) / w, (trY - tlY) / w,\n (blX - tlX) / h, (blY - tlY) / h,\n tlX, tlY,\n ];\n }\n\n function projectRect(shape: Extract<Shape, {type: \"rect\"}>): Shape | Shape[] {\n const {x, y, width: w, height: h, paint} = shape;\n const diamond = rectToDiamond(iso, x, y, w, h);\n\n // Dashed rects: decompose to 4 dashed lines.\n if (paint.dash || paint.dashEnabled) {\n const lines: LineShape[] = [];\n for (let i = 0; i < 8; i += 2) {\n const ni = (i + 2) % 8;\n lines.push({\n type: \"line\",\n points: [diamond[i], diamond[i + 1], diamond[ni], diamond[ni + 1]],\n paint: {\n stroke: paint.stroke,\n strokeWidth: paint.strokeWidth,\n dash: paint.dash,\n },\n layer: shape.layer,\n });\n }\n return lines;\n }\n\n // Cube with side faces.\n if (depth > 0 && paint.fill) {\n const out: Shape[] = [];\n // Find bottom-most vertex for cube extrusion.\n let bottomIdx = 0;\n for (let i = 1; i < 4; i++) {\n if (diamond[i * 2 + 1] > diamond[bottomIdx * 2 + 1]) bottomIdx = i;\n }\n const bX = diamond[bottomIdx * 2], bY = diamond[bottomIdx * 2 + 1];\n const rIdx = (bottomIdx + 3) % 4;\n const rX = diamond[rIdx * 2], rY = diamond[rIdx * 2 + 1];\n const lIdx = (bottomIdx + 1) % 4;\n const lX = diamond[lIdx * 2], lY = diamond[lIdx * 2 + 1];\n const tIdx = (bottomIdx + 2) % 4;\n const tX = diamond[tIdx * 2], tY = diamond[tIdx * 2 + 1];\n\n const projectedFill = projectFill(iso, paint.fill);\n\n // Right face, left face (no strokes), then top diamond.\n out.push({\n type: \"polygon\",\n vertices: [rX, rY, bX, bY, bX, bY + depth, rX, rY + depth],\n paint: {fill: darkenFill(projectedFill!, 0.2)},\n layer: shape.layer,\n });\n out.push({\n type: \"polygon\",\n vertices: [bX, bY, lX, lY, lX, lY + depth, bX, bY + depth],\n paint: {fill: darkenFill(projectedFill!, 0.4)},\n layer: shape.layer,\n });\n out.push({\n type: \"polygon\",\n vertices: diamond,\n paint: {fill: projectedFill},\n layer: shape.layer,\n hit: shape.hit,\n });\n\n // Outline edges (visible only — top face top edges + outer side edges).\n if (paint.stroke && paint.strokeWidth) {\n const sw = paint.strokeWidth;\n const stroke = paint.stroke;\n const lineLayer = shape.layer;\n const push = (points: number[]) => out.push({\n type: \"line\",\n points,\n paint: {stroke, strokeWidth: sw},\n layer: lineLayer,\n });\n push([tX, tY, rX, rY]);\n push([lX, lY, tX, tY]);\n push([rX, rY, rX, rY + depth]);\n push([lX, lY, lX, lY + depth]);\n push([rX, rY + depth, bX, bY + depth]);\n push([bX, bY + depth, lX, lY + depth]);\n }\n return out;\n }\n\n // Flat diamond.\n return {\n type: \"polygon\",\n vertices: diamond,\n paint: {fill: projectFill(iso, paint.fill), stroke: paint.stroke, strokeWidth: paint.strokeWidth},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n }\n\n function projectCircle(shape: Extract<Shape, {type: \"circle\"}>): Shape | Shape[] {\n const {cx, cy, radius, paint} = shape;\n const verts = new Array<number>(CIRCLE_SEGMENTS * 2);\n for (let i = 0; i < CIRCLE_SEGMENTS; i++) {\n const a = (i / CIRCLE_SEGMENTS) * Math.PI * 2;\n const [ox, oy] = iso(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);\n verts[i * 2] = ox;\n verts[i * 2 + 1] = oy;\n }\n\n if (paint.dash || paint.dashEnabled) {\n const lines: LineShape[] = [];\n for (let i = 0; i < CIRCLE_SEGMENTS; i++) {\n const ni = (i + 1) % CIRCLE_SEGMENTS;\n lines.push({\n type: \"line\",\n points: [verts[i * 2], verts[i * 2 + 1], verts[ni * 2], verts[ni * 2 + 1]],\n paint: {\n stroke: paint.stroke,\n strokeWidth: paint.strokeWidth,\n dash: paint.dash,\n },\n layer: shape.layer,\n });\n }\n return lines;\n }\n\n const out: Shape[] = [];\n const projectedFill = projectFill(iso, paint.fill);\n if (depth > 0 && paint.fill) {\n // Find extreme vertices.\n let rightIdx = 0, leftIdx = 0, bottomIdx = 0;\n let maxX = -Infinity, minX = Infinity, maxY = -Infinity;\n for (let i = 0; i < CIRCLE_SEGMENTS; i++) {\n if (verts[i * 2] > maxX) { maxX = verts[i * 2]; rightIdx = i; }\n if (verts[i * 2] < minX) { minX = verts[i * 2]; leftIdx = i; }\n if (verts[i * 2 + 1] > maxY) { maxY = verts[i * 2 + 1]; bottomIdx = i; }\n }\n\n const rightFace: number[] = [];\n let idx = rightIdx;\n for (let safety = 0; safety <= CIRCLE_SEGMENTS; safety++) {\n rightFace.push(verts[idx * 2], verts[idx * 2 + 1]);\n if (idx === bottomIdx) break;\n idx = (idx + 1) % CIRCLE_SEGMENTS;\n }\n for (let i = rightFace.length - 2; i >= 0; i -= 2) {\n rightFace.push(rightFace[i], rightFace[i + 1] + depth);\n }\n out.push({\n type: \"polygon\",\n vertices: rightFace,\n paint: {\n fill: darkenFill(projectedFill!, 0.2),\n stroke: paint.stroke,\n strokeWidth: paint.strokeWidth ? paint.strokeWidth * 0.5 : undefined,\n },\n layer: shape.layer,\n });\n\n const leftFace: number[] = [];\n idx = bottomIdx;\n for (let safety = 0; safety <= CIRCLE_SEGMENTS; safety++) {\n leftFace.push(verts[idx * 2], verts[idx * 2 + 1]);\n if (idx === leftIdx) break;\n idx = (idx + 1) % CIRCLE_SEGMENTS;\n }\n for (let i = leftFace.length - 2; i >= 0; i -= 2) {\n leftFace.push(leftFace[i], leftFace[i + 1] + depth);\n }\n out.push({\n type: \"polygon\",\n vertices: leftFace,\n paint: {\n fill: darkenFill(projectedFill!, 0.4),\n stroke: paint.stroke,\n strokeWidth: paint.strokeWidth ? paint.strokeWidth * 0.5 : undefined,\n },\n layer: shape.layer,\n });\n }\n\n const top: PolygonShape = {\n type: \"polygon\",\n vertices: verts,\n paint: {fill: projectedFill, stroke: paint.stroke, strokeWidth: paint.strokeWidth},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n out.push(top);\n return out.length === 1 ? out[0] : out;\n }\n\n return {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\":\n return projectRect(shape);\n case \"circle\":\n return projectCircle(shape);\n case \"line\":\n return {...shape, points: projectPoints(iso, shape.points)};\n case \"polygon\":\n return {\n ...shape,\n vertices: projectPoints(iso, shape.vertices),\n paint: {...shape.paint, fill: projectFill(iso, shape.paint.fill)},\n };\n case \"text\": {\n const w = shape.width ?? 0;\n const h = shape.height ?? 0;\n if (w > 0 && h > 0) {\n return {\n ...shape,\n x: 0,\n y: 0,\n transform: isoAffine(shape.x, shape.y, w, h),\n };\n }\n const [ox, oy] = iso(shape.x, shape.y);\n return {...shape, x: ox, y: oy};\n }\n case \"image\":\n return {\n ...shape,\n x: 0,\n y: 0,\n transform: isoAffine(shape.x, shape.y, shape.width, shape.height),\n };\n case \"group\": {\n const [ox, oy] = iso(shape.x, shape.y);\n // Link-layer connectors (exits, special exits, stubs) attach\n // to the cube base, not the projected top face. Shift the\n // group origin down by the cube depth in render space.\n if (depth > 0 && shape.layer === \"link\") {\n return {...shape, x: ox, y: oy + depth};\n }\n return {...shape, x: ox, y: oy};\n }\n }\n },\n\n worldToScene(x, y) {\n const [ox, oy] = iso(x, y);\n return {x: ox, y: oy};\n },\n\n sceneToWorld(x, y) {\n const [ox, oy] = isoInv(x, y);\n return {x: ox, y: oy};\n },\n };\n}\n","import type {Shape, Paint, RectShape, PolygonShape} from \"../../scene/Shape\";\nimport type {Style, StyleContext} from \"../Style\";\nimport {formatRgb, luminance, mapFill, parseRgb} from \"./paintMap\";\n\n/** Near-black asphalt used for dark room fills and strokes. */\nconst ASPHALT = \"#1a1a1a\";\n/** Bright safety yellow for stripe fills and text. */\nconst CAUTION_YELLOW = \"#ffc200\";\n/** Safety orange used for exit lines and polygon arrows. */\nconst EXIT_COLOR = \"#ff6600\";\n\n// Luminance gradient endpoints (non-striped shapes: circles, etc.)\nconst DARK_R = 26, DARK_G = 26, DARK_B = 26;\nconst LIGHT_R = 255, LIGHT_G = 194, LIGHT_B = 0;\n\nfunction toConstruction(color: string): string {\n const c = parseRgb(color);\n if (!c) return formatRgb(LIGHT_R, LIGHT_G, LIGHT_B);\n const lum = luminance(c);\n return formatRgb(\n Math.round(DARK_R + (LIGHT_R - DARK_R) * lum),\n Math.round(DARK_G + (LIGHT_G - DARK_G) * lum),\n Math.round(DARK_B + (LIGHT_B - DARK_B) * lum),\n c.a,\n );\n}\n\nfunction rewriteFillStroke(paint: Paint): Paint {\n return {\n ...paint,\n fill: mapFill(paint.fill, toConstruction),\n stroke: paint.stroke ? ASPHALT : paint.stroke,\n };\n}\n\n// --- Diagonal stripe clipping via Sutherland-Hodgman ---\n\ntype Pt = [number, number];\n\n/** Clip a convex polygon to the half-plane a·x + b·y ≥ c. */\nfunction clipHalfplane(poly: Pt[], a: number, b: number, c: number): Pt[] {\n const out: Pt[] = [];\n const n = poly.length;\n for (let i = 0; i < n; i++) {\n const curr = poly[i];\n const next = poly[(i + 1) % n];\n const dc = a * curr[0] + b * curr[1] - c;\n const dn = a * next[0] + b * next[1] - c;\n if (dc >= 0) out.push(curr);\n if ((dc > 0 && dn < 0) || (dc < 0 && dn > 0)) {\n const t = dc / (dc - dn);\n out.push([curr[0] + t * (next[0] - curr[0]), curr[1] + t * (next[1] - curr[1])]);\n }\n }\n return out;\n}\n\n/**\n * Return flat vertex arrays for the yellow stripe polygons that fall inside\n * the given rect. Stripes run at 45° in the \"/\" direction (x + y = const),\n * matching classic hazard-tape markings.\n */\nfunction rectStripeVertices(\n x: number, y: number, w: number, h: number, roomSize: number,\n): number[][] {\n const corners: Pt[] = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]];\n const period = roomSize * 0.4;\n const half = period * 0.35;\n const uMin = x + y;\n const uMax = x + w + y + h;\n const startK = Math.floor(uMin / period) - 1;\n const endK = Math.ceil(uMax / period) + 1;\n const result: number[][] = [];\n\n for (let k = startK; k <= endK; k++) {\n const u1 = k * period;\n const u2 = u1 + half;\n // Yellow band: u1 ≤ x + y ≤ u2\n let poly = clipHalfplane(corners, 1, 1, u1); // x + y ≥ u1\n poly = clipHalfplane(poly, -1, -1, -u2); // x + y ≤ u2\n if (poly.length >= 3) result.push(poly.flatMap(p => [p[0], p[1]]));\n }\n\n return result;\n}\n\n/**\n * Construction-site hazard aesthetic as a {@link Style}.\n *\n * Rect rooms get diagonal yellow/black stripes: the rect is rendered as a\n * dark asphalt base, then yellow stripe polygons (clipped to the room bounds\n * via Sutherland-Hodgman) are emitted on top, then the black border is drawn\n * last so room edges stay crisp. The stripe period scales with `roomSize`\n * so markings look consistent across zoom levels and room-size settings.\n *\n * Other shape types:\n * - Circles use a luminance-mapped gradient (asphalt → safety yellow).\n * - Exit lines and polygon arrows become safety orange (#FF6600).\n * - Text uses safety yellow.\n * - Images and groups pass through unchanged.\n */\nexport const constructionShapeStyle: Style = {\n transform(shape: Shape, ctx: StyleContext): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n if (!shape.paint.fill) {\n // Stroke-only rect — just recolour, no stripes needed.\n return {...shape, paint: rewriteFillStroke(shape.paint)};\n }\n\n // 1. Dark asphalt base (carries hit info, fill only).\n const base: RectShape = {\n ...shape,\n paint: {fill: ASPHALT, strokeWidth: shape.paint.strokeWidth},\n };\n\n // 2. Yellow diagonal stripes clipped to the room rect.\n const verts = rectStripeVertices(shape.x, shape.y, shape.width, shape.height, ctx.roomSize);\n const stripes: PolygonShape[] = verts.map(v => ({\n type: \"polygon\" as const,\n vertices: v,\n layer: shape.layer,\n paint: {fill: CAUTION_YELLOW, alpha: 0.65},\n }));\n\n // 3. Black border on top — no fill, no hit (hit is on base).\n const border: RectShape = {\n ...shape,\n hit: undefined,\n paint: {stroke: ASPHALT, strokeWidth: shape.paint.strokeWidth},\n };\n\n return [base, ...stripes, border];\n }\n\n case \"circle\":\n return {...shape, paint: rewriteFillStroke(shape.paint)};\n\n case \"polygon\":\n return {\n ...shape,\n paint: {\n ...shape.paint,\n fill: shape.paint.fill ? EXIT_COLOR : shape.paint.fill,\n stroke: shape.paint.stroke ? ASPHALT : shape.paint.stroke,\n },\n };\n\n case \"line\":\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: EXIT_COLOR}\n : shape.paint,\n };\n\n case \"text\":\n return {...shape, fill: EXIT_COLOR, stroke: ASPHALT, strokeWidth: ctx.roomSize * 0.25};\n\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","import type {Shape, Paint, RectShape, CircleShape, LineShape} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {mapFill, parseRgb} from \"./paintMap\";\n\n/** Near-white blue for labels. */\nconst TEXT_COLOR = \"#c8eeff\";\n/** Electric cyan for room borders and glow. */\nconst STROKE_COLOR = \"#00c8ff\";\n/** Targeting green-cyan for exits and arrows. */\nconst EXIT_COLOR = \"#00ffaa\";\n\nconst GLOW_ALPHA = 0.2;\nconst GLOW_WIDTH_MULT = 5;\n\n/** Default hue (cyan) used when the original colour is achromatic. */\nconst DEFAULT_HUE = 190;\n\nfunction rgbToHsl(r: number, g: number, b: number): [number, number, number] {\n r /= 255; g /= 255; b /= 255;\n const max = Math.max(r, g, b), min = Math.min(r, g, b);\n const l = (max + min) / 2;\n if (max === min) return [DEFAULT_HUE, 0, l];\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h: number;\n if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n else if (max === g) h = ((b - r) / d + 2) / 6;\n else h = ((r - g) / d + 4) / 6;\n return [h * 360, s, l];\n}\n\nfunction hslToRgb(h: number, s: number, l: number, a = 1): string {\n h = ((h % 360) + 360) % 360;\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n let r: number, g: number, b: number;\n if (h < 60) { r = c; g = x; b = 0; }\n else if (h < 120) { r = x; g = c; b = 0; }\n else if (h < 180) { r = 0; g = c; b = x; }\n else if (h < 240) { r = 0; g = x; b = c; }\n else if (h < 300) { r = x; g = 0; b = c; }\n else { r = c; g = 0; b = x; }\n const ri = Math.round((r + m) * 255);\n const gi = Math.round((g + m) * 255);\n const bi = Math.round((b + m) * 255);\n return a < 1 ? `rgba(${ri},${gi},${bi},${a})` : `rgb(${ri},${gi},${bi})`;\n}\n\n/**\n * Derive a fill from the original room colour: preserve the hue so each area\n * keeps its identity, but force a dark lightness and moderate saturation to\n * fit the deep-space aesthetic. Achromatic rooms default to cyan (190°).\n */\nfunction toSciFiFill(color: string): string {\n const c = parseRgb(color);\n if (!c) return hslToRgb(DEFAULT_HUE, 0.55, 0.18);\n const [h, s] = rgbToHsl(c.r, c.g, c.b);\n const hue = s < 0.08 ? DEFAULT_HUE : h;\n return hslToRgb(hue, 0.55, 0.18, c.a);\n}\n\nfunction rewriteFillStroke(paint: Paint): Paint {\n return {\n ...paint,\n fill: mapFill(paint.fill, toSciFiFill),\n stroke: paint.stroke ? STROKE_COLOR : paint.stroke,\n };\n}\n\nfunction roomGlowPaint(paint: Paint): Paint {\n return {\n stroke: STROKE_COLOR,\n strokeWidth: (paint.strokeWidth ?? 1) * GLOW_WIDTH_MULT,\n alpha: GLOW_ALPHA,\n };\n}\n\nfunction lineGlowPaint(paint: Paint): Paint {\n return {\n stroke: EXIT_COLOR,\n strokeWidth: (paint.strokeWidth ?? 1) * GLOW_WIDTH_MULT,\n alpha: GLOW_ALPHA,\n dash: paint.dash,\n dashEnabled: paint.dashEnabled,\n };\n}\n\n/**\n * Sci-fi / space-exploration aesthetic as a {@link Style}.\n *\n * - Fills preserve the original room hue (so area colours stay distinct) but\n * are pushed to a fixed dark lightness and moderate saturation, giving each\n * zone a deep-space tinted look. Achromatic rooms default to cyan.\n * - Room borders use electric cyan (#00C8FF) with a wide translucent glow halo,\n * giving the holographic-display bloom effect.\n * - Exit lines and arrow polygons use targeting green-cyan (#00FFAA) with a\n * glow pass, evoking radar / navigation overlays.\n * - Grid lines are rendered as a barely-visible dark tint — no glow.\n * - Text uses near-white blue for crisp legibility on dark fills.\n * - Images and groups pass through unchanged.\n */\nexport const scifiShapeStyle: Style = {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n const main: RectShape = {...shape, paint: rewriteFillStroke(shape.paint)};\n if (!shape.paint.stroke) return main;\n const glow: RectShape = {\n ...shape,\n hit: undefined,\n paint: {...roomGlowPaint(shape.paint), fill: undefined},\n };\n return [glow, main];\n }\n case \"circle\": {\n const main: CircleShape = {...shape, paint: rewriteFillStroke(shape.paint)};\n if (!shape.paint.stroke) return main;\n const glow: CircleShape = {\n ...shape,\n hit: undefined,\n paint: {...roomGlowPaint(shape.paint), fill: undefined},\n };\n return [glow, main];\n }\n case \"line\": {\n if (shape.grid) {\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: \"#0a1e2e\"}\n : shape.paint,\n };\n }\n const main: LineShape = {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: EXIT_COLOR}\n : shape.paint,\n };\n if (!shape.paint.stroke) return main;\n const glow: LineShape = {...shape, hit: undefined, paint: lineGlowPaint(shape.paint)};\n return [glow, main];\n }\n case \"polygon\":\n return {\n ...shape,\n paint: {\n ...shape.paint,\n fill: shape.paint.fill ? EXIT_COLOR : shape.paint.fill,\n stroke: undefined,\n },\n };\n case \"text\":\n return {...shape, fill: TEXT_COLOR};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","import type {LinearGradient, Paint, Shape} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {darkenColor, lightenColor} from \"../../utils/color\";\n\nexport interface GradientRoomsOptions {\n /**\n * How much to lighten the top stop relative to the room's solid fill.\n * Default 0.35. Set to 0 to keep the top edge unchanged.\n */\n lighten?: number;\n /**\n * How much to darken the bottom stop. Default 0.25.\n */\n darken?: number;\n}\n\nconst DEFAULTS = {lighten: 0.35, darken: 0.25};\n\n/**\n * Replace each rect/circle/polygon's flat fill with a vertical linear\n * gradient: lightened version of the fill at the top, darkened version at\n * the bottom. Gives rooms a faux-3D shaded look. Strokes and other paint\n * fields pass through.\n *\n * When composed with palette styles (Parchment, Blueprint, …) the gradient\n * stops are recoloured by the downstream style — so `compose(Parchment,\n * GradientRooms)` produces parchment-toned gradients, not flat parchment.\n */\nexport function gradientRoomsStyle(options: GradientRoomsOptions = {}): Style {\n const lighten = options.lighten ?? DEFAULTS.lighten;\n const darken = options.darken ?? DEFAULTS.darken;\n\n function withGradient(\n paint: Paint,\n x: number, y: number, w: number, h: number,\n ): Paint {\n const fill = paint.fill;\n if (typeof fill !== \"string\") return paint; // already gradient or undefined — leave alone\n const grad: LinearGradient = {\n type: \"linear\",\n x0: x, y0: y,\n x1: x, y1: y + h,\n stops: [\n {offset: 0, color: lightenColor(fill, lighten)},\n {offset: 1, color: darkenColor(fill, darken)},\n ],\n };\n // Strip alpha here only when it was carried by the paint — leave it as a\n // future enhancement; lightenColor/darkenColor today emit `rgb(...)` not\n // `rgba(...)`, so callers that rely on translucency would see a change.\n // Document the limitation in case a user files a bug.\n void w;\n return {...paint, fill: grad};\n }\n\n return {\n transform(shape: Shape): Shape {\n switch (shape.type) {\n case \"rect\":\n return {\n ...shape,\n paint: withGradient(shape.paint, shape.x, shape.y, shape.width, shape.height),\n };\n case \"circle\":\n return {\n ...shape,\n paint: withGradient(\n shape.paint,\n shape.cx - shape.radius,\n shape.cy - shape.radius,\n shape.radius * 2,\n shape.radius * 2,\n ),\n };\n case \"polygon\": {\n if (typeof shape.paint.fill !== \"string\") return shape;\n // Use the polygon's bounding box for gradient endpoints.\n let minY = Infinity, maxY = -Infinity, minX = Infinity;\n for (let i = 0; i < shape.vertices.length; i += 2) {\n const vx = shape.vertices[i];\n const vy = shape.vertices[i + 1];\n if (vy < minY) minY = vy;\n if (vy > maxY) maxY = vy;\n if (vx < minX) minX = vx;\n }\n return {\n ...shape,\n paint: withGradient(shape.paint, minX, minY, 1, maxY - minY),\n };\n }\n default:\n return shape;\n }\n },\n };\n}\n","/**\n * Target-agnostic visual styles. Each style is a {@link Style} that transforms\n * world-space {@link Shape}s before they hit culling, hit-testing, and the\n * draw-command pipeline. The same style drives interactive canvas rendering,\n * SVG export, and Canvas / PNG rasterization.\n *\n * Usage:\n * ```ts\n * import {compose, Parchment, Sketchy, Isometric} from 'mudlet-map-renderer';\n *\n * const style = compose(\n * Parchment,\n * Sketchy({jitter: 0.012, color: '#4a3728'}),\n * Isometric({rotation: 30, depth: 0.18}),\n * );\n * renderer.setStyle(style);\n * ```\n */\n\nimport type {Style} from \"./Style\";\nimport {parchmentShapeStyle} from \"./shape/ParchmentStyle\";\nimport {blueprintShapeStyle} from \"./shape/BlueprintStyle\";\nimport {neonShapeStyle} from \"./shape/NeonStyle\";\nimport {sketchyShapeStyle, type SketchyOptions} from \"./shape/SketchyStyle\";\nimport {isometricShapeStyle, type IsometricOptions, type IsometricRotation} from \"./shape/IsometricStyle\";\nimport {constructionShapeStyle} from \"./shape/ConstructionStyle\";\nimport {scifiShapeStyle} from \"./shape/SciFiStyle\";\nimport {gradientRoomsStyle, type GradientRoomsOptions} from \"./shape/GradientRoomsStyle\";\n\nexport {compose, identityStyle} from \"./Style\";\nexport type {Style, StyleContext} from \"./Style\";\nexport {applyStyleToShapes} from \"./applyStyle\";\n\n/** Warm sepia / old-parchment palette. */\nexport const Parchment: Style = parchmentShapeStyle;\n\n/** Technical blueprint aesthetic — white lines on deep blue. */\nexport const Blueprint: Style = blueprintShapeStyle;\n\n/** Cyberpunk / neon aesthetic — glowing outlines on dark background. */\nexport const Neon: Style = neonShapeStyle;\n\n/** Hand-drawn pencil wobble. */\nexport function Sketchy(options: SketchyOptions): Style {\n return sketchyShapeStyle(options);\n}\n\n/** 2:1 isometric projection with optional cube depth. */\nexport function Isometric(options: IsometricOptions = {}): Style {\n return isometricShapeStyle(options);\n}\n\n/** Construction-site hazard aesthetic — safety yellow on asphalt, orange exits. */\nexport const Construction: Style = constructionShapeStyle;\n\n/** Sci-fi / space-exploration aesthetic — holographic cyan glow on void black. */\nexport const SciFi: Style = scifiShapeStyle;\n\n/**\n * Replace flat room fills with a vertical linear gradient (lighter top,\n * darker bottom). Compose with palette styles to keep their tones — the\n * gradient stops are recoloured per stop.\n */\nexport function GradientRooms(options: GradientRoomsOptions = {}): Style {\n return gradientRoomsStyle(options);\n}\n\nexport type {SketchyOptions, IsometricOptions, IsometricRotation, GradientRoomsOptions};\n","import type {IMapReader} from \"../reader/MapReader\";\nimport type {IArea} from \"../reader/Area\";\nimport type {RoomLens} from \"../lens/RoomLens\";\nimport type {ViewportBounds, RendererEventMap, CullingMode} from \"../types/Settings\";\nimport {createSettings} from \"../types/Settings\";\nimport type {Settings} from \"../types/Settings\";\nimport {MapState} from \"../MapState\";\nimport {KonvaRenderBackend} from \"./KonvaRenderBackend\";\nimport type {CoordFn} from \"../coord/CoordFn\";\nimport type {Style} from \"../style\";\nimport {identityStyle} from \"../style\";\nimport type {Camera} from \"../camera/Camera\";\nimport type {CullingManager} from \"../CullingManager\";\nimport type {TypedEventEmitter} from \"../TypedEventEmitter\";\nimport type {SceneOverlay} from \"../overlay/SceneOverlay\";\nimport type {LiveEffect} from \"../overlay/LiveEffect\";\nimport type {Exporter, ExportContext, ExportCanvas} from \"../export/Exporter\";\nimport type {DrawnExitEntry, DrawnSpecialExitEntry, DrawnStubEntry} from \"../ScenePipeline\";\nimport type {HitTester, HitResult} from \"../hit/HitTester\";\n\n/**\n * Contract for interactive render backends.\n *\n * Engine-neutral surface — anything that requires a specific render engine\n * (Konva layers for live effects, Konva.Stage for `toCanvas`) is intentionally\n * not on this interface and lives only on the concrete backend.\n */\nexport interface InteractiveBackend {\n readonly camera: Camera;\n readonly culling: CullingManager;\n readonly hitTester: HitTester;\n readonly events: TypedEventEmitter<RendererEventMap>;\n /** Forward map → render-space transform from the active style (identity for flat styles). */\n readonly coordinateTransform: CoordFn;\n /**\n * Apply a target-agnostic {@link Style} to the live scene. The style\n * transforms world-space shapes (paint, geometry, projection) before they\n * are rasterized; the backend re-renders the current scene to reflect it.\n */\n setStyle(style: Style): void;\n updateBackground(): void;\n refresh(): void;\n /** Capture the current camera as a canvas with background fill. */\n exportCanvas(options?: { pixelRatio?: number }): ExportCanvas | undefined;\n addSceneOverlay(id: string, overlay: SceneOverlay): void;\n removeSceneOverlay(id: string): void;\n getSceneOverlays(): Iterable<SceneOverlay>;\n /**\n * Snapshot of inter-room exits as drawn in the last `buildScene` call\n * (polyline segments, arrows, bounds, dashes — exactly what the user\n * sees). Empty before the first draw. The list already reflects the\n * renderer's suppression rules, so anything drawn appears here and\n * anything not drawn does not.\n */\n getDrawnExits(): readonly DrawnExitEntry[];\n /** Companion to {@link getDrawnExits} for custom-line special exits. */\n getDrawnSpecialExits(): readonly DrawnSpecialExitEntry[];\n /** Companion to {@link getDrawnExits} for one-way stub indicators. */\n getDrawnStubs(): readonly DrawnStubEntry[];\n destroy(): void;\n}\n\n/**\n * Unified map renderer facade.\n *\n * Public surface is intentionally narrow:\n * - **State mutations** (`drawArea`, `setPosition`, `renderHighlight`, …) emit\n * events through `MapState` that the interactive backend reacts to.\n * - **{@link setStyle}** applies a target-agnostic visual transformation to the\n * interactive canvas and every exporter.\n * - **{@link export}** runs an {@link Exporter} plug-in and returns its output\n * (SVG string, PNG data URL, canvas, PDF bytes, …). New formats are added by\n * shipping new `Exporter<T>` implementations — no new methods on this class.\n * - **{@link addSceneOverlay}** is target-agnostic and appears in every output.\n * - **{@link addLiveEffect}** registers interactive-only animated effects (Konva\n * canvas only; skipped by exporters).\n */\nexport class MapRenderer {\n readonly state: MapState;\n readonly backend: InteractiveBackend;\n private currentStyle: Style = identityStyle;\n\n get settings(): Settings {\n return this.state.settings;\n }\n\n /** Camera owned by the active interactive backend. */\n get camera(): Camera {\n return this.backend.camera;\n }\n\n /** Culling manager owned by the active interactive backend. */\n get culling(): CullingManager {\n return this.backend.culling;\n }\n\n /** Hit-test index owned by the active interactive backend. */\n get hitTester(): HitTester {\n return this.backend.hitTester;\n }\n\n /** Renderer event emitter (room click, area exit click, zoom change, …). */\n get events(): TypedEventEmitter<RendererEventMap> {\n return this.backend.events;\n }\n\n /**\n * @param mapReader Map data source.\n * @param settings Renderer settings. Defaults to `createSettings()`.\n * @param container DOM element for interactive rendering. Omit for headless.\n * @param backendFactory Optional factory that receives the `MapState` and returns\n * a custom `InteractiveBackend`. When omitted, a `KonvaRenderBackend` is created.\n */\n constructor(\n mapReader: IMapReader,\n settings?: Settings,\n container?: HTMLDivElement,\n backendFactory?: (state: MapState) => InteractiveBackend,\n ) {\n const resolvedSettings = settings ?? createSettings();\n this.state = new MapState(mapReader, resolvedSettings);\n this.backend = backendFactory\n ? backendFactory(this.state)\n : new KonvaRenderBackend(this.state, container);\n }\n\n destroy() {\n this.backend.destroy();\n }\n\n // --- State mutations (emit events → backend reacts) ---\n\n drawArea(id: number, zIndex: number) {\n this.state.setArea(id, zIndex);\n }\n\n getCurrentArea(): IArea | undefined {\n return this.state.currentAreaInstance;\n }\n\n setPosition(roomId: number, center: boolean = true) {\n this.state.setPosition(roomId, center);\n }\n\n updatePositionMarker(roomId: number) {\n this.state.updatePositionMarker(roomId);\n }\n\n clearPosition() {\n this.state.clearPosition();\n }\n\n centerOn(roomId: number, instant?: boolean) {\n this.state.setCenterRoom(roomId, instant);\n }\n\n renderHighlight(roomId: number, color: string) {\n this.state.addHighlight(roomId, color);\n }\n\n removeHighlight(roomId: number) {\n this.state.removeHighlight(roomId);\n }\n\n hasHighlight(roomId: number): boolean {\n return this.state.hasHighlight(roomId);\n }\n\n clearHighlights() {\n this.state.clearHighlights();\n }\n\n renderPath(locations: number[], color?: string) {\n this.state.addPath(locations, color);\n }\n\n clearPaths() {\n this.state.clearPaths();\n }\n\n refreshCurrentRoomOverlay() {\n this.state.refreshPosition();\n }\n\n // --- Visibility lens ---\n\n /**\n * Replace the active visibility lens. Lenses filter which rooms (and how\n * partially-visible exits) the renderer draws — exploration / fog-of-war\n * is one example, guild/quest scope overlays are others. Compose multiple\n * concerns with `composeLenses(...)`.\n *\n * Triggers an immediate rebuild. For mutations on a stateful lens (e.g.\n * `ExplorationLens.addVisited`), call {@link refresh} explicitly.\n */\n setLens(lens: RoomLens) {\n this.state.setLens(lens);\n }\n\n getLens(): RoomLens {\n return this.state.lens;\n }\n\n // --- Style ---\n\n /**\n * Apply a {@link Style} to the interactive canvas and every export path.\n * The backend rebuilds the current scene under the new style — no explicit\n * `refresh()` is needed.\n *\n * ```ts\n * import {compose, Parchment, Sketchy} from 'mudlet-map-renderer';\n * renderer.setStyle(compose(Parchment, Sketchy({jitter: 0.012, color: '#4a3728'})));\n * ```\n *\n * Pass `identityStyle` (or call {@link clearStyle}) to remove the current style.\n */\n setStyle(style: Style) {\n this.currentStyle = style;\n this.backend.setStyle(style);\n }\n\n /** Equivalent to `setStyle(identityStyle)`. */\n clearStyle() {\n this.setStyle(identityStyle);\n }\n\n /** Returns the style currently applied (defaults to identity). */\n getStyle(): Style {\n return this.currentStyle;\n }\n\n updateBackground() {\n this.backend.updateBackground();\n }\n\n refresh() {\n this.backend.updateBackground();\n this.backend.refresh();\n }\n\n // --- Overlays ---\n\n /** Target-agnostic overlay; appears in every output including exporters. */\n addSceneOverlay(id: string, overlay: SceneOverlay) {\n this.backend.addSceneOverlay(id, overlay);\n }\n\n removeSceneOverlay(id: string) {\n this.backend.removeSceneOverlay(id);\n }\n\n /**\n * Register an interactive-only animated effect. No-ops when running with a\n * non-Konva backend. Does not appear in SVG/PNG exports — use\n * {@link addSceneOverlay} for overlays that must appear in exports.\n *\n * ```ts\n * renderer.addLiveEffect('rain', new RainEffect());\n * ```\n */\n addLiveEffect(id: string, effect: LiveEffect) {\n if (this.backend instanceof KonvaRenderBackend) {\n this.backend.addLiveEffect(id, effect);\n }\n }\n\n removeLiveEffect(id: string) {\n if (this.backend instanceof KonvaRenderBackend) {\n this.backend.removeLiveEffect(id);\n }\n }\n\n // --- Hit testing ---\n\n /**\n * Hit-test a world-space map point against the current scene.\n *\n * Returns the nearest pickable shape at `(worldX, worldY)`, or `null`\n * when no hittable shape is within range. Coordinates must be in the\n * same flat map space as room positions — the method applies the active\n * style's world→scene projection internally, so callers never need to\n * think about Isometric mode.\n *\n * ```ts\n * const hit = renderer.hitTest(room.x, room.y);\n * if (hit?.kind === 'room') console.log('room', hit.id);\n * ```\n */\n hitTest(worldX: number, worldY: number): HitResult | null {\n const rendered = this.backend.coordinateTransform(worldX, worldY);\n return this.backend.hitTester.pick(rendered.x, rendered.y);\n }\n\n // --- Drawn geometry (hit-testing integration) ---\n\n /**\n * Polyline / arrow / bounds data for every inter-room exit the renderer\n * drew on the last scene build. Intended for tools (e.g. editors) that\n * need to hit-test against exactly what the user sees, including dash\n * patterns, one-way arrows, and the renderer's suppression rules.\n */\n getDrawnExits(): readonly DrawnExitEntry[] {\n return this.backend.getDrawnExits();\n }\n\n /** Companion to {@link getDrawnExits} for custom-line special exits. */\n getDrawnSpecialExits(): readonly DrawnSpecialExitEntry[] {\n return this.backend.getDrawnSpecialExits();\n }\n\n /**\n * Polyline data for every one-way stub indicator the renderer drew\n * (one entry per direction in `room.stubs`). Coordinates are in\n * render space and match what's on screen.\n */\n getDrawnStubs(): readonly DrawnStubEntry[] {\n return this.backend.getDrawnStubs();\n }\n\n // --- Export ---\n\n /**\n * Run an {@link Exporter} against the current scene and return its output.\n *\n * ```ts\n * const svg = renderer.export(new SvgExporter({ padding: 5 }));\n * const url = renderer.export(new PngExporter({ pixelRatio: 2 }));\n * const blob = await renderer.export(new PngBlobExporter());\n * const canvas = renderer.export(new CanvasExporter({ width, height }));\n * ```\n */\n export<T>(exporter: Exporter<T>): T {\n const context: ExportContext = {\n state: this.state,\n backend: this.backend,\n style: this.currentStyle,\n sceneOverlays: this.backend.getSceneOverlays(),\n };\n return exporter.render(context);\n }\n\n // --- Camera & interaction ---\n\n on<K extends keyof RendererEventMap>(event: K, handler: (detail: RendererEventMap[K]) => void): void {\n this.backend.events.on(event, handler);\n }\n\n off<K extends keyof RendererEventMap>(event: K, handler: (detail: RendererEventMap[K]) => void): void {\n this.backend.events.off(event, handler);\n }\n\n setZoom(zoom: number): boolean {\n return this.backend.camera.setZoom(zoom);\n }\n\n zoomToCenter(zoom: number): boolean {\n return this.backend.camera.zoomToCenter(zoom);\n }\n\n getZoom(): number {\n return this.backend.camera.zoom;\n }\n\n getViewportBounds(): ViewportBounds {\n return this.backend.camera.getViewportBounds();\n }\n\n getAreaBounds(): ViewportBounds | null {\n if (!this.state.currentAreaInstance || this.state.currentZIndex === undefined) return null;\n const plane = this.state.currentAreaInstance.getPlane(this.state.currentZIndex);\n if (!plane) return null;\n const b = this.state.getEffectiveBounds(this.state.currentAreaInstance, plane);\n const areaName = this.state.settings.areaName ? this.state.currentAreaInstance.getAreaName() : null;\n // Mirror the offsets used by computeExportBounds so that getAreaBounds()\n // covers the same region the export/preview image will render.\n const nameRight = areaName ? b.minX - 3.5 + areaName.length * 2.5 * 0.6 : b.maxX;\n const raw: ViewportBounds = {\n minX: areaName ? b.minX - 3.5 : b.minX,\n maxX: Math.max(b.maxX, nameRight),\n minY: areaName ? b.minY - 7 : b.minY,\n maxY: b.maxY,\n };\n const fn = this.backend.coordinateTransform;\n const c1 = fn(raw.minX, raw.minY);\n const c2 = fn(raw.maxX, raw.minY);\n const c3 = fn(raw.maxX, raw.maxY);\n const c4 = fn(raw.minX, raw.maxY);\n return {\n minX: Math.min(c1.x, c2.x, c3.x, c4.x),\n maxX: Math.max(c1.x, c2.x, c3.x, c4.x),\n minY: Math.min(c1.y, c2.y, c3.y, c4.y),\n maxY: Math.max(c1.y, c2.y, c3.y, c4.y),\n };\n }\n\n fitArea(insets?: { top?: number; right?: number; bottom?: number; left?: number }) {\n const bounds = this.getAreaBounds();\n if (!bounds) return;\n this.backend.camera.fitToMapBounds(bounds.minX, bounds.maxX, bounds.minY, bounds.maxY, insets);\n }\n\n get centerOnResize(): boolean {\n return this.backend.camera.centerOnResize;\n }\n\n set centerOnResize(value: boolean) {\n this.backend.camera.centerOnResize = value;\n }\n\n get minZoom(): number {\n return this.backend.camera.minZoom;\n }\n\n set minZoom(value: number) {\n this.backend.camera.minZoom = value;\n }\n\n setCullingMode(mode: CullingMode) {\n this.state.settings.cullingMode = mode;\n this.state.settings.cullingEnabled = mode !== \"none\";\n this.backend.culling.scheduleCulling();\n }\n\n getCullingMode(): CullingMode {\n return this.state.settings.cullingMode;\n }\n}\n","/**\n * {@link buildDrawCommands} — single source of truth for camera transform math.\n *\n * Walks a {@link SceneIR}-shaped tree (already passed through styles) and\n * produces a flat {@link DrawCommand} list per logical layer. Coordinates\n * arrive in world space; commands leave in render space (camera scale and\n * pan applied), with stroke widths and corner radii already scaled.\n *\n * `noScale` group shapes are emitted as `pushTransform`/`popTransform`\n * pairs: the group origin is placed at its world-mapped screen position,\n * but the children render at fixed pixel size regardless of zoom — the\n * legacy `RecordingLayerNode.canScaleNoScalingGroups` invariant.\n */\n\nimport type {\n DrawCommand,\n DrawCommandBatch,\n LineCommand,\n PolygonCommand,\n RectCommand,\n TextCommand,\n} from \"./DrawCommand\";\nimport {\n transformFill,\n type GroupShape,\n type LayerId,\n type Shape,\n} from \"../scene/Shape\";\n\n/** Camera state needed to project world coordinates into render space. */\nexport interface CameraTransform {\n /** Pixels per world unit. */\n scale: number;\n /** Render-space origin in pixels. */\n offsetX: number;\n offsetY: number;\n}\n\nconst DEFAULT_LAYER: LayerId = \"room\";\n\n/**\n * Build per-layer {@link DrawCommandBatch}es from a list of world-space shapes\n * and an active camera transform.\n *\n * Layer order in the result follows first-appearance order; renderers iterate\n * the array as-is (z-order is the caller's responsibility — typically scene\n * layout uses the LayerId taxonomy to pre-sort).\n */\nexport function buildDrawCommands(\n shapes: Shape[],\n camera: CameraTransform,\n): DrawCommandBatch[] {\n const batches = new Map<LayerId, DrawCommand[]>();\n\n function commandsFor(layer: LayerId): DrawCommand[] {\n let bucket = batches.get(layer);\n if (!bucket) {\n bucket = [];\n batches.set(layer, bucket);\n }\n return bucket;\n }\n\n function emitShape(\n shape: Shape,\n worldX: number,\n worldY: number,\n scale: number,\n offsetX: number,\n offsetY: number,\n layerOverride: LayerId | undefined,\n ) {\n const layer = shape.layer ?? layerOverride ?? DEFAULT_LAYER;\n\n switch (shape.type) {\n case \"rect\": {\n const x = (worldX + shape.x) * scale + offsetX;\n const y = (worldY + shape.y) * scale + offsetY;\n const w = shape.width * scale;\n const h = shape.height * scale;\n const cmd: RectCommand = {\n type: \"rect\",\n x, y, w, h,\n fill: transformFill(shape.paint.fill, worldX, worldY, scale, offsetX, offsetY),\n stroke: shape.paint.stroke,\n sw: (shape.paint.strokeWidth ?? 0) * scale,\n cr: (shape.cornerRadius ?? 0) * scale,\n dash: scaleDash(shape.paint.dash, scale, shape.paint.dashEnabled),\n };\n commandsFor(layer).push(cmd);\n return;\n }\n case \"circle\": {\n commandsFor(layer).push({\n type: \"circle\",\n cx: (worldX + shape.cx) * scale + offsetX,\n cy: (worldY + shape.cy) * scale + offsetY,\n r: shape.radius * scale,\n fill: transformFill(shape.paint.fill, worldX, worldY, scale, offsetX, offsetY),\n stroke: shape.paint.stroke,\n sw: (shape.paint.strokeWidth ?? 0) * scale,\n dash: scaleDash(shape.paint.dash, scale, shape.paint.dashEnabled),\n });\n return;\n }\n case \"line\": {\n const cmd: LineCommand = {\n type: \"line\",\n points: scalePoints(shape.points, worldX, worldY, scale, offsetX, offsetY),\n stroke: shape.paint.stroke,\n sw: (shape.paint.strokeWidth ?? 0) * scale,\n dash: scaleDash(shape.paint.dash, scale, shape.paint.dashEnabled),\n lineCap: shape.lineCap,\n lineJoin: shape.lineJoin,\n alpha: shape.paint.alpha,\n };\n commandsFor(layer).push(cmd);\n return;\n }\n case \"polygon\": {\n const cmd: PolygonCommand = {\n type: \"polygon\",\n vertices: scalePoints(shape.vertices, worldX, worldY, scale, offsetX, offsetY),\n fill: transformFill(shape.paint.fill, worldX, worldY, scale, offsetX, offsetY),\n stroke: shape.paint.stroke,\n sw: (shape.paint.strokeWidth ?? 0) * scale,\n };\n commandsFor(layer).push(cmd);\n return;\n }\n case \"text\": {\n const t = scaleTransform(shape.transform, worldX, worldY, scale, offsetX, offsetY);\n // When a transform is set the matrix carries position; the\n // command's x/y is zero so renderers don't double-translate.\n const x = t ? 0 : (worldX + shape.x) * scale + offsetX;\n const y = t ? 0 : (worldY + shape.y) * scale + offsetY;\n const cmd: TextCommand = {\n type: \"text\",\n x, y,\n text: shape.text,\n fontSize: shape.fontSize * scale,\n fontFamily: shape.fontFamily ?? \"sans-serif\",\n fontStyle: shape.fontStyle ?? \"normal\",\n fill: shape.fill ?? \"black\",\n stroke: shape.stroke,\n sw: (shape.strokeWidth ?? 0) * scale,\n align: shape.align ?? \"left\",\n vAlign: shape.verticalAlign ?? \"top\",\n w: (shape.width ?? 0) * scale,\n h: (shape.height ?? 0) * scale,\n baselineRatio: shape.baselineRatio,\n konvaCorrectionRatio: shape.konvaCorrectionRatio,\n transform: t,\n };\n commandsFor(layer).push(cmd);\n return;\n }\n case \"image\": {\n const t = scaleTransform(shape.transform, worldX, worldY, scale, offsetX, offsetY);\n const x = t ? 0 : (worldX + shape.x) * scale + offsetX;\n const y = t ? 0 : (worldY + shape.y) * scale + offsetY;\n commandsFor(layer).push({\n type: \"image\",\n x, y,\n w: shape.width * scale,\n h: shape.height * scale,\n src: shape.src,\n transform: t,\n });\n return;\n }\n case \"group\":\n emitGroup(shape, worldX, worldY, scale, offsetX, offsetY, layer);\n return;\n }\n }\n\n function emitGroup(\n group: GroupShape,\n worldX: number,\n worldY: number,\n scale: number,\n offsetX: number,\n offsetY: number,\n layerOverride: LayerId | undefined,\n ) {\n const childLayer = group.layer ?? layerOverride;\n\n if (group.noScale) {\n // Anchor the group at its scaled world position, then render\n // children in pixel space (scale = 1, offset = 0).\n const px = (worldX + group.x) * scale + offsetX;\n const py = (worldY + group.y) * scale + offsetY;\n\n const pushLayer = childLayer ?? DEFAULT_LAYER;\n const bucket = commandsFor(pushLayer);\n bucket.push({type: \"pushTransform\", matrix: [1, 0, 0, 1, px, py]});\n for (const child of group.children) {\n emitShape(child, 0, 0, 1, 0, 0, childLayer);\n }\n // popTransform must land in the same bucket as its push so\n // renderers can replay them in order. Children whose `layer`\n // diverts them elsewhere are pulled out of this push/pop scope\n // — that's the intended behaviour, since cross-layer children\n // are independent draws (e.g. a noScale label group has a\n // pixmap on the link layer plus a rect on the top layer).\n bucket.push({type: \"popTransform\"});\n return;\n }\n\n const nextX = worldX + group.x;\n const nextY = worldY + group.y;\n for (const child of group.children) {\n emitShape(child, nextX, nextY, scale, offsetX, offsetY, childLayer);\n }\n }\n\n for (const shape of shapes) {\n emitShape(shape, 0, 0, camera.scale, camera.offsetX, camera.offsetY, undefined);\n }\n\n const out: DrawCommandBatch[] = [];\n for (const [layer, commands] of batches) {\n out.push({layer, commands});\n }\n return out;\n}\n\nfunction scalePoints(\n pts: number[],\n worldX: number,\n worldY: number,\n scale: number,\n offsetX: number,\n offsetY: number,\n): number[] {\n const out = new Array<number>(pts.length);\n for (let i = 0; i < pts.length; i += 2) {\n out[i] = (worldX + pts[i]) * scale + offsetX;\n out[i + 1] = (worldY + pts[i + 1]) * scale + offsetY;\n }\n return out;\n}\n\n/** Scale dash lengths from world units to render units. `dashEnabled === false` blanks the dash. */\nfunction scaleDash(\n dash: number[] | undefined,\n scale: number,\n dashEnabled: boolean | undefined,\n): number[] | undefined {\n if (dashEnabled === false) return undefined;\n if (!dash || dash.length === 0) return dash;\n const out = new Array<number>(dash.length);\n for (let i = 0; i < dash.length; i++) out[i] = dash[i] * scale;\n return out;\n}\n\n/**\n * Apply the camera's scale + offset to a 2D affine matrix originally\n * authored in world space. Result composes camera ∘ shape on world points.\n */\nfunction scaleTransform(\n matrix: [number, number, number, number, number, number] | undefined,\n worldX: number,\n worldY: number,\n scale: number,\n offsetX: number,\n offsetY: number,\n): [number, number, number, number, number, number] | undefined {\n if (!matrix) return undefined;\n const [a, b, c, d, e, f] = matrix;\n // Camera: [scale, 0, 0, scale, worldX*scale + offsetX, worldY*scale + offsetY]\n // Composed = Camera * Shape (column-major affine).\n return [\n a * scale,\n b * scale,\n c * scale,\n d * scale,\n e * scale + worldX * scale + offsetX,\n f * scale + worldY * scale + offsetY,\n ];\n}\n","/**\n * SvgRenderer — flushes {@link DrawCommandBatch}es to a list of SVG element\n * strings.\n *\n * Coordinates in commands are already in render space (camera transform\n * applied), so primitives map directly to SVG attributes. Stack commands\n * (`pushTransform`/`popTransform`, `pushClip`/`popClip`) wrap the elements\n * emitted between them in `<g transform=…>` / `<g clip-path=…>` groups.\n */\n\nimport type {\n DrawCommand,\n DrawCommandBatch,\n PrimitiveDrawCommand,\n} from \"../draw/DrawCommand\";\nimport type {FillStyle, LinearGradient, RadialGradient} from \"../scene/Shape\";\n\nfunction escapeXml(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n .replace(/\"/g, \""\").replace(/'/g, \"'\");\n}\n\nfunction attr(name: string, value: string | number | undefined): string {\n if (value === undefined) return \"\";\n return ` ${name}=\"${typeof value === \"string\" ? escapeXml(value) : value}\"`;\n}\n\nfunction dashAttr(dash: number[] | undefined): string {\n if (!dash || dash.length === 0) return \"\";\n return ` stroke-dasharray=\"${dash.join(\" \")}\"`;\n}\n\n/**\n * Monotonically increasing id source for gradient `<linearGradient>` /\n * `<radialGradient>` elements emitted by {@link svgFromBatches}. The\n * exporter can call `svgFromBatches` many times per document; using a\n * module-level counter keeps ids unique across calls without forcing\n * callers to manage state.\n */\nlet _gradId = 0;\nfunction nextGradientId(): string {\n return `mmr-grad-${++_gradId}`;\n}\n\nfunction emitLinearGradient(id: string, g: LinearGradient): string {\n const stops = g.stops\n .map(s => `<stop offset=\"${s.offset}\" stop-color=\"${escapeXml(s.color)}\"/>`)\n .join(\"\");\n return `<defs><linearGradient id=\"${id}\" gradientUnits=\"userSpaceOnUse\" x1=\"${g.x0}\" y1=\"${g.y0}\" x2=\"${g.x1}\" y2=\"${g.y1}\">${stops}</linearGradient></defs>`;\n}\n\nfunction emitRadialGradient(id: string, g: RadialGradient): string {\n const stops = g.stops\n .map(s => `<stop offset=\"${s.offset}\" stop-color=\"${escapeXml(s.color)}\"/>`)\n .join(\"\");\n const focalAttrs =\n (g.fx !== undefined ? ` fx=\"${g.fx}\"` : \"\") +\n (g.fy !== undefined ? ` fy=\"${g.fy}\"` : \"\") +\n (g.fr !== undefined ? ` fr=\"${g.fr}\"` : \"\");\n return `<defs><radialGradient id=\"${id}\" gradientUnits=\"userSpaceOnUse\" cx=\"${g.cx}\" cy=\"${g.cy}\" r=\"${g.r}\"${focalAttrs}>${stops}</radialGradient></defs>`;\n}\n\n/**\n * Resolve a {@link FillStyle} for an SVG `fill=` attribute. Strings pass\n * through. Gradients allocate an id, emit a `<defs>` block into `out`, and\n * return the `url(#id)` reference for the caller to plug into the attribute.\n */\nfunction resolveSvgFill(fill: FillStyle | undefined, out: string[]): string | undefined {\n if (fill === undefined) return undefined;\n if (typeof fill === \"string\") return fill;\n const id = nextGradientId();\n out.push(fill.type === \"linear\" ? emitLinearGradient(id, fill) : emitRadialGradient(id, fill));\n return `url(#${id})`;\n}\n\n/**\n * Convert one or more {@link DrawCommandBatch}es into SVG element strings, in\n * the order they appear. Caller wraps the result in `<svg viewBox=…>` /\n * `</svg>` and inserts a background rect.\n */\nexport function svgFromBatches(batches: DrawCommandBatch[]): string[] {\n const lines: string[] = [];\n for (const batch of batches) {\n emitCommands(batch.commands, lines);\n }\n return lines;\n}\n\nfunction emitCommands(commands: DrawCommand[], out: string[]): void {\n // Walk commands sequentially. Stack commands open/close `<g>` wrappers,\n // tracked via a stack of insertion indices so the closing `</g>` is\n // appended in the correct position.\n const groupStack: string[] = [];\n\n for (const cmd of commands) {\n switch (cmd.type) {\n case \"pushTransform\": {\n const [a, b, c, d, e, f] = cmd.matrix;\n out.push(`<g transform=\"matrix(${a},${b},${c},${d},${e},${f})\">`);\n groupStack.push(\"</g>\");\n break;\n }\n case \"pushClip\": {\n // No clipPath element emitted yet — most Mudlet exporters don't\n // need it. Fall back to a transparent group so the corresponding\n // pop balances correctly.\n out.push(`<g>`);\n groupStack.push(\"</g>\");\n break;\n }\n case \"popTransform\":\n case \"popClip\": {\n const close = groupStack.pop();\n if (close) out.push(close);\n break;\n }\n default: {\n emitPrimitive(cmd, out);\n break;\n }\n }\n }\n\n while (groupStack.length > 0) {\n out.push(groupStack.pop()!);\n }\n}\n\nfunction emitPrimitive(cmd: PrimitiveDrawCommand, out: string[]): void {\n switch (cmd.type) {\n case \"rect\": {\n const fill = resolveSvgFill(cmd.fill, out) ?? \"none\";\n const corner = cmd.cr > 0 ? `${attr(\"rx\", cmd.cr)}${attr(\"ry\", cmd.cr)}` : \"\";\n out.push(`<rect${attr(\"x\", cmd.x)}${attr(\"y\", cmd.y)}${attr(\"width\", cmd.w)}${attr(\"height\", cmd.h)}${attr(\"fill\", fill)}${attr(\"stroke\", cmd.stroke)}${cmd.sw ? attr(\"stroke-width\", cmd.sw) : \"\"}${corner}${dashAttr(cmd.dash)}/>`);\n return;\n }\n case \"circle\": {\n const fill = resolveSvgFill(cmd.fill, out) ?? \"none\";\n out.push(`<circle${attr(\"cx\", cmd.cx)}${attr(\"cy\", cmd.cy)}${attr(\"r\", cmd.r)}${attr(\"fill\", fill)}${attr(\"stroke\", cmd.stroke)}${cmd.sw ? attr(\"stroke-width\", cmd.sw) : \"\"}${dashAttr(cmd.dash)}/>`);\n return;\n }\n case \"line\": {\n const points = cmd.points;\n if (points.length < 2) return;\n const svgPoints: string[] = [];\n for (let i = 0; i < points.length; i += 2) {\n svgPoints.push(`${points[i]},${points[i + 1]}`);\n }\n out.push(`<polyline points=\"${svgPoints.join(\" \")}\"${attr(\"stroke\", cmd.stroke)}${cmd.sw ? attr(\"stroke-width\", cmd.sw) : \"\"}${dashAttr(cmd.dash)}${attr(\"stroke-linecap\", cmd.lineCap)}${attr(\"stroke-linejoin\", cmd.lineJoin)}${attr(\"opacity\", cmd.alpha)} fill=\"none\"/>`);\n return;\n }\n case \"polygon\": {\n const verts = cmd.vertices;\n if (verts.length < 4) return;\n const svgPoints: string[] = [];\n for (let i = 0; i < verts.length; i += 2) {\n svgPoints.push(`${verts[i]},${verts[i + 1]}`);\n }\n const fill = resolveSvgFill(cmd.fill, out);\n out.push(`<polygon points=\"${svgPoints.join(\" \")}\"${attr(\"fill\", fill)}${attr(\"stroke\", cmd.stroke)}${cmd.sw ? attr(\"stroke-width\", cmd.sw) : \"\"}/>`);\n return;\n }\n case \"text\": {\n // SVG text positioning matches the legacy SvgBackend: alignment\n // collapses width into an anchor (left/middle/end), vertical\n // centering uses either an explicit baseline ratio or the SVG\n // `dominant-baseline=\"central\"` shortcut.\n let x = cmd.x;\n let y = cmd.y;\n let anchor = \"start\";\n let baseline = \"auto\";\n\n if (cmd.w > 0) {\n if (cmd.align === \"center\") {\n x = cmd.x + cmd.w / 2;\n anchor = \"middle\";\n } else if (cmd.align === \"right\") {\n x = cmd.x + cmd.w;\n anchor = \"end\";\n }\n }\n if (cmd.h > 0 && cmd.vAlign === \"middle\") {\n if (cmd.baselineRatio !== undefined) {\n y = cmd.y + cmd.h / 2 + cmd.baselineRatio * cmd.fontSize;\n } else {\n y = cmd.y + cmd.h / 2;\n baseline = \"central\";\n }\n }\n\n const transformAttr = cmd.transform\n ? ` transform=\"matrix(${cmd.transform.join(\",\")})\"`\n : \"\";\n const weight = cmd.fontStyle === \"bold\" ? ` font-weight=\"bold\"` : \"\";\n const strokeAttr = cmd.stroke && cmd.sw > 0\n ? ` stroke=\"${cmd.stroke}\" stroke-width=\"${cmd.sw}\" paint-order=\"stroke fill\"`\n : \"\";\n out.push(`<text${attr(\"x\", x)}${attr(\"y\", y)}${attr(\"font-size\", cmd.fontSize)}${cmd.fontFamily ? attr(\"font-family\", cmd.fontFamily) : \"\"}${weight}${attr(\"fill\", cmd.fill)}${strokeAttr} text-anchor=\"${anchor}\" dominant-baseline=\"${baseline}\"${transformAttr}>${escapeXml(cmd.text)}</text>`);\n return;\n }\n case \"image\": {\n if (cmd.transform) {\n const [a, b, c, d, e, f] = cmd.transform;\n out.push(`<image${attr(\"width\", cmd.w)}${attr(\"height\", cmd.h)} href=\"${escapeXml(cmd.src)}\" transform=\"matrix(${a},${b},${c},${d},${e},${f})\"/>`);\n return;\n }\n out.push(`<image${attr(\"x\", cmd.x)}${attr(\"y\", cmd.y)}${attr(\"width\", cmd.w)}${attr(\"height\", cmd.h)} href=\"${escapeXml(cmd.src)}\"/>`);\n return;\n }\n }\n}\n","/**\n * CanvasRenderer — replays {@link DrawCommandBatch}es onto a Canvas2D\n * context.\n *\n * Coordinates in commands are already in render space (camera transform\n * applied), so the renderer issues calls directly without applying any extra\n * scale or translation. Stack commands push/pop matching `ctx.save()` /\n * `ctx.restore()` pairs and apply the transform/clip on the way in.\n *\n * Used by {@link CanvasExporter} (and {@link PngBytesExporter}) so headless\n * exports rasterize through the shared {@link buildDrawCommands} pipeline\n * instead of grabbing the live Konva stage.\n */\n\nimport type {\n DrawCommand,\n DrawCommandBatch,\n PrimitiveDrawCommand,\n} from \"../draw/DrawCommand\";\nimport {resolveFill} from \"./canvasGradient\";\n\n/**\n * Image loader hook. Canvas2D's `drawImage` needs an `HTMLImageElement` (or\n * compatible). Browsers create one via `new Image()`; Node-canvas provides\n * its own `Image` constructor. The default tries both — callers can override\n * for tests or unusual targets.\n */\nexport type ImageFactory = (src: string) => unknown;\n\nconst defaultImageFactory: ImageFactory = (src) => {\n if (typeof Image !== \"undefined\") {\n const img = new Image();\n img.src = src;\n return img;\n }\n return null;\n};\n\nexport interface CanvasRenderOptions {\n imageFactory?: ImageFactory;\n}\n\n/**\n * Replay every batch onto `ctx`, in order. Each batch's commands are flushed\n * sequentially; transform / clip stacks are independent per batch so a stray\n * push in one layer cannot leak into the next.\n */\nexport function renderToCanvas(\n ctx: CanvasRenderingContext2D,\n batches: DrawCommandBatch[],\n options: CanvasRenderOptions = {},\n): void {\n const imageFactory = options.imageFactory ?? defaultImageFactory;\n for (const batch of batches) {\n replayCommands(ctx, batch.commands, imageFactory);\n }\n}\n\nfunction replayCommands(\n ctx: CanvasRenderingContext2D,\n commands: DrawCommand[],\n imageFactory: ImageFactory,\n): void {\n let stackDepth = 0;\n\n for (const cmd of commands) {\n switch (cmd.type) {\n case \"pushTransform\": {\n ctx.save();\n stackDepth++;\n ctx.transform(...cmd.matrix);\n break;\n }\n case \"pushClip\": {\n ctx.save();\n stackDepth++;\n ctx.beginPath();\n ctx.rect(cmd.x, cmd.y, cmd.w, cmd.h);\n ctx.clip();\n break;\n }\n case \"popTransform\":\n case \"popClip\": {\n if (stackDepth > 0) {\n ctx.restore();\n stackDepth--;\n }\n break;\n }\n default:\n replayPrimitive(ctx, cmd, imageFactory);\n break;\n }\n }\n\n while (stackDepth > 0) {\n ctx.restore();\n stackDepth--;\n }\n}\n\nfunction replayPrimitive(\n ctx: CanvasRenderingContext2D,\n cmd: PrimitiveDrawCommand,\n imageFactory: ImageFactory,\n): void {\n switch (cmd.type) {\n case \"rect\": {\n ctx.beginPath();\n if (cmd.cr > 0 && typeof ctx.roundRect === \"function\") {\n ctx.roundRect(cmd.x, cmd.y, cmd.w, cmd.h, cmd.cr);\n } else {\n ctx.rect(cmd.x, cmd.y, cmd.w, cmd.h);\n }\n if (cmd.fill) {\n ctx.fillStyle = resolveFill(ctx, cmd.fill);\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n ctx.setLineDash(cmd.dash ?? []);\n ctx.stroke();\n }\n break;\n }\n case \"circle\": {\n ctx.beginPath();\n ctx.arc(cmd.cx, cmd.cy, cmd.r, 0, Math.PI * 2);\n if (cmd.fill) {\n ctx.fillStyle = resolveFill(ctx, cmd.fill);\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n ctx.setLineDash(cmd.dash ?? []);\n ctx.stroke();\n }\n break;\n }\n case \"line\": {\n if (cmd.points.length < 4) break;\n const savedAlpha = ctx.globalAlpha;\n if (cmd.alpha !== undefined) ctx.globalAlpha = cmd.alpha;\n ctx.beginPath();\n ctx.moveTo(cmd.points[0], cmd.points[1]);\n for (let i = 2; i < cmd.points.length; i += 2) {\n ctx.lineTo(cmd.points[i], cmd.points[i + 1]);\n }\n if (cmd.stroke) ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n ctx.setLineDash(cmd.dash ?? []);\n if (cmd.lineCap) ctx.lineCap = cmd.lineCap;\n if (cmd.lineJoin) ctx.lineJoin = cmd.lineJoin;\n ctx.stroke();\n if (cmd.alpha !== undefined) ctx.globalAlpha = savedAlpha;\n break;\n }\n case \"polygon\": {\n if (cmd.vertices.length < 4) break;\n ctx.beginPath();\n ctx.moveTo(cmd.vertices[0], cmd.vertices[1]);\n for (let i = 2; i < cmd.vertices.length; i += 2) {\n ctx.lineTo(cmd.vertices[i], cmd.vertices[i + 1]);\n }\n ctx.closePath();\n if (cmd.fill) {\n ctx.fillStyle = resolveFill(ctx, cmd.fill);\n ctx.fill();\n }\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw;\n ctx.setLineDash([]);\n ctx.stroke();\n }\n break;\n }\n case \"text\": {\n // Sub-pixel font sizes break Canvas2D text metrics on some\n // engines (notably node-canvas). Render at TEXT_SCALE × the\n // requested size and counter-scale the matrix so output stays\n // pixel-correct.\n const TEXT_SCALE = 100;\n const scaledSize = cmd.fontSize * TEXT_SCALE;\n const font = `${cmd.fontStyle} ${scaledSize}px ${cmd.fontFamily}`;\n ctx.save();\n ctx.font = font;\n ctx.fillStyle = cmd.fill;\n if (cmd.stroke && cmd.sw > 0) {\n ctx.strokeStyle = cmd.stroke;\n ctx.lineWidth = cmd.sw * TEXT_SCALE;\n ctx.lineJoin = \"round\";\n }\n const hasBaselineRatio = cmd.baselineRatio !== undefined;\n if (cmd.transform) {\n ctx.transform(...cmd.transform);\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n ctx.textAlign = \"center\";\n if (hasBaselineRatio) {\n ctx.textBaseline = \"alphabetic\";\n const by = (cmd.h / 2 + cmd.baselineRatio! * cmd.fontSize) * TEXT_SCALE;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, cmd.w * TEXT_SCALE / 2, by);\n ctx.fillText(cmd.text, cmd.w * TEXT_SCALE / 2, by);\n } else {\n ctx.textBaseline = \"middle\";\n const mx = cmd.w * TEXT_SCALE / 2;\n const my = cmd.h * TEXT_SCALE / 2;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, mx, my);\n ctx.fillText(cmd.text, mx, my);\n }\n } else if (cmd.w > 0 && cmd.h > 0) {\n ctx.textAlign = (cmd.align || \"left\");\n const tx = cmd.align === \"center\"\n ? cmd.x + cmd.w / 2\n : cmd.align === \"right\"\n ? cmd.x + cmd.w\n : cmd.x;\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n if (cmd.vAlign === \"middle\" && hasBaselineRatio) {\n ctx.textBaseline = \"alphabetic\";\n const ty = cmd.y + cmd.h / 2 + cmd.baselineRatio! * cmd.fontSize;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n ctx.fillText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n } else {\n ctx.textBaseline = cmd.vAlign === \"middle\" ? \"middle\" : \"top\";\n const ty = cmd.vAlign === \"middle\" ? cmd.y + cmd.h / 2 : cmd.y;\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n ctx.fillText(cmd.text, tx * TEXT_SCALE, ty * TEXT_SCALE);\n }\n } else {\n ctx.textAlign = \"left\";\n ctx.textBaseline = \"top\";\n ctx.scale(1 / TEXT_SCALE, 1 / TEXT_SCALE);\n if (cmd.stroke && cmd.sw > 0) ctx.strokeText(cmd.text, cmd.x * TEXT_SCALE, cmd.y * TEXT_SCALE);\n ctx.fillText(cmd.text, cmd.x * TEXT_SCALE, cmd.y * TEXT_SCALE);\n }\n ctx.restore();\n break;\n }\n case \"image\": {\n const image = imageFactory(cmd.src) as CanvasImageSource | null;\n if (!image) break;\n if (cmd.transform) {\n ctx.save();\n ctx.transform(...cmd.transform);\n ctx.drawImage(image, 0, 0, cmd.w, cmd.h);\n ctx.restore();\n } else {\n ctx.drawImage(image, cmd.x, cmd.y, cmd.w, cmd.h);\n }\n break;\n }\n }\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[]) => 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 flush(Array.isArray(out) ? out : [out]);\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","import type {Style} from \"../style/Style\";\n\nexport interface ExportBounds {\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n}\n\n/**\n * Convert world-space export bounds into the scene-space AABB an exporter\n * (SVG, canvas) should target.\n *\n * Coordinate-warping styles (e.g. Isometric) project world coords into a\n * different render space, so the SVG viewBox / canvas fit must be sized to\n * the projected region — otherwise the rendered scene drifts outside the\n * background rect and is partially clipped.\n *\n * For non-warping styles (identity, parchment, sketchy, …) `worldToScene`\n * is undefined and the bounds pass through unchanged.\n *\n * `scenePad` adds a uniform buffer in scene space — used to absorb\n * decorations the projection itself doesn't account for (cube depth in\n * Isometric, neon glow, …).\n */\nexport function projectExportBoundsToScene(\n bounds: ExportBounds,\n style: Style,\n scenePad = 0,\n): ExportBounds {\n if (!style.worldToScene) return bounds;\n const corners = [\n style.worldToScene(bounds.x, bounds.y),\n style.worldToScene(bounds.x + bounds.w, bounds.y),\n style.worldToScene(bounds.x, bounds.y + bounds.h),\n style.worldToScene(bounds.x + bounds.w, bounds.y + bounds.h),\n ];\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (const p of corners) {\n if (p.x < minX) minX = p.x;\n if (p.x > maxX) maxX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.y > maxY) maxY = p.y;\n }\n return {\n x: minX - scenePad,\n y: minY - scenePad,\n w: maxX - minX + scenePad * 2,\n h: maxY - minY + scenePad * 2,\n };\n}\n","import {ScenePipeline} from \"../ScenePipeline\";\nimport {Camera} from \"../camera/Camera\";\nimport {buildDrawCommands} from \"../draw/DrawCommandBuilder\";\nimport {svgFromBatches} from \"../render/SvgRenderer\";\nimport type {Shape} from \"../scene/Shape\";\nimport type {SvgExportOptions} from \"../SvgTypes\";\nimport {applyStyleToShapes} from \"../style/applyStyle\";\nimport {identityStyle} from \"../style/Style\";\nimport type {Style} from \"../style/Style\";\nimport type {Exporter, ExportContext} from \"./Exporter\";\nimport {flushSceneShapes} from \"./flushSceneShapes\";\nimport {clipSceneToViewport} from \"./clipSceneToViewport\";\nimport {projectExportBoundsToScene} from \"./sceneBounds\";\n\nconst IDENTITY_CAMERA = {scale: 1, offsetX: 0, offsetY: 0};\n\nfunction escapeXml(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\n .replace(/\"/g, \""\").replace(/'/g, \"'\");\n}\n\n/**\n * Renders the current scene as an SVG string by driving\n * {@link ScenePipeline} → {@link buildDrawCommands} → {@link svgFromBatches}.\n *\n * The pipeline is rebuilt with bounded viewport bounds (so the grid layout\n * matches the export region), then `sceneShapes` is flushed through the\n * draw-command pipeline at scale 1 — coordinates land inside the SVG\n * viewBox in world space, identical to the legacy SvgBackend output. Active\n * {@link SceneOverlay}s are still rendered against an {@link SvgBackend}\n * so user code that builds custom overlays via the DrawingBackend keeps\n * working until the overlay API is migrated to shapes.\n */\nexport class SvgExporter implements Exporter<string | undefined> {\n constructor(private readonly options: SvgExportOptions = {}) {}\n\n render({state, style, sceneOverlays}: ExportContext): string | undefined {\n const {currentArea, currentZIndex, currentAreaInstance} = state;\n if (currentArea === undefined || currentZIndex === undefined || !currentAreaInstance) return;\n\n const area = currentAreaInstance;\n const plane = area.getPlane(currentZIndex);\n if (!plane) return;\n\n const settings = state.settings;\n const padding = this.options.padding ?? 3;\n const bounds = state.computeExportBounds(area, plane, this.options.roomId, padding);\n const exportCamera = Camera.forMapBounds(bounds.x, bounds.x + bounds.w, bounds.y, bounds.y + bounds.h);\n const viewportBounds = exportCamera.getViewportBounds();\n\n const pipeline = new ScenePipeline(state.mapReader, settings);\n const result = pipeline.buildScene(area, plane, currentZIndex, state.lens);\n const transforms = {\n forward: style.worldToScene ? (x: number, y: number) => style.worldToScene!(x, y) : undefined,\n inverse: style.sceneToWorld ? (x: number, y: number) => style.sceneToWorld!(x, y) : undefined,\n };\n const clipped = clipSceneToViewport(result, viewportBounds, settings, transforms);\n const ctx = {scale: 1, roomSize: settings.roomSize};\n const styled = (shapes: Shape[]): Shape[] =>\n style === identityStyle ? shapes : applyStyleToShapes(shapes, style as Style, ctx);\n\n // Coordinate-warping styles (Isometric) render shapes in scene space —\n // the viewBox and background rect must follow the projection so rooms\n // don't drift off the background. Scene-pad covers projection-unaware\n // decorations (cube depth, glow halos, …).\n const sceneBounds = projectExportBoundsToScene(bounds, style, settings.roomSize * 0.5);\n\n const lines: string[] = [];\n lines.push(`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"${sceneBounds.x} ${sceneBounds.y} ${sceneBounds.w} ${sceneBounds.h}\">`);\n lines.push(`<rect x=\"${sceneBounds.x}\" y=\"${sceneBounds.y}\" width=\"${sceneBounds.w}\" height=\"${sceneBounds.h}\" fill=\"${escapeXml(settings.backgroundColor)}\"/>`);\n\n const flush = (shapes: Shape[]) => {\n if (shapes.length === 0) return;\n lines.push(...svgFromBatches(buildDrawCommands(styled(shapes), IDENTITY_CAMERA)));\n };\n\n flushSceneShapes(\n clipped,\n {state, viewportBounds, sceneOverlays, overlays: this.options.overlays},\n flush,\n );\n\n lines.push(\"</svg>\");\n return lines.join(\"\\n\");\n }\n}\n","import type {Exporter, ExportContext} from \"./Exporter\";\n\nexport interface PngExportOptions {\n pixelRatio?: number;\n}\n\n/**\n * Exports the current interactive canvas as a PNG data URL. Rasterizes the\n * live Konva stage — whatever style the renderer currently has applied is\n * what gets captured.\n *\n * For re-framing with an explicit width/height (e.g. server-side rendering),\n * use {@link CanvasExporter} and serialize its output yourself.\n */\nexport class PngExporter implements Exporter<string | undefined> {\n constructor(private readonly options: PngExportOptions = {}) {}\n\n render({backend}: ExportContext): string | undefined {\n const canvas = backend.exportCanvas(this.options);\n return canvas?.toDataURL('image/png');\n }\n}\n\n/** Companion to {@link PngExporter} that returns a Blob instead of a data URL. */\nexport class PngBlobExporter implements Exporter<Promise<Blob> | undefined> {\n constructor(private readonly options: PngExportOptions = {}) {}\n\n render({backend}: ExportContext): Promise<Blob> | undefined {\n const canvas = backend.exportCanvas(this.options);\n if (!canvas || !canvas.toBlob) return;\n const toBlob = canvas.toBlob.bind(canvas);\n return new Promise<Blob>((resolve) => {\n toBlob((blob: Blob | null) => { if (blob) resolve(blob); }, 'image/png');\n });\n }\n}\n","import type {ExportCanvas} from \"./Exporter\";\n\n/**\n * Serialize an {@link ExportCanvas} to raw bytes, synchronously, using the\n * portable `toDataURL` API. Works identically in the browser and in Node.\n *\n * ```ts\n * const canvas = renderer.export(new CanvasExporter({ width, height }));\n * const png = canvasToBytes(canvas); // Uint8Array\n * fs.writeFileSync('out.png', png); // Node\n * // or: new Blob([png], { type: 'image/png' }) — browser\n * ```\n *\n * @param canvas Canvas produced by {@link CanvasExporter} or the\n * interactive backend's `exportCanvas`.\n * @param mimeType Output format. Defaults to `'image/png'`. Pass `'image/jpeg'`\n * + `quality` (0..1) for JPEG.\n * @param quality JPEG quality (ignored for PNG).\n */\nexport function canvasToBytes(\n canvas: ExportCanvas,\n mimeType: string = 'image/png',\n quality?: number,\n): Uint8Array {\n const dataUrl = canvas.toDataURL(mimeType, quality);\n const comma = dataUrl.indexOf(',');\n const base64 = comma >= 0 ? dataUrl.slice(comma + 1) : dataUrl;\n\n // `atob` is standard in browsers and available globally in Node ≥16.\n const bin = atob(base64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n","import Konva from \"konva\";\nimport {ScenePipeline} from \"../ScenePipeline\";\nimport {Camera} from \"../camera/Camera\";\nimport {applyStyleToShapes} from \"../style/applyStyle\";\nimport {identityStyle} from \"../style/Style\";\nimport type {Style} from \"../style/Style\";\nimport {buildDrawCommands} from \"../draw/DrawCommandBuilder\";\nimport {renderToCanvas} from \"../render/CanvasRenderer\";\nimport type {Shape} from \"../scene/Shape\";\nimport type {Exporter, ExportContext, ExportCanvas} from \"./Exporter\";\nimport {canvasToBytes} from \"./canvasToBytes\";\nimport {flushSceneShapes} from \"./flushSceneShapes\";\nimport {clipSceneToViewport} from \"./clipSceneToViewport\";\nimport {projectExportBoundsToScene} from \"./sceneBounds\";\n\nexport interface CanvasExportOptions {\n /** Width of the output image in pixels. */\n width: number;\n /** Height of the output image in pixels. */\n height: number;\n /** Room ID to center the export on. If omitted, exports the full area. */\n roomId?: number;\n /** Padding in map units around the exported region. Default: 3 */\n padding?: number;\n /** Overlays to render over the scene (position marker, highlights, paths). */\n overlays?: {\n position?: { roomId: number };\n highlights?: Array<{ roomId: number; color: string }>;\n paths?: Array<{ locations: number[]; color: string }>;\n };\n}\n\n/**\n * Renders the current scene into a canvas at the requested width/height by\n * driving {@link ScenePipeline} → {@link buildDrawCommands} →\n * {@link renderToCanvas}.\n *\n * Decoupled from the live Konva stage: the pipeline is rebuilt against the\n * export bounds, shapes are projected through a fitted camera transform, and\n * the result is rasterized onto a fresh 2D canvas. Background colour is\n * filled before any draw commands replay, so PNG / JPEG output looks identical\n * to the on-screen map.\n *\n * Unlike {@link PngExporter} (which captures the on-screen viewport via the\n * live Konva stage), `CanvasExporter` is fully headless and reproducible.\n */\nexport class CanvasExporter implements Exporter<ExportCanvas | undefined> {\n constructor(private readonly options: CanvasExportOptions) {}\n\n render({state, style, sceneOverlays}: ExportContext): ExportCanvas | undefined {\n const {currentArea, currentZIndex, currentAreaInstance} = state;\n if (currentArea === undefined || currentZIndex === undefined || !currentAreaInstance) return;\n\n const area = currentAreaInstance;\n const plane = area.getPlane(currentZIndex);\n if (!plane) return;\n\n const settings = state.settings;\n const {width, height} = this.options;\n const padding = this.options.padding ?? 3;\n const bounds = state.computeExportBounds(area, plane, this.options.roomId, padding);\n\n // Fit scene-space bounds (post-style-projection) into the requested\n // canvas size. Coordinate-warping styles (Isometric) render outside\n // the world AABB, so fitting world bounds clips/offsets the scene.\n const sceneBounds = projectExportBoundsToScene(bounds, style, settings.roomSize * 0.5);\n const scale = Math.min(width / sceneBounds.w, height / sceneBounds.h);\n const mapPixelW = sceneBounds.w * scale;\n const mapPixelH = sceneBounds.h * scale;\n const offsetX = (width - mapPixelW) / 2 - sceneBounds.x * scale;\n const offsetY = (height - mapPixelH) / 2 - sceneBounds.y * scale;\n const renderCam = {scale, offsetX, offsetY};\n\n // Culling must cover the full canvas, not just the tight export region.\n // Camera.forRenderCamera reproduces the letterbox-extended viewport from\n // the fitted transform, so rooms in aspect-ratio padding areas are not\n // incorrectly culled.\n const cullingCamera = Camera.forRenderCamera(width, height, scale, offsetX, offsetY);\n\n const transforms = {\n forward: style.worldToScene ? (x: number, y: number) => style.worldToScene!(x, y) : undefined,\n inverse: style.sceneToWorld ? (x: number, y: number) => style.sceneToWorld!(x, y) : undefined,\n };\n\n const pipeline = new ScenePipeline(state.mapReader, settings);\n const result = pipeline.buildScene(area, plane, currentZIndex, state.lens);\n const clipped = clipSceneToViewport(result, cullingCamera.getViewportBounds(), settings, transforms);\n\n const canvas = Konva.Util.createCanvasElement() as unknown as HTMLCanvasElement;\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n ctx.fillStyle = settings.backgroundColor;\n ctx.fillRect(0, 0, width, height);\n\n const styleCtx = {scale, roomSize: settings.roomSize};\n const styled = (shapes: Shape[]): Shape[] =>\n style === identityStyle ? shapes : applyStyleToShapes(shapes, style as Style, styleCtx);\n\n const flush = (shapes: Shape[]) => {\n if (shapes.length === 0) return;\n renderToCanvas(ctx, buildDrawCommands(styled(shapes), renderCam));\n };\n\n const viewportBounds = Camera.forMapBounds(bounds.x, bounds.x + bounds.w, bounds.y, bounds.y + bounds.h).getViewportBounds();\n flushSceneShapes(\n clipped,\n {state, viewportBounds, sceneOverlays, overlays: this.options.overlays},\n flush,\n );\n\n return canvas as unknown as ExportCanvas;\n }\n}\n\nexport interface PngBytesExportOptions extends CanvasExportOptions {\n /** MIME type to encode. Defaults to `'image/png'`. */\n mimeType?: string;\n /** Encoder quality (0..1). Only used for lossy formats like `'image/jpeg'`. */\n quality?: number;\n}\n\n/**\n * Headless PNG/JPEG bytes at a specific width × height.\n *\n * Composes {@link CanvasExporter} with a portable `toDataURL` → `Uint8Array`\n * decode, so callers get bytes directly without touching a canvas or casting\n * to platform-specific types:\n *\n * ```ts\n * const png = renderer.export(new PngBytesExporter({ width: 1920, height: 1080 }));\n * fs.writeFileSync('out.png', png!); // Node\n * new Blob([png!], { type: 'image/png' }); // Browser\n * ```\n *\n * For JPEG: `new PngBytesExporter({ width, height, mimeType: 'image/jpeg', quality: 0.9 })`.\n */\nexport class PngBytesExporter implements Exporter<Uint8Array | undefined> {\n private readonly canvasExporter: CanvasExporter;\n\n constructor(private readonly options: PngBytesExportOptions) {\n this.canvasExporter = new CanvasExporter(options);\n }\n\n render(context: ExportContext): Uint8Array | undefined {\n const canvas = this.canvasExporter.render(context);\n if (!canvas) return;\n return canvasToBytes(canvas, this.options.mimeType, this.options.quality);\n }\n}\n","/**\n * Ambient light data: a single image containing a smooth radial vignette,\n * positioned centered on the player.\n */\nexport type AmbientLightData = {\n cx: number;\n cy: number;\n displaySize: number;\n src: string;\n};\n\n/** Tunable parameters for the ambient light vignette. */\nexport type AmbientLightParams = {\n color: string;\n radius: number;\n intensity: number;\n};\n\n// --- Vignette texture cache ---\n// Regenerate only when settings or display ratio change.\nlet _cachedUrl: string | undefined;\nlet _cacheKey: string | undefined;\n\nconst TEXTURE_SIZE = 256;\n\n/**\n * Generate a vignette texture as a data URL.\n * Uses canvas 2D radial gradient + destination-out compositing to create\n * a smooth darkness overlay with a clear center — no banding.\n */\nfunction getVignetteDataUrl(intensity: number, color: string, lightRatio: number): string {\n const key = `${intensity.toFixed(2)}:${color}:${lightRatio.toFixed(3)}`;\n if (_cacheKey === key && _cachedUrl) return _cachedUrl;\n\n const size = TEXTURE_SIZE;\n const half = size / 2;\n\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d')!;\n\n // 1. Fill entire canvas with uniform darkness\n ctx.fillStyle = `rgba(0, 0, 0, ${intensity})`;\n ctx.fillRect(0, 0, size, size);\n\n // 2. Punch a smooth gradient \"hole\" using destination-out.\n // At center: remove all darkness (fully clear).\n // At light edge (lightRatio): remove nothing (stays dark).\n ctx.globalCompositeOperation = 'destination-out';\n\n const gradient = ctx.createRadialGradient(half, half, 0, half, half, half);\n const lr = Math.min(lightRatio, 0.99);\n\n gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');\n gradient.addColorStop(lr * 0.25, 'rgba(0, 0, 0, 0.97)');\n gradient.addColorStop(lr * 0.50, 'rgba(0, 0, 0, 0.82)');\n gradient.addColorStop(lr * 0.70, 'rgba(0, 0, 0, 0.50)');\n gradient.addColorStop(lr * 0.85, 'rgba(0, 0, 0, 0.22)');\n gradient.addColorStop(lr * 0.95, 'rgba(0, 0, 0, 0.06)');\n gradient.addColorStop(lr, 'rgba(0, 0, 0, 0)');\n if (lr < 0.98) {\n gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');\n }\n\n ctx.fillStyle = gradient;\n ctx.beginPath();\n ctx.arc(half, half, half, 0, Math.PI * 2);\n ctx.fill();\n\n // 3. Optional: subtle colored glow in the center\n ctx.globalCompositeOperation = 'source-over';\n const cr = parseInt(color.slice(1, 3), 16);\n const cg = parseInt(color.slice(3, 5), 16);\n const cb = parseInt(color.slice(5, 7), 16);\n\n const tintGradient = ctx.createRadialGradient(half, half, 0, half, half, half * lr);\n tintGradient.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, 0.05)`);\n tintGradient.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);\n ctx.fillStyle = tintGradient;\n ctx.fillRect(0, 0, size, size);\n\n _cachedUrl = canvas.toDataURL();\n _cacheKey = key;\n return _cachedUrl;\n}\n\n/**\n * Compute ambient light vignette for the given player position and viewport.\n *\n * Produces a single image overlay containing a smooth radial gradient:\n * clear at center → full darkness at edges. The image is sized to always\n * cover the entire visible viewport with margin.\n */\nexport function computeAmbientLight(\n cx: number,\n cy: number,\n viewportBounds: { minX: number; maxX: number; minY: number; maxY: number },\n params: AmbientLightParams,\n): AmbientLightData {\n const {radius, intensity, color} = params;\n\n const vw = viewportBounds.maxX - viewportBounds.minX;\n const vh = viewportBounds.maxY - viewportBounds.minY;\n const viewportDiag = Math.sqrt(vw * vw + vh * vh);\n\n // Display size: large enough that the dark edges always cover the full viewport.\n // The image is square and centered on the player, so corner distance = displaySize * √2 / 2.\n const displaySize = Math.max(viewportDiag * 2.5, radius * 4);\n\n // What fraction of the image half-diagonal corresponds to the light radius\n const lightRatio = Math.round(radius / (displaySize / 2) * 50) / 50; // snap to 0.02 steps for caching\n\n const src = getVignetteDataUrl(intensity, color, lightRatio);\n\n return {cx, cy, displaySize, src};\n}\n","import type {MapState} from \"../MapState\";\nimport type {ViewportBounds} from \"../types/Settings\";\nimport {computeAmbientLight, type AmbientLightParams} from \"../scene/AmbientLightStyle\";\nimport type {Shape} from \"../scene/Shape\";\nimport type {SceneOverlay, SceneOverlayContext} from \"./SceneOverlay\";\n\nexport type AmbientLightOptions = Partial<AmbientLightParams>;\n\nconst DEFAULTS: AmbientLightParams = {\n color: '#ffcc44',\n radius: 12,\n intensity: 0.7,\n};\n\n/**\n * Vignette centered on the player room. Re-renders on position changes and\n * viewport changes (pan/zoom/resize). Registering the overlay turns the effect\n * on; removing it turns it off — there is no global settings flag.\n *\n * ```ts\n * const ambient = new AmbientLightOverlay({ color: '#ffcc44', radius: 12 });\n * renderer.addSceneOverlay('ambient-light', ambient);\n * // later:\n * ambient.setOptions({ intensity: 0.9 });\n * renderer.removeSceneOverlay('ambient-light');\n * ```\n */\nexport class AmbientLightOverlay implements SceneOverlay {\n private params: AmbientLightParams;\n private ctx?: SceneOverlayContext;\n private viewportUnsub?: () => void;\n private readonly onPosition = () => this.ctx?.invalidate();\n\n constructor(options?: AmbientLightOptions) {\n this.params = {...DEFAULTS, ...options};\n }\n\n /** Update tunables at runtime. Triggers a re-render. */\n setOptions(options: AmbientLightOptions): void {\n this.params = {...this.params, ...options};\n this.ctx?.invalidate();\n }\n\n getOptions(): AmbientLightParams {\n return {...this.params};\n }\n\n attach(ctx: SceneOverlayContext): void {\n this.ctx = ctx;\n ctx.state.events.on('position', this.onPosition);\n this.viewportUnsub = ctx.onViewportChange(() => ctx.invalidate());\n }\n\n detach(): void {\n this.ctx?.state.events.off('position', this.onPosition);\n this.viewportUnsub?.();\n this.viewportUnsub = undefined;\n this.ctx = undefined;\n }\n\n render(state: MapState, bounds: ViewportBounds): Shape | void {\n if (state.positionRoomId === undefined) return;\n const room = state.mapReader.getRoom(state.positionRoomId);\n if (!room) return;\n const data = computeAmbientLight(room.x, room.y, bounds, this.params);\n return {\n type: \"image\",\n x: data.cx - data.displaySize / 2,\n y: data.cy - data.displaySize / 2,\n width: data.displaySize,\n height: data.displaySize,\n src: data.src,\n layer: \"overlay\",\n };\n }\n}\n","/**\n * This very basic implementation of a priority queue is used to select the\n * next node of the graph to walk to.\n *\n * The queue is always sorted to have the least expensive node on top.\n * Some helper methods are also implemented.\n *\n * You should **never** modify the queue directly, but only using the methods\n * provided by the class.\n */\nclass PriorityQueue {\n /**\n * Creates a new empty priority queue\n */\n constructor() {\n // The `keys` set is used to greatly improve the speed at which we can\n // check the presence of a value in the queue\n this.keys = new Set();\n this.queue = [];\n }\n\n /**\n * Sort the queue to have the least expensive node to visit on top\n *\n * @private\n */\n sort() {\n this.queue.sort((a, b) => a.priority - b.priority);\n }\n\n /**\n * Sets a priority for a key in the queue.\n * Inserts it in the queue if it does not already exists.\n *\n * @param {any} key Key to update or insert\n * @param {number} value Priority of the key\n * @return {number} Size of the queue\n */\n set(key, value) {\n const priority = Number(value);\n if (isNaN(priority)) throw new TypeError('\"priority\" must be a number');\n\n if (!this.keys.has(key)) {\n // Insert a new entry if the key is not already in the queue\n this.keys.add(key);\n this.queue.push({ key, priority });\n } else {\n // Update the priority of an existing key\n this.queue.map((element) => {\n if (element.key === key) {\n Object.assign(element, { priority });\n }\n\n return element;\n });\n }\n\n this.sort();\n return this.queue.length;\n }\n\n /**\n * The next method is used to dequeue a key:\n * It removes the first element from the queue and returns it\n *\n * @return {object} First priority queue entry\n */\n next() {\n const element = this.queue.shift();\n\n // Remove the key from the `_keys` set\n this.keys.delete(element.key);\n\n return element;\n }\n\n /**\n * @return {boolean} `true` when the queue is empty\n */\n isEmpty() {\n return Boolean(this.queue.length === 0);\n }\n\n /**\n * Check if the queue has a key in it\n *\n * @param {any} key Key to lookup\n * @return {boolean}\n */\n has(key) {\n return this.keys.has(key);\n }\n\n /**\n * Get the element in the queue with the specified key\n *\n * @param {any} key Key to lookup\n * @return {object}\n */\n get(key) {\n return this.queue.find((element) => element.key === key);\n }\n}\n\nmodule.exports = PriorityQueue;\n","/**\n * Removes a key and all of its references from a map.\n * This function has no side-effects as it returns\n * a brand new map.\n *\n * @param {Map} map - Map to remove the key from\n * @param {string} key - Key to remove from the map\n * @return {Map} New map without the passed key\n */\nfunction removeDeepFromMap(map, key) {\n const newMap = new Map();\n\n for (const [aKey, val] of map) {\n if (aKey !== key && val instanceof Map) {\n newMap.set(aKey, removeDeepFromMap(val, key));\n } else if (aKey !== key) {\n newMap.set(aKey, val);\n }\n }\n\n return newMap;\n}\n\nmodule.exports = removeDeepFromMap;\n","/**\n * Validates a cost for a node\n *\n * @private\n * @param {number} val - Cost to validate\n * @return {bool}\n */\nfunction isValidNode(val) {\n const cost = Number(val);\n\n if (isNaN(cost) || cost <= 0) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Creates a deep `Map` from the passed object.\n *\n * @param {Object} source - Object to populate the map with\n * @return {Map} New map with the passed object data\n */\nfunction toDeepMap(source) {\n const map = new Map();\n const keys = Object.keys(source);\n\n keys.forEach((key) => {\n const val = source[key];\n\n if (val !== null && typeof val === \"object\" && !Array.isArray(val)) {\n return map.set(key, toDeepMap(val));\n }\n\n if (!isValidNode(val)) {\n throw new Error(\n `Could not add node at key \"${key}\", make sure it's a valid node`,\n val\n );\n }\n\n return map.set(key, Number(val));\n });\n\n return map;\n}\n\nmodule.exports = toDeepMap;\n","/**\n * Validate a map to ensure all it's values are either a number or a map\n *\n * @param {Map} map - Map to valiadte\n */\nfunction validateDeep(map) {\n if (!(map instanceof Map)) {\n throw new Error(`Invalid graph: Expected Map instead found ${typeof map}`);\n }\n\n map.forEach((value, key) => {\n if (typeof value === \"object\" && value instanceof Map) {\n validateDeep(value);\n return;\n }\n\n if (typeof value !== \"number\" || value <= 0) {\n throw new Error(\n `Values must be numbers greater than 0. Found value ${value} at ${key}`\n );\n }\n });\n}\n\nmodule.exports = validateDeep;\n","const Queue = require(\"./PriorityQueue\");\nconst removeDeepFromMap = require(\"./removeDeepFromMap\");\nconst toDeepMap = require(\"./toDeepMap\");\nconst validateDeep = require(\"./validateDeep\");\n\n/** Creates and manages a graph */\nclass Graph {\n /**\n * Creates a new Graph, optionally initializing it a nodes graph representation.\n *\n * A graph representation is an object that has as keys the name of the point and as values\n * the points reacheable from that node, with the cost to get there:\n *\n * {\n * node (Number|String): {\n * neighbor (Number|String): cost (Number),\n * ...,\n * },\n * }\n *\n * In alternative to an object, you can pass a `Map` of `Map`. This will\n * allow you to specify numbers as keys.\n *\n * @param {Objec|Map} [graph] - Initial graph definition\n * @example\n *\n * const route = new Graph();\n *\n * // Pre-populated graph\n * const route = new Graph({\n * A: { B: 1 },\n * B: { A: 1, C: 2, D: 4 },\n * });\n *\n * // Passing a Map\n * const g = new Map()\n *\n * const a = new Map()\n * a.set('B', 1)\n *\n * const b = new Map()\n * b.set('A', 1)\n * b.set('C', 2)\n * b.set('D', 4)\n *\n * g.set('A', a)\n * g.set('B', b)\n *\n * const route = new Graph(g)\n */\n constructor(graph) {\n if (graph instanceof Map) {\n validateDeep(graph);\n this.graph = graph;\n } else if (graph) {\n this.graph = toDeepMap(graph);\n } else {\n this.graph = new Map();\n }\n }\n\n /**\n * Adds a node to the graph\n *\n * @param {string} name - Name of the node\n * @param {Object|Map} neighbors - Neighbouring nodes and cost to reach them\n * @return {this}\n * @example\n *\n * const route = new Graph();\n *\n * route.addNode('A', { B: 1 });\n *\n * // It's possible to chain the calls\n * route\n * .addNode('B', { A: 1 })\n * .addNode('C', { A: 3 });\n *\n * // The neighbors can be expressed in a Map\n * const d = new Map()\n * d.set('A', 2)\n * d.set('B', 8)\n *\n * route.addNode('D', d)\n */\n addNode(name, neighbors) {\n let nodes;\n if (neighbors instanceof Map) {\n validateDeep(neighbors);\n nodes = neighbors;\n } else {\n nodes = toDeepMap(neighbors);\n }\n\n this.graph.set(name, nodes);\n\n return this;\n }\n\n /**\n * @deprecated since version 2.0, use `Graph#addNode` instead\n */\n addVertex(name, neighbors) {\n return this.addNode(name, neighbors);\n }\n\n /**\n * Removes a node and all of its references from the graph\n *\n * @param {string|number} key - Key of the node to remove from the graph\n * @return {this}\n * @example\n *\n * const route = new Graph({\n * A: { B: 1, C: 5 },\n * B: { A: 3 },\n * C: { B: 2, A: 2 },\n * });\n *\n * route.removeNode('C');\n * // The graph now is:\n * // { A: { B: 1 }, B: { A: 3 } }\n */\n removeNode(key) {\n this.graph = removeDeepFromMap(this.graph, key);\n\n return this;\n }\n\n /**\n * Compute the shortest path between the specified nodes\n *\n * @param {string} start - Starting node\n * @param {string} goal - Node we want to reach\n * @param {object} [options] - Options\n *\n * @param {boolean} [options.trim] - Exclude the origin and destination nodes from the result\n * @param {boolean} [options.reverse] - Return the path in reversed order\n * @param {boolean} [options.cost] - Also return the cost of the path when set to true\n *\n * @return {array|object} Computed path between the nodes.\n *\n * When `option.cost` is set to true, the returned value will be an object with shape:\n * - `path` *(Array)*: Computed path between the nodes\n * - `cost` *(Number)*: Cost of the path\n *\n * @example\n *\n * const route = new Graph()\n *\n * route.addNode('A', { B: 1 })\n * route.addNode('B', { A: 1, C: 2, D: 4 })\n * route.addNode('C', { B: 2, D: 1 })\n * route.addNode('D', { C: 1, B: 4 })\n *\n * route.path('A', 'D') // => ['A', 'B', 'C', 'D']\n *\n * // trimmed\n * route.path('A', 'D', { trim: true }) // => [B', 'C']\n *\n * // reversed\n * route.path('A', 'D', { reverse: true }) // => ['D', 'C', 'B', 'A']\n *\n * // include the cost\n * route.path('A', 'D', { cost: true })\n * // => {\n * // path: [ 'A', 'B', 'C', 'D' ],\n * // cost: 4\n * // }\n */\n path(start, goal, options = {}) {\n // Don't run when we don't have nodes set\n if (!this.graph.size) {\n if (options.cost) return { path: null, cost: 0 };\n\n return null;\n }\n\n const explored = new Set();\n const frontier = new Queue();\n const previous = new Map();\n\n let path = [];\n let totalCost = 0;\n\n let avoid = [];\n if (options.avoid) avoid = [].concat(options.avoid);\n\n if (avoid.includes(start)) {\n throw new Error(`Starting node (${start}) cannot be avoided`);\n } else if (avoid.includes(goal)) {\n throw new Error(`Ending node (${goal}) cannot be avoided`);\n }\n\n // Add the starting point to the frontier, it will be the first node visited\n frontier.set(start, 0);\n\n // Run until we have visited every node in the frontier\n while (!frontier.isEmpty()) {\n // Get the node in the frontier with the lowest cost (`priority`)\n const node = frontier.next();\n\n // When the node with the lowest cost in the frontier in our goal node,\n // we can compute the path and exit the loop\n if (node.key === goal) {\n // Set the total cost to the current value\n totalCost = node.priority;\n\n let nodeKey = node.key;\n while (previous.has(nodeKey)) {\n path.push(nodeKey);\n nodeKey = previous.get(nodeKey);\n }\n\n break;\n }\n\n // Add the current node to the explored set\n explored.add(node.key);\n\n // Loop all the neighboring nodes\n const neighbors = this.graph.get(node.key) || new Map();\n neighbors.forEach((nCost, nNode) => {\n // If we already explored the node, or the node is to be avoided, skip it\n if (explored.has(nNode) || avoid.includes(nNode)) return null;\n\n // If the neighboring node is not yet in the frontier, we add it with\n // the correct cost\n if (!frontier.has(nNode)) {\n previous.set(nNode, node.key);\n return frontier.set(nNode, node.priority + nCost);\n }\n\n const frontierPriority = frontier.get(nNode).priority;\n const nodeCost = node.priority + nCost;\n\n // Otherwise we only update the cost of this node in the frontier when\n // it's below what's currently set\n if (nodeCost < frontierPriority) {\n previous.set(nNode, node.key);\n return frontier.set(nNode, nodeCost);\n }\n\n return null;\n });\n }\n\n // Return null when no path can be found\n if (!path.length) {\n if (options.cost) return { path: null, cost: 0 };\n\n return null;\n }\n\n // From now on, keep in mind that `path` is populated in reverse order,\n // from destination to origin\n\n // Remove the first value (the goal node) if we want a trimmed result\n if (options.trim) {\n path.shift();\n } else {\n // Add the origin waypoint at the end of the array\n path = path.concat([start]);\n }\n\n // Reverse the path if we don't want it reversed, so the result will be\n // from `start` to `goal`\n if (!options.reverse) {\n path = path.reverse();\n }\n\n // Return an object if we also want the cost\n if (options.cost) {\n return {\n path,\n cost: totalCost,\n };\n }\n\n return path;\n }\n\n /**\n * @deprecated since version 2.0, use `Graph#path` instead\n */\n shortestPath(...args) {\n return this.path(...args);\n }\n}\n\nmodule.exports = Graph;\n","import type {IMapReader} from \"./reader/MapReader\";\n\nconst exitNumberToDirection: 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\nconst directionToExitWeightKey: Record<MapData.direction, string> = {\n north: \"n\", northeast: \"ne\", northwest: \"nw\",\n east: \"e\", west: \"w\",\n south: \"s\", southeast: \"se\", southwest: \"sw\",\n up: \"up\", down: \"down\", in: \"in\", out: \"out\",\n};\n\nexport interface Edge {\n id: number;\n weight: number;\n}\n\nexport interface GraphData {\n adj: Map<number, Edge[]>;\n /** For node-dijkstra library */\n graphDefinition: Record<string, Record<string, number>>;\n maxEdgeDistance: number;\n minEdgeWeight: number;\n}\n\n/**\n * Builds a weighted adjacency graph from MapReader room/exit data.\n * Separated from PathFinder so the graph can be reused and tested independently.\n */\nexport class MapGraph {\n\n private readonly mapReader: IMapReader;\n private readonly data: GraphData;\n\n constructor(mapReader: IMapReader) {\n this.mapReader = mapReader;\n this.data = this.buildGraph();\n }\n\n getAdj(): Map<number, Edge[]> {\n return this.data.adj;\n }\n\n getGraphDefinition(): Record<string, Record<string, number>> {\n return this.data.graphDefinition;\n }\n\n getMaxEdgeDistance(): number {\n return this.data.maxEdgeDistance;\n }\n\n getMinEdgeWeight(): number {\n return this.data.minEdgeWeight;\n }\n\n getRoom(roomId: number) {\n return this.mapReader.getRoom(roomId);\n }\n\n private resolveEdgeWeight(room: MapData.Room, exitWeightKey: string, target: MapData.Room): number {\n const exitWeight = room.exitWeights?.[exitWeightKey];\n if (exitWeight !== undefined && exitWeight > 0) return exitWeight;\n return Math.max(target.weight, 1);\n }\n\n private buildGraph(): GraphData {\n const adj = new Map<number, Edge[]>();\n const graphDefinition: Record<string, Record<string, number>> = {};\n let maxEdgeDist = 1;\n let minEdgeWeight = Infinity;\n\n this.mapReader.getRooms().forEach(room => {\n const edges: Edge[] = [];\n const connections: Record<string, number> = {};\n\n const lockedDirections = new Set(\n (room.exitLocks ?? [])\n .map(lockId => exitNumberToDirection[lockId])\n .filter((direction): direction is MapData.direction => Boolean(direction))\n );\n\n const lockedSpecialTargets = new Set(room.mSpecialExitLocks ?? []);\n\n Object.entries(room.exits ?? {}).forEach(([direction, targetRoomId]) => {\n if (lockedDirections.has(direction as MapData.direction)) return;\n const target = this.mapReader.getRoom(targetRoomId);\n if (target) {\n const weightKey = directionToExitWeightKey[direction as MapData.direction] ?? direction;\n const weight = this.resolveEdgeWeight(room, weightKey, target);\n edges.push({id: targetRoomId, weight});\n connections[targetRoomId.toString()] = weight;\n if (weight < minEdgeWeight) minEdgeWeight = weight;\n const dx = target.x - room.x;\n const dy = target.y - room.y;\n const dz = target.z - room.z;\n const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);\n if (dist > maxEdgeDist) maxEdgeDist = dist;\n }\n });\n\n Object.entries(room.specialExits ?? {}).forEach(([exitCommand, targetRoomId]) => {\n if (lockedSpecialTargets.has(targetRoomId)) return;\n const target = this.mapReader.getRoom(targetRoomId);\n if (target) {\n const weight = this.resolveEdgeWeight(room, exitCommand, target);\n edges.push({id: targetRoomId, weight});\n connections[targetRoomId.toString()] = weight;\n if (weight < minEdgeWeight) minEdgeWeight = weight;\n const dx = target.x - room.x;\n const dy = target.y - room.y;\n const dz = target.z - room.z;\n const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);\n if (dist > maxEdgeDist) maxEdgeDist = dist;\n }\n });\n\n adj.set(room.id, edges);\n graphDefinition[room.id.toString()] = connections;\n });\n\n if (!isFinite(minEdgeWeight)) minEdgeWeight = 1;\n\n return {adj, graphDefinition, maxEdgeDistance: maxEdgeDist, minEdgeWeight};\n }\n}\n","import Graph from \"node-dijkstra\";\nimport type {IMapReader} from \"./reader/MapReader\";\nimport {MapGraph} from \"./MapGraph\";\nimport type {Edge} from \"./MapGraph\";\n\nexport type PathFindingAlgorithm = 'dijkstra' | 'astar';\n\n// --- Min-heap for A* ---\n\ninterface HeapEntry {\n id: number;\n priority: number;\n}\n\nfunction heapPush(heap: HeapEntry[], entry: HeapEntry) {\n heap.push(entry);\n let i = heap.length - 1;\n while (i > 0) {\n const parent = (i - 1) >> 1;\n if (heap[parent].priority <= heap[i].priority) break;\n [heap[parent], heap[i]] = [heap[i], heap[parent]];\n i = parent;\n }\n}\n\nfunction heapPop(heap: HeapEntry[]): HeapEntry | undefined {\n if (heap.length === 0) return undefined;\n const top = heap[0];\n const last = heap.pop()!;\n if (heap.length > 0) {\n heap[0] = last;\n let i = 0;\n const n = heap.length;\n while (true) {\n let smallest = i;\n const left = 2 * i + 1;\n const right = 2 * i + 2;\n if (left < n && heap[left].priority < heap[smallest].priority) smallest = left;\n if (right < n && heap[right].priority < heap[smallest].priority) smallest = right;\n if (smallest === i) break;\n [heap[i], heap[smallest]] = [heap[smallest], heap[i]];\n i = smallest;\n }\n }\n return top;\n}\n\n// --- Algorithm implementations ---\n\nfunction reconstructPath(cameFrom: Map<number, number>, from: number, to: number): number[] {\n const path: number[] = [to];\n let current = to;\n while (current !== from) {\n current = cameFrom.get(current)!;\n path.push(current);\n }\n path.reverse();\n return path;\n}\n\nfunction findPathDijkstra(graph: Graph, from: number, to: number): number[] | null {\n const path = graph.path(from.toString(), to.toString());\n const nodes = Array.isArray(path) ? path : path?.path;\n return nodes ? nodes.map((id: string) => Number(id)) : null;\n}\n\nfunction findPathAStar(\n adj: Map<number, Edge[]>,\n from: number,\n to: number,\n mapGraph: MapGraph,\n): number[] | null {\n const goalRoom = mapGraph.getRoom(to);\n if (!goalRoom) return null;\n const goalX = goalRoom.x;\n const goalY = goalRoom.y;\n const goalZ = goalRoom.z;\n const maxEdgeDistance = mapGraph.getMaxEdgeDistance();\n const minEdgeWeight = mapGraph.getMinEdgeWeight();\n\n const heuristic = (roomId: number): number => {\n const room = mapGraph.getRoom(roomId);\n if (!room) return 0;\n const dx = room.x - goalX;\n const dy = room.y - goalY;\n const dz = room.z - goalZ;\n return (Math.sqrt(dx * dx + dy * dy + dz * dz) / maxEdgeDistance) * minEdgeWeight;\n };\n\n const gScore = new Map<number, number>();\n const cameFrom = new Map<number, number>();\n const heap: HeapEntry[] = [];\n\n gScore.set(from, 0);\n heapPush(heap, {id: from, priority: heuristic(from)});\n\n while (heap.length > 0) {\n const {id: current} = heapPop(heap)!;\n if (current === to) return reconstructPath(cameFrom, from, to);\n\n const currentG = gScore.get(current) ?? Infinity;\n\n const edges = adj.get(current);\n if (!edges) continue;\n for (const edge of edges) {\n const nextG = currentG + edge.weight;\n if (nextG < (gScore.get(edge.id) ?? Infinity)) {\n gScore.set(edge.id, nextG);\n cameFrom.set(edge.id, current);\n heapPush(heap, {id: edge.id, priority: nextG + heuristic(edge.id)});\n }\n }\n }\n return null;\n}\n\n// --- PathFinder ---\n\nexport default class PathFinder {\n\n private readonly mapGraph: MapGraph;\n private readonly dijkstraGraph: Graph;\n private _algorithm: PathFindingAlgorithm;\n private readonly cache = new Map<string, number[] | null>();\n\n constructor(mapReader: IMapReader, algorithm: PathFindingAlgorithm = 'dijkstra') {\n this._algorithm = algorithm;\n this.mapGraph = new MapGraph(mapReader);\n this.dijkstraGraph = new Graph(this.mapGraph.getGraphDefinition());\n }\n\n get algorithm(): PathFindingAlgorithm {\n return this._algorithm;\n }\n\n setAlgorithm(algorithm: PathFindingAlgorithm): void {\n if (algorithm === this._algorithm) return;\n this._algorithm = algorithm;\n this.cache.clear();\n }\n\n findPath(from: number, to: number): number[] | null {\n const cacheKey = `${from}->${to}`;\n if (this.cache.has(cacheKey)) {\n return this.cache.get(cacheKey)!;\n }\n\n if (from === to) {\n const result = this.mapGraph.getRoom(from) ? [from] : null;\n this.cache.set(cacheKey, result);\n return result;\n }\n\n if (!this.mapGraph.getRoom(from) || !this.mapGraph.getRoom(to)) {\n this.cache.set(cacheKey, null);\n return null;\n }\n\n let result: number[] | null;\n switch (this._algorithm) {\n case 'dijkstra':\n result = findPathDijkstra(this.dijkstraGraph, from, to);\n break;\n case 'astar':\n result = findPathAStar(this.mapGraph.getAdj(), from, to, this.mapGraph);\n break;\n }\n\n this.cache.set(cacheKey, result);\n return result;\n }\n}\n","import type {RoomLens, ExitTreatment} from \"./RoomLens\";\nimport {defaultExitTreatment} from \"./RoomLens\";\nimport type IExit from \"../reader/Exit\";\n\n/**\n * How a composite lens reconciles disagreeing children when both expose\n * `getExitTreatment`.\n *\n * - `\"most-restrictive\"` (default): `hidden` > `stub` > `full`. A single\n * child that says hide wins. Safe default for stacked visibility filters.\n * - `\"least-restrictive\"`: `full` > `stub` > `hidden`. Useful when lenses are\n * additive scopes (\"show guild rooms OR quest rooms\").\n * - `\"first\"`: the first child whose `getExitTreatment` returns non-undefined\n * decides. Lets the caller pick a dominant lens by ordering.\n */\nexport type ExitConflictStrategy = \"most-restrictive\" | \"least-restrictive\" | \"first\";\n\nexport type ComposeOptions = {\n /** Defaults to `\"most-restrictive\"`. */\n exitStrategy?: ExitConflictStrategy;\n /**\n * AND (visibility must hold in every lens) vs OR (any lens may grant\n * visibility). Defaults to `\"and\"` — restrictive intersection.\n */\n visibility?: \"and\" | \"or\";\n};\n\nconst TREATMENT_ORDER: Record<ExitTreatment, number> = {\n hidden: 2,\n stub: 1,\n full: 0,\n};\n\nfunction pickTreatment(\n a: ExitTreatment,\n b: ExitTreatment,\n strategy: ExitConflictStrategy,\n): ExitTreatment {\n if (strategy === \"first\") return a;\n if (strategy === \"least-restrictive\") {\n return TREATMENT_ORDER[a] < TREATMENT_ORDER[b] ? a : b;\n }\n return TREATMENT_ORDER[a] > TREATMENT_ORDER[b] ? a : b;\n}\n\n/**\n * Combine multiple lenses into one. Visibility uses AND by default (a room\n * must pass every lens to render); exit treatment uses most-restrictive when\n * children disagree. Both behaviours are configurable via {@link ComposeOptions}.\n *\n * Composing zero lenses returns a no-op equivalent to `ALL_VISIBLE`.\n */\nexport function composeLenses(...lenses: RoomLens[]): RoomLens;\nexport function composeLenses(options: ComposeOptions, ...lenses: RoomLens[]): RoomLens;\nexport function composeLenses(\n first?: ComposeOptions | RoomLens,\n ...rest: RoomLens[]\n): RoomLens {\n let options: ComposeOptions = {};\n let lenses: RoomLens[];\n if (first && typeof (first as RoomLens).isVisible === \"function\") {\n lenses = [first as RoomLens, ...rest];\n } else {\n options = (first as ComposeOptions) ?? {};\n lenses = rest;\n }\n\n const strategy = options.exitStrategy ?? \"most-restrictive\";\n const visibilityMode = options.visibility ?? \"and\";\n\n if (lenses.length === 0) {\n return {isVisible: () => true, getVersion: () => 0};\n }\n if (lenses.length === 1) {\n return lenses[0];\n }\n\n return {\n isVisible(room: MapData.Room) {\n if (visibilityMode === \"or\") {\n return lenses.some(l => l.isVisible(room));\n }\n return lenses.every(l => l.isVisible(room));\n },\n getExitTreatment(exit: IExit, a: MapData.Room, b: MapData.Room): ExitTreatment {\n let chosen: ExitTreatment | undefined;\n for (const lens of lenses) {\n const t = lens.getExitTreatment\n ? lens.getExitTreatment(exit, a, b)\n : defaultExitTreatment(lens, exit, a, b);\n if (chosen === undefined) {\n chosen = t;\n } else {\n chosen = pickTreatment(chosen, t, strategy);\n if (strategy === \"first\") break;\n }\n }\n return chosen ?? \"full\";\n },\n getVersion() {\n // Sum of children's versions — any child bump bumps the composite.\n let v = 0;\n for (const lens of lenses) v += lens.getVersion();\n return v;\n },\n };\n}\n","import type {RoomLens} from \"./RoomLens\";\n\n/**\n * Fog-of-war lens: rooms render only when their id is in the visited set.\n *\n * Default exit treatment is left to {@link RoomLens}'s built-in derivation\n * (both visible → full, one visible → stub, neither → hidden), which produces\n * the \"explored frontier\" look without any extra wiring.\n *\n * Mutations bump {@link getVersion} so a composing renderer can detect the\n * change; the renderer still needs an explicit `refresh()` after mutation —\n * the lens doesn't emit events.\n */\nexport class ExplorationLens implements RoomLens {\n private readonly visited: Set<number>;\n private version = 0;\n\n constructor(visited?: Iterable<number>) {\n this.visited = visited instanceof Set\n ? new Set(visited)\n : new Set(visited ?? []);\n }\n\n isVisible(room: MapData.Room): boolean {\n return this.visited.has(room.id);\n }\n\n getVersion(): number {\n return this.version;\n }\n\n hasVisited(roomId: number): boolean {\n return this.visited.has(roomId);\n }\n\n getVisitedRoomIds(): number[] {\n return [...this.visited];\n }\n\n getVisitedCount(): number {\n return this.visited.size;\n }\n\n /** Returns true if the room was newly added. */\n addVisited(roomId: number): boolean {\n if (this.visited.has(roomId)) return false;\n this.visited.add(roomId);\n this.version++;\n return true;\n }\n\n /** Returns the number of newly added rooms. */\n addVisitedAll(roomIds: Iterable<number>): number {\n let added = 0;\n for (const id of roomIds) {\n if (!this.visited.has(id)) {\n this.visited.add(id);\n added++;\n }\n }\n if (added > 0) this.version++;\n return added;\n }\n\n /** Replace the visited set wholesale. */\n setVisited(roomIds: Iterable<number>): void {\n this.visited.clear();\n for (const id of roomIds) this.visited.add(id);\n this.version++;\n }\n\n clear(): void {\n if (this.visited.size === 0) return;\n this.visited.clear();\n this.version++;\n }\n}\n","import Konva from \"konva\";\nimport type {IMapReader} from \"./reader/MapReader\";\nimport {PlanarDirection, planarDirections, oppositeDirections} from \"./directions\";\n\nconst directionNumberToName: Record<number, MapData.direction> = {\n 1: \"north\",\n 2: \"northeast\",\n 3: \"northwest\",\n 4: \"east\",\n 5: \"west\",\n 6: \"south\",\n 7: \"southeast\",\n 8: \"southwest\",\n 9: \"up\",\n 10: \"down\",\n 11: \"in\",\n 12: \"out\",\n};\n\ntype AreaConnection = {\n fromAreaId: number;\n toAreaId: number;\n fromRoomId: number;\n toRoomId: number;\n direction: PlanarDirection | null;\n fromRoomPosition: {x: number; y: number};\n toRoomPosition: {x: number; y: number};\n};\n\ntype AreaNode = {\n areaId: number;\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n connections: AreaConnection[];\n roomCount: number;\n // Real world center position (from room coordinates)\n realCenterX: number;\n realCenterY: number;\n};\n\ntype ConnectionGroup = {\n fromAreaId: number;\n toAreaId: number;\n connections: AreaConnection[];\n primaryDirection: PlanarDirection | null;\n averageOffset: {x: number; y: number};\n};\n\nexport type AreaDomainInfo = {\n isIshtar: boolean;\n isEmpire: boolean;\n};\n\nexport type DomainFilter = \"ishtar\" | \"empire\" | \"interdomain\" | \"all\";\n\nexport type AreaMapSettings = {\n areaWidth: number;\n areaHeight: number;\n areaSpacing: number;\n fontSize: number;\n connectionLineWidth: number;\n areaFillColor: string;\n areaStrokeColor: string;\n textColor: string;\n connectionColor: string;\n highlightColor: string;\n};\n\nexport function createAreaMapSettings(): AreaMapSettings {\n return {\n areaWidth: 150,\n areaHeight: 80,\n areaSpacing: 50,\n fontSize: 14,\n connectionLineWidth: 2,\n areaFillColor: \"#2a2a3e\",\n areaStrokeColor: \"#4a4a6e\",\n textColor: \"#e0e0e0\",\n connectionColor: \"#6a6a8e\",\n highlightColor: \"#ff9900\",\n };\n}\n\nexport class AreaMapRenderer {\n private readonly stage: Konva.Stage;\n private readonly backgroundLayer: Konva.Layer;\n private readonly areaLayer: Konva.Layer;\n private readonly connectionLayer: Konva.Layer;\n private readonly mapReader: IMapReader;\n private readonly settings: AreaMapSettings;\n private areaNodes: Map<number, AreaNode> = new Map();\n private connectionGroups: ConnectionGroup[] = [];\n private currentZoom = 1;\n private highlightedArea?: number;\n private domainInfo: Map<number, AreaDomainInfo> = new Map();\n private domainFilter: DomainFilter = \"all\";\n private backgroundImage?: Konva.Image;\n private backgroundConfig?: {url: string; x: number; y: number; width: number; height: number; opacity: number};\n private dotsMode = false;\n\n constructor(container: HTMLDivElement, mapReader: IMapReader, settings?: AreaMapSettings) {\n this.settings = settings ?? createAreaMapSettings();\n this.stage = new Konva.Stage({\n container: container,\n width: container.clientWidth,\n height: container.clientHeight,\n draggable: true,\n });\n\n this.backgroundLayer = new Konva.Layer({listening: false});\n this.stage.add(this.backgroundLayer);\n\n this.connectionLayer = new Konva.Layer({listening: false});\n this.stage.add(this.connectionLayer);\n\n this.areaLayer = new Konva.Layer();\n this.stage.add(this.areaLayer);\n\n this.mapReader = mapReader;\n\n this.initScaling();\n this.initResize(container);\n }\n\n private initResize(container: HTMLDivElement) {\n const resizeObserver = new ResizeObserver(() => {\n this.stage.width(container.clientWidth);\n this.stage.height(container.clientHeight);\n this.stage.batchDraw();\n });\n resizeObserver.observe(container);\n }\n\n private initScaling() {\n const scaleBy = 1.1;\n\n this.stage.on(\"wheel\", (e) => {\n e.evt.preventDefault();\n\n const oldScale = this.stage.scaleX();\n const pointer = this.stage.getPointerPosition();\n if (!pointer) return;\n\n const mousePointTo = {\n x: (pointer.x - this.stage.x()) / oldScale,\n y: (pointer.y - this.stage.y()) / oldScale,\n };\n\n const direction = e.evt.deltaY > 0 ? -1 : 1;\n const newZoom = direction > 0 ? this.currentZoom * scaleBy : this.currentZoom / scaleBy;\n\n if (this.setZoom(newZoom)) {\n const newScale = this.stage.scaleX();\n const newPos = {\n x: pointer.x - mousePointTo.x * newScale,\n y: pointer.y - mousePointTo.y * newScale,\n };\n\n this.stage.position(newPos);\n this.stage.batchDraw();\n }\n });\n }\n\n setZoom(zoom: number): boolean {\n const clamped = Math.max(0.1, zoom);\n if (this.currentZoom === clamped) {\n return false;\n }\n this.currentZoom = clamped;\n this.stage.scale({x: this.currentZoom, y: this.currentZoom});\n return true;\n }\n\n getZoom() {\n return this.currentZoom;\n }\n\n setDomainInfo(domainInfo: Record<number, AreaDomainInfo>) {\n this.domainInfo.clear();\n for (const [areaId, info] of Object.entries(domainInfo)) {\n this.domainInfo.set(Number(areaId), info);\n }\n }\n\n setDomainFilter(filter: DomainFilter) {\n this.domainFilter = filter;\n }\n\n getDomainFilter() {\n return this.domainFilter;\n }\n\n setBackgroundImage(config: {url: string; x: number; y: number; width: number; height: number; opacity?: number}) {\n this.backgroundConfig = {\n url: config.url,\n x: config.x,\n y: config.y,\n width: config.width,\n height: config.height,\n opacity: config.opacity ?? 0.3,\n };\n }\n\n clearBackgroundImage() {\n this.backgroundConfig = undefined;\n this.backgroundImage = undefined;\n this.backgroundLayer.destroyChildren();\n this.backgroundLayer.batchDraw();\n }\n\n redrawBackground() {\n this.drawBackground();\n }\n\n setDotsMode(enabled: boolean) {\n this.dotsMode = enabled;\n }\n\n getDotsMode() {\n return this.dotsMode;\n }\n\n redraw() {\n this.drawBackground();\n this.drawConnections();\n this.drawAreas();\n this.stage.batchDraw();\n }\n\n private isAreaInDomain(areaId: number): boolean {\n const info = this.domainInfo.get(areaId);\n if (!info) {\n // If no domain info, only show in \"interdomain\" or \"all\"\n return this.domainFilter === \"interdomain\" || this.domainFilter === \"all\";\n }\n\n switch (this.domainFilter) {\n case \"ishtar\":\n return info.isIshtar;\n case \"empire\":\n return info.isEmpire;\n case \"interdomain\":\n return !info.isIshtar && !info.isEmpire;\n case \"all\":\n return true;\n }\n }\n\n private areAreasInSameDomain(areaId1: number, areaId2: number): boolean {\n const info1 = this.domainInfo.get(areaId1);\n const info2 = this.domainInfo.get(areaId2);\n\n if (!info1 || !info2) {\n return false;\n }\n\n // Both in Ishtar\n if (info1.isIshtar && info2.isIshtar) return true;\n // Both in Empire\n if (info1.isEmpire && info2.isEmpire) return true;\n // Both interdomain (neither Ishtar nor Empire)\n return !info1.isIshtar && !info1.isEmpire && !info2.isIshtar && !info2.isEmpire;\n }\n\n render() {\n this.analyzeConnections();\n this.layoutAreas();\n this.drawBackground();\n this.drawConnections();\n this.drawAreas();\n this.centerView();\n this.stage.batchDraw();\n }\n\n private drawBackground() {\n this.backgroundLayer.destroyChildren();\n\n if (!this.backgroundConfig) return;\n\n const imageObj = new Image();\n imageObj.crossOrigin = \"anonymous\";\n\n imageObj.onload = () => {\n this.backgroundImage = new Konva.Image({\n x: this.backgroundConfig!.x,\n y: this.backgroundConfig!.y,\n image: imageObj,\n width: this.backgroundConfig!.width,\n height: this.backgroundConfig!.height,\n opacity: this.backgroundConfig!.opacity,\n listening: false,\n });\n\n this.backgroundLayer.add(this.backgroundImage);\n this.backgroundLayer.batchDraw();\n };\n\n imageObj.onerror = () => {\n console.error(\"Failed to load background image:\", this.backgroundConfig?.url);\n };\n\n imageObj.src = this.backgroundConfig.url;\n }\n\n private analyzeConnections() {\n this.areaNodes.clear();\n this.connectionGroups = [];\n\n const rooms = this.mapReader.getRooms();\n const areas = this.mapReader.getAreas();\n\n // Calculate bounds and center for each area (only z=0 rooms) to determine proportional sizes and positions\n const areaBounds = new Map<number, {minX: number; maxX: number; minY: number; maxY: number; centerX: number; centerY: number; roomCount: number}>();\n for (const area of areas) {\n const areaId = area.getAreaId();\n const areaRooms = area.getRooms();\n if (areaRooms.length === 0) continue;\n\n let minX = Infinity, maxX = -Infinity;\n let minY = Infinity, maxY = -Infinity;\n let z0RoomCount = 0;\n\n for (const room of areaRooms) {\n // Only count rooms at z=0\n if (room.z !== 0) continue;\n z0RoomCount++;\n minX = Math.min(minX, room.x);\n maxX = Math.max(maxX, room.x);\n minY = Math.min(minY, room.y);\n maxY = Math.max(maxY, room.y);\n }\n\n // If no z=0 rooms, use all rooms\n if (z0RoomCount === 0) {\n for (const room of areaRooms) {\n minX = Math.min(minX, room.x);\n maxX = Math.max(maxX, room.x);\n minY = Math.min(minY, room.y);\n maxY = Math.max(maxY, room.y);\n }\n }\n\n // Calculate center of the area\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n\n areaBounds.set(areaId, {minX, maxX, minY, maxY, centerX, centerY, roomCount: areaRooms.length});\n }\n\n // Find the scale factor to normalize area sizes\n // Use the largest area dimension as reference\n let maxDimension = 1;\n for (const bounds of areaBounds.values()) {\n const width = bounds.maxX - bounds.minX;\n const height = bounds.maxY - bounds.minY;\n maxDimension = Math.max(maxDimension, width, height);\n }\n\n // Scale factor: map the largest area to max size, others proportionally\n const maxAreaSize = 200; // Maximum rectangle dimension\n const minAreaSize = 100; // Minimum rectangle dimension\n const scaleFactor = maxAreaSize / maxDimension;\n\n // Create area nodes (skip areas with no rooms and filter by domain)\n for (const area of areas) {\n const areaId = area.getAreaId();\n const bounds = areaBounds.get(areaId);\n if (!bounds) continue;\n\n // Skip areas not in selected domain\n if (!this.isAreaInDomain(areaId)) {\n continue;\n }\n\n // Calculate proportional size\n const boundsWidth = bounds.maxX - bounds.minX;\n const boundsHeight = bounds.maxY - bounds.minY;\n\n // Scale proportionally, with minimum size\n let width = Math.max(minAreaSize, boundsWidth * scaleFactor);\n let height = Math.max(minAreaSize, boundsHeight * scaleFactor);\n\n // Ensure reasonable aspect ratio (not too extreme)\n const aspectRatio = width / height;\n if (aspectRatio > 3) {\n height = width / 3;\n } else if (aspectRatio < 1/3) {\n width = height / 3;\n }\n\n this.areaNodes.set(areaId, {\n areaId,\n name: area.getAreaName(),\n x: 0,\n y: 0,\n width,\n height,\n connections: [],\n roomCount: bounds.roomCount,\n realCenterX: bounds.centerX,\n realCenterY: bounds.centerY,\n });\n }\n\n // Find cross-area connections\n const connectionMap = new Map<string, AreaConnection[]>();\n\n for (const room of rooms) {\n const fromAreaId = room.area;\n // Skip if source area has no rooms (not in our nodes)\n if (!this.areaNodes.has(fromAreaId)) continue;\n\n const lockedDirections = this.getLockedDirections(room);\n const lockedSpecialTargets = new Set(room.mSpecialExitLocks ?? []);\n\n // Check regular exits\n for (const [dir, targetRoomId] of Object.entries(room.exits ?? {})) {\n if (lockedDirections.has(dir as MapData.direction)) continue;\n\n const targetRoom = this.mapReader.getRoom(targetRoomId);\n if (!targetRoom || targetRoom.area === fromAreaId) continue;\n // Skip if target area has no rooms (not in our nodes)\n if (!this.areaNodes.has(targetRoom.area)) continue;\n // Skip cross-domain connections\n if (!this.areAreasInSameDomain(fromAreaId, targetRoom.area)) continue;\n\n const connection = this.createConnection(\n room, targetRoom, dir as MapData.direction\n );\n if (connection) {\n this.addConnection(connectionMap, connection);\n }\n }\n\n // Check special exits\n for (const [cmd, targetRoomId] of Object.entries(room.specialExits ?? {})) {\n if (lockedSpecialTargets.has(targetRoomId)) continue;\n\n const targetRoom = this.mapReader.getRoom(targetRoomId);\n if (!targetRoom || targetRoom.area === fromAreaId) continue;\n // Skip if target area has no rooms (not in our nodes)\n if (!this.areaNodes.has(targetRoom.area)) continue;\n // Skip cross-domain connections\n if (!this.areAreasInSameDomain(fromAreaId, targetRoom.area)) continue;\n\n const direction = this.parseDirection(cmd);\n const connection = this.createConnection(room, targetRoom, direction);\n if (connection) {\n this.addConnection(connectionMap, connection);\n }\n }\n }\n\n // Group connections between same area pairs\n for (const [key, connections] of connectionMap) {\n const [fromId, toId] = key.split(\"-\").map(Number);\n const group = this.createConnectionGroup(fromId, toId, connections);\n this.connectionGroups.push(group);\n\n const fromNode = this.areaNodes.get(fromId);\n const toNode = this.areaNodes.get(toId);\n if (fromNode) fromNode.connections.push(...connections);\n if (toNode) {\n // Add reverse connections\n for (const conn of connections) {\n toNode.connections.push({\n ...conn,\n fromAreaId: conn.toAreaId,\n toAreaId: conn.fromAreaId,\n fromRoomId: conn.toRoomId,\n toRoomId: conn.fromRoomId,\n direction: conn.direction ? oppositeDirections[conn.direction] : null,\n fromRoomPosition: conn.toRoomPosition,\n toRoomPosition: conn.fromRoomPosition,\n });\n }\n }\n }\n }\n\n private getLockedDirections(room: MapData.Room): Set<MapData.direction> {\n return new Set(\n (room.exitLocks ?? [])\n .map((lockId) => directionNumberToName[lockId])\n .filter((d): d is MapData.direction => Boolean(d))\n );\n }\n\n private createConnection(\n fromRoom: MapData.Room,\n toRoom: MapData.Room,\n direction: MapData.direction | null\n ): AreaConnection | null {\n const planarDir = this.toPlanarDirection(direction);\n\n return {\n fromAreaId: fromRoom.area,\n toAreaId: toRoom.area,\n fromRoomId: fromRoom.id,\n toRoomId: toRoom.id,\n direction: planarDir,\n fromRoomPosition: {x: fromRoom.x, y: fromRoom.y},\n toRoomPosition: {x: toRoom.x, y: toRoom.y},\n };\n }\n\n private addConnection(map: Map<string, AreaConnection[]>, conn: AreaConnection) {\n // Use consistent key ordering (smaller id first)\n const key = conn.fromAreaId < conn.toAreaId\n ? `${conn.fromAreaId}-${conn.toAreaId}`\n : `${conn.toAreaId}-${conn.fromAreaId}`;\n\n if (!map.has(key)) {\n map.set(key, []);\n }\n map.get(key)!.push(conn);\n }\n\n private createConnectionGroup(\n fromAreaId: number,\n toAreaId: number,\n connections: AreaConnection[]\n ): ConnectionGroup {\n // Calculate primary direction based on most common direction\n const directionCounts = new Map<PlanarDirection, number>();\n let totalOffsetX = 0;\n let totalOffsetY = 0;\n\n for (const conn of connections) {\n if (conn.direction) {\n directionCounts.set(\n conn.direction,\n (directionCounts.get(conn.direction) ?? 0) + 1\n );\n }\n // Calculate offset based on room positions\n totalOffsetX += conn.toRoomPosition.x - conn.fromRoomPosition.x;\n totalOffsetY += conn.toRoomPosition.y - conn.fromRoomPosition.y;\n }\n\n let primaryDirection: PlanarDirection | null = null;\n let maxCount = 0;\n for (const [dir, count] of directionCounts) {\n if (count > maxCount) {\n maxCount = count;\n primaryDirection = dir;\n }\n }\n\n return {\n fromAreaId,\n toAreaId,\n connections,\n primaryDirection,\n averageOffset: {\n x: totalOffsetX / connections.length,\n y: totalOffsetY / connections.length,\n },\n };\n }\n\n private parseDirection(cmd: string): MapData.direction | null {\n const lower = cmd.toLowerCase().trim();\n const dirMap: Record<string, MapData.direction> = {\n n: \"north\", north: \"north\",\n s: \"south\", south: \"south\",\n e: \"east\", east: \"east\",\n w: \"west\", west: \"west\",\n ne: \"northeast\", northeast: \"northeast\",\n nw: \"northwest\", northwest: \"northwest\",\n se: \"southeast\", southeast: \"southeast\",\n sw: \"southwest\", southwest: \"southwest\",\n u: \"up\", up: \"up\",\n d: \"down\", down: \"down\",\n i: \"in\", in: \"in\",\n o: \"out\", out: \"out\",\n };\n return dirMap[lower] ?? null;\n }\n\n private toPlanarDirection(dir: MapData.direction | null): PlanarDirection | null {\n if (!dir) return null;\n if (planarDirections.includes(dir as PlanarDirection)) {\n return dir as PlanarDirection;\n }\n return null;\n }\n\n private layoutAreas() {\n const nodes = Array.from(this.areaNodes.values());\n if (nodes.length === 0) return;\n\n // Find connected components\n const components = this.findConnectedComponents();\n\n // Layout each component separately\n let componentOffsetX = 0;\n const componentGap = this.settings.areaWidth * 3;\n\n for (const component of components) {\n if (component.length === 1) {\n // Single isolated area\n const node = this.areaNodes.get(component[0]);\n if (node) {\n node.x = componentOffsetX;\n node.y = 0;\n }\n componentOffsetX += this.settings.areaWidth + componentGap;\n } else {\n // Use force-directed layout for connected areas\n const bounds = this.forceDirectedLayout(component);\n\n // Shift component to start at componentOffsetX\n for (const areaId of component) {\n const node = this.areaNodes.get(areaId);\n if (node && isFinite(bounds.minX) && isFinite(bounds.minY)) {\n node.x -= bounds.minX;\n node.x += componentOffsetX;\n node.y -= bounds.minY;\n }\n }\n\n const width = isFinite(bounds.maxX - bounds.minX) ? bounds.maxX - bounds.minX : this.settings.areaWidth;\n componentOffsetX += width + componentGap;\n }\n }\n }\n\n private findConnectedComponents(): number[][] {\n const visited = new Set<number>();\n const components: number[][] = [];\n\n for (const [areaId] of this.areaNodes) {\n if (visited.has(areaId)) continue;\n\n const component: number[] = [];\n const queue = [areaId];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n if (visited.has(current)) continue;\n\n visited.add(current);\n component.push(current);\n\n const node = this.areaNodes.get(current);\n if (node) {\n for (const conn of node.connections) {\n if (!visited.has(conn.toAreaId)) {\n queue.push(conn.toAreaId);\n }\n }\n }\n }\n\n components.push(component);\n }\n\n return components;\n }\n\n private forceDirectedLayout(areaIds: number[]): {minX: number; minY: number; maxX: number; maxY: number} {\n // Find unique connected areas for each node\n const connectionCount = new Map<number, Set<number>>();\n for (const id of areaIds) {\n const node = this.areaNodes.get(id);\n if (!node) continue;\n const connected = new Set(node.connections.map(c => c.toAreaId));\n connectionCount.set(id, connected);\n }\n\n // Hub detection: A hub is a node with 3+ connections (branching point)\n // Nodes with 1-2 connections are potential chain nodes\n // We need to trace chains and find their terminal hubs\n const hubNodes = new Set<number>();\n const processedInChain = new Set<number>();\n\n // First pass: mark obvious hubs (3+ connections)\n for (const [id, connected] of connectionCount) {\n if (connected.size >= 3) {\n hubNodes.add(id);\n }\n }\n\n // Second pass: find chains and identify their terminal hubs\n // A chain is a sequence of nodes each with <=2 connections\n // The chain ends at a hub (3+ connections) or a dead end (1 connection)\n for (const [startId, connected] of connectionCount) {\n if (hubNodes.has(startId) || processedInChain.has(startId)) continue;\n if (connected.size > 2) continue; // Already a hub\n\n // This node has 1-2 connections - trace the chain in both directions\n const chainMembers: number[] = [startId];\n processedInChain.add(startId);\n\n // Trace in both directions from this node\n const toVisit = Array.from(connected);\n for (const nextId of toVisit) {\n let currentId = nextId;\n let prevId = startId;\n\n while (currentId && !processedInChain.has(currentId) && !hubNodes.has(currentId)) {\n const currentConnections = connectionCount.get(currentId);\n if (!currentConnections) break;\n\n if (currentConnections.size >= 3) {\n // Found a hub at the end\n hubNodes.add(currentId);\n break;\n }\n\n chainMembers.push(currentId);\n processedInChain.add(currentId);\n\n // Find next node (not the one we came from)\n let foundNext = false;\n for (const neighborId of currentConnections) {\n if (neighborId !== prevId && !processedInChain.has(neighborId)) {\n prevId = currentId;\n currentId = neighborId;\n foundNext = true;\n break;\n }\n }\n if (!foundNext) break;\n }\n }\n\n // If chain has no hub connection, pick the first node as a pseudo-hub\n // (isolated chain or chain of single-connection nodes)\n let hasHubConnection = false;\n for (const memberId of chainMembers) {\n const memberConns = connectionCount.get(memberId);\n if (memberConns) {\n for (const connId of memberConns) {\n if (hubNodes.has(connId)) {\n hasHubConnection = true;\n break;\n }\n }\n }\n if (hasHubConnection) break;\n }\n\n if (!hasHubConnection && chainMembers.length > 0) {\n // Find the node with most connections to be the pseudo-hub\n let bestHub = chainMembers[0];\n let maxConns = connectionCount.get(chainMembers[0])?.size ?? 0;\n for (const memberId of chainMembers) {\n const conns = connectionCount.get(memberId)?.size ?? 0;\n if (conns > maxConns) {\n maxConns = conns;\n bestHub = memberId;\n }\n }\n hubNodes.add(bestHub);\n processedInChain.delete(bestHub);\n }\n }\n\n // Also add any isolated nodes (0 connections) as their own hubs\n for (const id of areaIds) {\n const connected = connectionCount.get(id);\n if (!connected || connected.size === 0) {\n hubNodes.add(id);\n }\n }\n\n // Build chains from each hub\n const nodeToCluster = new Map<number, number>(); // maps node to its hub\n const clusterChains = new Map<number, number[][]>(); // hub -> array of chains\n\n for (const hubId of hubNodes) {\n nodeToCluster.set(hubId, hubId);\n clusterChains.set(hubId, []);\n }\n\n // For each hub, find chains emanating from it\n const assignedToChain = new Set<number>(hubNodes);\n\n for (const hubId of hubNodes) {\n const hubConnections = connectionCount.get(hubId) ?? new Set();\n\n for (const connectedId of hubConnections) {\n // If already assigned (another hub or already in a chain), skip\n if (assignedToChain.has(connectedId)) continue;\n\n // Build chain starting from this connection\n const chain: number[] = [];\n let currentId = connectedId;\n let prevId = hubId;\n\n while (currentId && !assignedToChain.has(currentId)) {\n chain.push(currentId);\n nodeToCluster.set(currentId, hubId);\n assignedToChain.add(currentId);\n\n // Find next in chain (the one connection that isn't where we came from)\n const currentConnections = connectionCount.get(currentId) ?? new Set();\n let nextId: number | undefined;\n for (const nextConnId of currentConnections) {\n if (nextConnId !== prevId && !assignedToChain.has(nextConnId)) {\n nextId = nextConnId;\n break;\n }\n }\n\n prevId = currentId;\n currentId = nextId!;\n }\n\n if (chain.length > 0) {\n clusterChains.get(hubId)!.push(chain);\n }\n }\n }\n\n // Position hubs first using BFS\n const positioned = new Set<number>();\n const queue: number[] = [];\n\n // Start with first hub (or first node if no hubs)\n const firstHub = hubNodes.values().next().value;\n const startId = firstHub !== undefined ? firstHub : areaIds[0];\n if (startId !== undefined) {\n const startNode = this.areaNodes.get(startId);\n if (startNode) {\n startNode.x = 0;\n startNode.y = 0;\n positioned.add(startId);\n queue.push(startId);\n }\n }\n\n // BFS to position hubs based on connection directions\n // Use real-world positions only to refine ordering when multiple areas share same direction\n while (queue.length > 0) {\n const currentId = queue.shift()!;\n const currentNode = this.areaNodes.get(currentId)!;\n\n // Group connections by direction\n const connectionsByDir = new Map<string, Array<{id: number; node: AreaNode; conn: AreaConnection}>>();\n\n for (const conn of currentNode.connections) {\n const targetId = conn.toAreaId;\n if (positioned.has(targetId)) continue;\n if (!hubNodes.has(targetId)) continue;\n\n const targetNode = this.areaNodes.get(targetId);\n if (!targetNode) continue;\n\n const dirKey = conn.direction ?? \"none\";\n if (!connectionsByDir.has(dirKey)) {\n connectionsByDir.set(dirKey, []);\n }\n connectionsByDir.get(dirKey)!.push({id: targetId, node: targetNode, conn});\n }\n\n // Position each group, sorting by real-world position within the group\n for (const [dirKey, targets] of connectionsByDir) {\n const dirOffset = this.getDirectionOffset(dirKey === \"none\" ? null : dirKey as PlanarDirection);\n\n // Sort targets by their real-world position perpendicular to the direction\n // This ensures proper ordering when multiple areas are in the same direction\n targets.sort((a, b) => {\n if (Math.abs(dirOffset.x) > Math.abs(dirOffset.y)) {\n // Primarily horizontal direction - sort by Y (perpendicular)\n return b.node.realCenterY - a.node.realCenterY; // Higher Y = more north = lower index\n } else {\n // Primarily vertical direction - sort by X (perpendicular)\n return a.node.realCenterX - b.node.realCenterX; // Lower X = more west = lower index\n }\n });\n\n const baseSpacingX = this.settings.areaWidth + this.settings.areaSpacing;\n const baseSpacingY = this.settings.areaHeight + this.settings.areaSpacing;\n\n // Position each target with offset for multiple in same direction\n targets.forEach((target, index) => {\n target.node.x = currentNode.x + dirOffset.x * baseSpacingX;\n target.node.y = currentNode.y + dirOffset.y * baseSpacingY;\n\n // Add perpendicular offset for multiple areas in same direction\n if (targets.length > 1) {\n const perpOffset = (index - (targets.length - 1) / 2) * 0.5;\n if (Math.abs(dirOffset.x) > Math.abs(dirOffset.y)) {\n // Horizontal - offset vertically\n target.node.y += perpOffset * baseSpacingY;\n } else {\n // Vertical - offset horizontally\n target.node.x += perpOffset * baseSpacingX;\n }\n }\n\n positioned.add(target.id);\n queue.push(target.id);\n });\n }\n }\n\n // Apply forces only to hub nodes\n const hubArray = Array.from(hubNodes);\n if (hubArray.length > 1) {\n this.applyForces(hubArray, 80);\n }\n\n // Position chain nodes with appropriate distance based on direction\n // Add extra spacing to avoid overlaps with connection lines\n const horizontalDistance = this.settings.areaWidth + this.settings.areaSpacing + 30; // For east/west (230px)\n const verticalDistance = this.settings.areaHeight + this.settings.areaSpacing + 30; // For north/south (160px)\n\n // Store chain offsets relative to hub so we can reposition after hub moves\n const chainOffsets = new Map<number, {dx: number; dy: number; hubId: number}>();\n\n // Fallback directions for chains without explicit direction\n const fallbackDirections: PlanarDirection[] = [\n \"east\", \"west\", \"north\", \"south\",\n \"northeast\", \"northwest\", \"southeast\", \"southwest\"\n ];\n\n for (const [hubId, chains] of clusterChains) {\n const hubNode = this.areaNodes.get(hubId);\n if (!hubNode) continue;\n\n // Track which fallback direction to use for null-direction chains\n let fallbackIndex = 0;\n\n // Track how many chains use each direction to offset duplicates\n const directionUsageCount = new Map<string, number>();\n\n for (const chain of chains) {\n let prevId = hubId;\n let prevX = hubNode.x;\n let prevY = hubNode.y;\n let chainFallbackDirection: PlanarDirection | null = null;\n\n for (const chainNodeId of chain) {\n const chainNode = this.areaNodes.get(chainNodeId);\n if (!chainNode) continue;\n\n // Find direction from prev to this chain node\n const prevNodeData = this.areaNodes.get(prevId);\n let direction: PlanarDirection | null = null;\n if (prevNodeData) {\n for (const conn of prevNodeData.connections) {\n if (conn.toAreaId === chainNodeId) {\n direction = conn.direction;\n break;\n }\n }\n }\n\n // If no direction and this is first node in chain, use fallback\n if (!direction && prevId === hubId) {\n chainFallbackDirection = fallbackDirections[fallbackIndex % fallbackDirections.length];\n fallbackIndex++;\n direction = chainFallbackDirection;\n } else if (!direction && chainFallbackDirection) {\n // Continue in same fallback direction for rest of chain\n direction = chainFallbackDirection;\n }\n\n // Position relative to previous node with direction-appropriate distance\n const offset = this.getDirectionOffset(direction);\n\n // Check if this is first node in chain from hub - apply stacking offset for same direction\n let stackOffset = 0;\n if (prevId === hubId && direction) {\n const dirKey = direction;\n const usageCount = directionUsageCount.get(dirKey) ?? 0;\n directionUsageCount.set(dirKey, usageCount + 1);\n stackOffset = usageCount;\n }\n\n // Use horizontal distance for x component, vertical for y component\n chainNode.x = prevX + offset.x * horizontalDistance;\n chainNode.y = prevY + offset.y * verticalDistance;\n\n // Apply perpendicular offset if multiple chains in same direction\n if (stackOffset > 0) {\n // Offset perpendicular to the direction - use full area size + extra spacing\n const extraPadding = 1.2; // 20% extra spacing for multi-satellite hubs\n if (offset.x !== 0 && offset.y === 0) {\n // Horizontal direction - offset vertically\n chainNode.y += stackOffset * verticalDistance * extraPadding;\n } else if (offset.y !== 0 && offset.x === 0) {\n // Vertical direction - offset horizontally\n chainNode.x += stackOffset * horizontalDistance * extraPadding;\n } else {\n // Diagonal - offset along the perpendicular diagonal (full spacing)\n chainNode.x += stackOffset * horizontalDistance * extraPadding;\n chainNode.y -= stackOffset * verticalDistance * extraPadding;\n }\n }\n\n // Store offset from hub for later repositioning\n chainOffsets.set(chainNodeId, {\n dx: chainNode.x - hubNode.x,\n dy: chainNode.y - hubNode.y,\n hubId\n });\n\n prevId = chainNodeId;\n prevX = chainNode.x;\n prevY = chainNode.y;\n }\n }\n }\n\n // Calculate cluster bounds for each hub (hub + all its chains)\n const clusterBounds = new Map<number, {minX: number; minY: number; maxX: number; maxY: number}>();\n const clusterBoundsOffsets = new Map<number, {dx: number; dy: number}>();\n\n for (const hubId of hubNodes) {\n const hubNode = this.areaNodes.get(hubId);\n if (!hubNode) continue;\n\n let minX = hubNode.x;\n let minY = hubNode.y;\n let maxX = hubNode.x + hubNode.width;\n let maxY = hubNode.y + hubNode.height;\n\n const chains = clusterChains.get(hubId) ?? [];\n for (const chain of chains) {\n for (const chainNodeId of chain) {\n const chainNode = this.areaNodes.get(chainNodeId);\n if (!chainNode) continue;\n minX = Math.min(minX, chainNode.x);\n minY = Math.min(minY, chainNode.y);\n maxX = Math.max(maxX, chainNode.x + chainNode.width);\n maxY = Math.max(maxY, chainNode.y + chainNode.height);\n }\n }\n\n clusterBounds.set(hubId, {minX, minY, maxX, maxY});\n // Store offset from hub origin to bounds origin\n clusterBoundsOffsets.set(hubId, {dx: minX - hubNode.x, dy: minY - hubNode.y});\n }\n\n // Apply forces only to hubs, using cluster bounds for collision\n this.applyClusterForces(hubArray, clusterBounds, clusterBoundsOffsets, 60);\n\n // Reposition chain nodes based on updated hub positions\n for (const [chainNodeId, offset] of chainOffsets) {\n const chainNode = this.areaNodes.get(chainNodeId);\n const hubNode = this.areaNodes.get(offset.hubId);\n if (!chainNode || !hubNode) continue;\n\n chainNode.x = hubNode.x + offset.dx;\n chainNode.y = hubNode.y + offset.dy;\n }\n\n // Calculate bounds\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const areaId of areaIds) {\n const node = this.areaNodes.get(areaId)!;\n minX = Math.min(minX, node.x);\n minY = Math.min(minY, node.y);\n maxX = Math.max(maxX, node.x + node.width);\n maxY = Math.max(maxY, node.y + node.height);\n }\n\n return {minX, minY, maxX, maxY};\n }\n\n private getDirectionOffset(direction: PlanarDirection | null): {x: number; y: number} {\n // Use 0.75 for diagonals to keep them compact but not overlapping\n const offsets: 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: 0.75, y: -0.75},\n northwest: {x: -0.75, y: -0.75},\n southeast: {x: 0.75, y: 0.75},\n southwest: {x: -0.75, y: 0.75},\n };\n\n if (direction && offsets[direction]) {\n return offsets[direction];\n }\n\n // Default: random-ish offset for non-directional connections\n return {x: 1, y: 0};\n }\n\n private applyClusterForces(\n hubIds: number[],\n clusterBounds: Map<number, {minX: number; minY: number; maxX: number; maxY: number}>,\n clusterOffsets: Map<number, {dx: number; dy: number}>, // offset from hub origin to cluster bounds origin\n iterations: number\n ) {\n const damping = 0.8;\n const padding = 50; // Extra space between clusters\n\n const velocities = new Map<number, {vx: number; vy: number}>();\n for (const id of hubIds) {\n velocities.set(id, {vx: 0, vy: 0});\n }\n\n for (let iter = 0; iter < iterations; iter++) {\n // Update cluster bounds based on current hub positions\n for (const hubId of hubIds) {\n const hubNode = this.areaNodes.get(hubId);\n const bounds = clusterBounds.get(hubId);\n const offset = clusterOffsets.get(hubId);\n if (!hubNode || !bounds || !offset) continue;\n\n const width = bounds.maxX - bounds.minX;\n const height = bounds.maxY - bounds.minY;\n\n bounds.minX = hubNode.x + offset.dx;\n bounds.minY = hubNode.y + offset.dy;\n bounds.maxX = bounds.minX + width;\n bounds.maxY = bounds.minY + height;\n }\n\n for (const id of hubIds) {\n const node = this.areaNodes.get(id);\n const vel = velocities.get(id);\n const bounds = clusterBounds.get(id);\n if (!node || !vel || !bounds) continue;\n\n // Repulsion from other clusters using AABB overlap check\n for (const otherId of hubIds) {\n if (id === otherId) continue;\n const otherBounds = clusterBounds.get(otherId);\n if (!otherBounds) continue;\n\n // Calculate centers\n const cx1 = (bounds.minX + bounds.maxX) / 2;\n const cy1 = (bounds.minY + bounds.maxY) / 2;\n const cx2 = (otherBounds.minX + otherBounds.maxX) / 2;\n const cy2 = (otherBounds.minY + otherBounds.maxY) / 2;\n\n const dx = cx1 - cx2;\n const dy = cy1 - cy2;\n\n // Required distance based on actual rectangles (half-widths and half-heights)\n const hw1 = (bounds.maxX - bounds.minX) / 2;\n const hh1 = (bounds.maxY - bounds.minY) / 2;\n const hw2 = (otherBounds.maxX - otherBounds.minX) / 2;\n const hh2 = (otherBounds.maxY - otherBounds.minY) / 2;\n\n // Minimum distance to avoid overlap (axis-aligned rectangles)\n const sepX = hw1 + hw2 + padding;\n const sepY = hh1 + hh2 + padding;\n\n // Check if overlapping\n const overlapX = sepX - Math.abs(dx);\n const overlapY = sepY - Math.abs(dy);\n\n if (overlapX > 0 && overlapY > 0) {\n // Overlapping - push apart along the axis with smaller overlap\n if (overlapX < overlapY) {\n const pushX = overlapX * (dx >= 0 ? 1 : -1) * 0.5;\n vel.vx += pushX;\n } else {\n const pushY = overlapY * (dy >= 0 ? 1 : -1) * 0.5;\n vel.vy += pushY;\n }\n }\n }\n\n // Spring force toward connected hubs (gentle)\n for (const conn of node.connections) {\n const other = this.areaNodes.get(conn.toAreaId);\n if (!other || !hubIds.includes(conn.toAreaId)) continue;\n\n const dx = other.x - node.x;\n const dy = other.y - node.y;\n const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n\n const idealDistance = this.settings.areaWidth + this.settings.areaSpacing + 50;\n const displacement = dist - idealDistance;\n const springForce = displacement * 0.015;\n vel.vx += (dx / dist) * springForce;\n vel.vy += (dy / dist) * springForce;\n }\n }\n\n // Apply velocities\n for (const id of hubIds) {\n const node = this.areaNodes.get(id);\n const vel = velocities.get(id);\n if (!node || !vel) continue;\n\n node.x += vel.vx;\n node.y += vel.vy;\n\n vel.vx *= damping;\n vel.vy *= damping;\n }\n }\n }\n\n private applyForces(areaIds: number[], iterations: number) {\n const damping = 0.8;\n const padding = 20; // Minimum gap between areas\n const idealDistance = this.settings.areaWidth + this.settings.areaSpacing; // 200px - same as chain spacing\n const lineAvoidanceDistance = this.settings.areaWidth / 2 + 60; // Increased to push nodes further from lines\n\n // Build list of edges for line avoidance (only between nodes in areaIds)\n const areaIdSet = new Set(areaIds);\n const edges: Array<{from: number; to: number}> = [];\n const edgeSet = new Set<string>();\n for (const id of areaIds) {\n const node = this.areaNodes.get(id);\n if (!node) continue;\n for (const conn of node.connections) {\n if (!areaIdSet.has(conn.toAreaId)) continue;\n const key = Math.min(id, conn.toAreaId) + \"-\" + Math.max(id, conn.toAreaId);\n if (!edgeSet.has(key)) {\n edgeSet.add(key);\n edges.push({from: id, to: conn.toAreaId});\n }\n }\n }\n\n const velocities = new Map<number, {vx: number; vy: number}>();\n for (const id of areaIds) {\n velocities.set(id, {vx: 0, vy: 0});\n }\n\n for (let iter = 0; iter < iterations; iter++) {\n // Calculate forces\n for (const id of areaIds) {\n const node = this.areaNodes.get(id);\n const vel = velocities.get(id);\n if (!node || !vel) continue;\n\n const nodeCenterX = node.x + node.width / 2;\n const nodeCenterY = node.y + node.height / 2;\n\n // Repulsion from other nodes using AABB overlap detection\n for (const otherId of areaIds) {\n if (id === otherId) continue;\n const other = this.areaNodes.get(otherId);\n if (!other) continue;\n\n // Calculate required separation (half-widths + half-heights + padding)\n const minSepX = (node.width + other.width) / 2 + padding;\n const minSepY = (node.height + other.height) / 2 + padding;\n\n // Calculate center-to-center distance\n const cx1 = node.x + node.width / 2;\n const cy1 = node.y + node.height / 2;\n const cx2 = other.x + other.width / 2;\n const cy2 = other.y + other.height / 2;\n\n const dx = cx1 - cx2;\n const dy = cy1 - cy2;\n\n // Check overlap on each axis\n const overlapX = minSepX - Math.abs(dx);\n const overlapY = minSepY - Math.abs(dy);\n\n if (overlapX > 0 && overlapY > 0) {\n // Overlapping - push apart along the axis with smaller overlap\n if (overlapX < overlapY) {\n vel.vx += (dx >= 0 ? 1 : -1) * overlapX * 0.5;\n } else {\n vel.vy += (dy >= 0 ? 1 : -1) * overlapY * 0.5;\n }\n }\n }\n\n // Repulsion from edges (lines) that this node is not part of\n for (const edge of edges) {\n if (edge.from === id || edge.to === id) continue;\n\n const fromNode = this.areaNodes.get(edge.from);\n const toNode = this.areaNodes.get(edge.to);\n if (!fromNode || !toNode) continue;\n\n const fromX = fromNode.x + fromNode.width / 2;\n const fromY = fromNode.y + fromNode.height / 2;\n const toX = toNode.x + toNode.width / 2;\n const toY = toNode.y + toNode.height / 2;\n\n const closest = this.closestPointOnSegment(\n nodeCenterX, nodeCenterY,\n fromX, fromY, toX, toY\n );\n\n const dx = nodeCenterX - closest.x;\n const dy = nodeCenterY - closest.y;\n const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n\n if (dist < lineAvoidanceDistance) {\n const repulsion = (lineAvoidanceDistance - dist) * 0.6; // Stronger push away from lines\n vel.vx += (dx / dist) * repulsion;\n vel.vy += (dy / dist) * repulsion;\n }\n }\n\n // Spring force for connected nodes\n for (const conn of node.connections) {\n if (!areaIdSet.has(conn.toAreaId)) continue;\n const other = this.areaNodes.get(conn.toAreaId);\n if (!other) continue;\n\n const dx = other.x - node.x;\n const dy = other.y - node.y;\n const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n\n const displacement = dist - idealDistance;\n const springForce = displacement * 0.03;\n vel.vx += (dx / dist) * springForce;\n vel.vy += (dy / dist) * springForce;\n\n // Directional hint\n const targetOffset = this.getDirectionOffset(conn.direction);\n const idealX = node.x + targetOffset.x * idealDistance;\n const idealY = node.y + targetOffset.y * idealDistance;\n\n const otherVel = velocities.get(conn.toAreaId);\n if (otherVel) {\n otherVel.vx += (idealX - other.x) * 0.01;\n otherVel.vy += (idealY - other.y) * 0.01;\n }\n }\n }\n\n // Apply velocities with damping\n for (const id of areaIds) {\n const node = this.areaNodes.get(id);\n const vel = velocities.get(id);\n if (!node || !vel) continue;\n\n node.x += vel.vx;\n node.y += vel.vy;\n\n vel.vx *= damping;\n vel.vy *= damping;\n }\n }\n }\n\n private closestPointOnSegment(\n px: number, py: number,\n ax: number, ay: number,\n bx: number, by: number\n ): {x: number; y: number} {\n const abx = bx - ax;\n const aby = by - ay;\n const apx = px - ax;\n const apy = py - ay;\n\n const abLenSq = abx * abx + aby * aby;\n if (abLenSq === 0) {\n return {x: ax, y: ay};\n }\n\n let t = (apx * abx + apy * aby) / abLenSq;\n t = Math.max(0, Math.min(1, t));\n\n return {\n x: ax + t * abx,\n y: ay + t * aby,\n };\n }\n\n private edgesIntersect(\n ax: number, ay: number, bx: number, by: number,\n cx: number, cy: number, dx: number, dy: number\n ): boolean {\n // Check if line segment AB intersects with line segment CD\n const ccw = (p1x: number, p1y: number, p2x: number, p2y: number, p3x: number, p3y: number) => {\n return (p3y - p1y) * (p2x - p1x) > (p2y - p1y) * (p3x - p1x);\n };\n\n return (\n ccw(ax, ay, cx, cy, dx, dy) !== ccw(bx, by, cx, cy, dx, dy) &&\n ccw(ax, ay, bx, by, cx, cy) !== ccw(ax, ay, bx, by, dx, dy)\n );\n }\n\n private drawConnections() {\n this.connectionLayer.destroyChildren();\n\n for (const group of this.connectionGroups) {\n const fromNode = this.areaNodes.get(group.fromAreaId);\n const toNode = this.areaNodes.get(group.toAreaId);\n if (!fromNode || !toNode) continue;\n\n // Skip if positions are invalid\n if (!isFinite(fromNode.x) || !isFinite(fromNode.y) ||\n !isFinite(toNode.x) || !isFinite(toNode.y)) {\n continue;\n }\n\n const fromCenter = {\n x: fromNode.x + fromNode.width / 2,\n y: fromNode.y + fromNode.height / 2,\n };\n const toCenter = {\n x: toNode.x + toNode.width / 2,\n y: toNode.y + toNode.height / 2,\n };\n\n // In dots mode, draw from center to center; otherwise edge to edge\n let fromPoint: {x: number; y: number};\n let toPoint: {x: number; y: number};\n\n if (this.dotsMode) {\n fromPoint = fromCenter;\n toPoint = toCenter;\n } else {\n fromPoint = this.getEdgePoint(fromNode, toCenter);\n toPoint = this.getEdgePoint(toNode, fromCenter);\n }\n\n // Skip if points are invalid\n if (!isFinite(fromPoint.x) || !isFinite(fromPoint.y) ||\n !isFinite(toPoint.x) || !isFinite(toPoint.y)) {\n continue;\n }\n\n const lineColor = this.dotsMode ? \"#ffffff\" : this.settings.connectionColor;\n const lineWidth = this.dotsMode ? 1 : Math.min(this.settings.connectionLineWidth, 1 + group.connections.length * 0.5);\n\n const line = new Konva.Line({\n points: [fromPoint.x, fromPoint.y, toPoint.x, toPoint.y],\n stroke: lineColor,\n strokeWidth: lineWidth,\n lineCap: \"round\",\n listening: false,\n });\n\n this.connectionLayer.add(line);\n }\n }\n\n private getEdgePoint(node: AreaNode, target: {x: number; y: number}): {x: number; y: number} {\n const cx = node.x + node.width / 2;\n const cy = node.y + node.height / 2;\n\n const dx = target.x - cx;\n const dy = target.y - cy;\n\n const hw = node.width / 2;\n const hh = node.height / 2;\n\n // Calculate intersection with rectangle edges\n if (dx === 0 && dy === 0) {\n return {x: cx, y: cy};\n }\n\n const absDx = Math.abs(dx);\n const absDy = Math.abs(dy);\n\n let scale: number;\n if (absDx / hw > absDy / hh) {\n scale = hw / absDx;\n } else {\n scale = hh / absDy;\n }\n\n return {\n x: cx + dx * scale,\n y: cy + dy * scale,\n };\n }\n\n private drawAreas() {\n this.areaLayer.destroyChildren();\n\n for (const [, node] of this.areaNodes) {\n // Skip if position is invalid\n if (!isFinite(node.x) || !isFinite(node.y)) {\n continue;\n }\n\n const isHighlighted = node.areaId === this.highlightedArea;\n\n if (this.dotsMode) {\n // Dots mode: small circle with text label\n const dotRadius = 6;\n const centerX = node.x + node.width / 2;\n const centerY = node.y + node.height / 2;\n\n const group = new Konva.Group({\n x: centerX,\n y: centerY,\n draggable: true,\n });\n\n const dot = new Konva.Circle({\n radius: dotRadius,\n fill: isHighlighted ? this.settings.highlightColor : \"#ffffff\",\n stroke: isHighlighted ? this.settings.highlightColor : \"#ffffff\",\n strokeWidth: 1,\n });\n\n const label = new Konva.Text({\n text: node.name,\n fontSize: 10,\n fill: \"#ffffff\",\n x: dotRadius + 4,\n y: -5,\n });\n\n group.add(dot);\n group.add(label);\n\n // Add click handler\n group.on(\"click tap\", () => {\n this.emitAreaClickEvent(node.areaId);\n });\n\n group.on(\"mouseenter\", () => {\n this.stage.container().style.cursor = \"pointer\";\n dot.fill(this.settings.highlightColor);\n dot.stroke(this.settings.highlightColor);\n this.areaLayer.batchDraw();\n });\n\n group.on(\"mouseleave\", () => {\n this.stage.container().style.cursor = \"auto\";\n dot.fill(isHighlighted ? this.settings.highlightColor : \"#ffffff\");\n dot.stroke(isHighlighted ? this.settings.highlightColor : \"#ffffff\");\n this.areaLayer.batchDraw();\n });\n\n group.on(\"dragmove\", () => {\n // Update node position (offset by center)\n node.x = group.x() - node.width / 2;\n node.y = group.y() - node.height / 2;\n this.drawConnections();\n this.connectionLayer.batchDraw();\n });\n\n group.on(\"dragstart\", () => {\n this.stage.container().style.cursor = \"grabbing\";\n });\n\n group.on(\"dragend\", () => {\n this.stage.container().style.cursor = \"pointer\";\n });\n\n this.areaLayer.add(group);\n } else {\n // Normal mode: rectangle with text\n const group = new Konva.Group({\n x: node.x,\n y: node.y,\n draggable: true,\n });\n\n const rect = new Konva.Rect({\n width: node.width,\n height: node.height,\n fill: this.settings.areaFillColor,\n stroke: isHighlighted ? this.settings.highlightColor : this.settings.areaStrokeColor,\n strokeWidth: isHighlighted ? 3 : 2,\n cornerRadius: 8,\n });\n\n const name = new Konva.Text({\n text: node.name,\n fontSize: this.settings.fontSize,\n fill: this.settings.textColor,\n width: node.width - 10,\n height: node.height - 20,\n x: 5,\n y: 10,\n align: \"center\",\n verticalAlign: \"middle\",\n ellipsis: true,\n wrap: \"word\",\n });\n\n const roomCount = new Konva.Text({\n text: `${node.roomCount} rooms`,\n fontSize: 10,\n fill: this.settings.connectionColor,\n width: node.width - 10,\n x: 5,\n y: node.height - 18,\n align: \"center\",\n });\n\n group.add(rect);\n group.add(name);\n group.add(roomCount);\n\n // Add click handler\n group.on(\"click tap\", () => {\n this.emitAreaClickEvent(node.areaId);\n });\n\n group.on(\"mouseenter\", () => {\n this.stage.container().style.cursor = \"pointer\";\n rect.stroke(this.settings.highlightColor);\n this.areaLayer.batchDraw();\n });\n\n group.on(\"mouseleave\", () => {\n this.stage.container().style.cursor = \"auto\";\n rect.stroke(isHighlighted ? this.settings.highlightColor : this.settings.areaStrokeColor);\n this.areaLayer.batchDraw();\n });\n\n group.on(\"dragmove\", () => {\n node.x = group.x();\n node.y = group.y();\n this.drawConnections();\n this.connectionLayer.batchDraw();\n });\n\n group.on(\"dragstart\", () => {\n this.stage.container().style.cursor = \"grabbing\";\n });\n\n group.on(\"dragend\", () => {\n this.stage.container().style.cursor = \"pointer\";\n });\n\n this.areaLayer.add(group);\n }\n }\n }\n\n private centerView() {\n // Calculate bounds of all areas\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n\n for (const [, node] of this.areaNodes) {\n minX = Math.min(minX, node.x);\n minY = Math.min(minY, node.y);\n maxX = Math.max(maxX, node.x + node.width);\n maxY = Math.max(maxY, node.y + node.height);\n }\n\n if (!isFinite(minX)) return;\n\n const contentWidth = maxX - minX;\n const contentHeight = maxY - minY;\n const centerX = minX + contentWidth / 2;\n const centerY = minY + contentHeight / 2;\n\n // Calculate zoom to fit\n const padding = 50;\n const scaleX = (this.stage.width() - padding * 2) / contentWidth;\n const scaleY = (this.stage.height() - padding * 2) / contentHeight;\n const fitZoom = Math.min(scaleX, scaleY, 1.5);\n\n this.setZoom(fitZoom);\n\n // Center the view\n const scale = this.stage.scaleX();\n this.stage.position({\n x: this.stage.width() / 2 - centerX * scale,\n y: this.stage.height() / 2 - centerY * scale,\n });\n }\n\n highlightArea(areaId: number | undefined) {\n this.highlightedArea = areaId;\n this.drawAreas();\n this.stage.batchDraw();\n }\n\n centerOnArea(areaId: number) {\n const node = this.areaNodes.get(areaId);\n if (!node) return;\n\n const scale = this.stage.scaleX();\n const centerX = node.x + node.width / 2;\n const centerY = node.y + node.height / 2;\n\n this.stage.position({\n x: this.stage.width() / 2 - centerX * scale,\n y: this.stage.height() / 2 - centerY * scale,\n });\n\n this.stage.batchDraw();\n }\n\n private emitAreaClickEvent(areaId: number) {\n const event = new CustomEvent<{areaId: number}>(\"areaclick\", {\n detail: {areaId},\n });\n this.stage.container().dispatchEvent(event);\n }\n\n getAreaNode(areaId: number): AreaNode | undefined {\n return this.areaNodes.get(areaId);\n }\n\n getConnectionGroups(): ConnectionGroup[] {\n return this.connectionGroups;\n }\n\n destroy() {\n this.stage.destroy();\n }\n}\n"],"x_google_ignoreList":[60,61,62,63,64],"mappings":"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"}
|