mudlet-map-renderer 2.2.0 → 2.3.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/README.md +28 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.mjs +913 -485
- package/dist/index.mjs.map +1 -1
- package/dist/labelPlacement.d.ts +132 -0
- package/dist/overlay/RippleEffect.d.ts +48 -0
- package/dist/style/index.d.ts +14 -0
- package/dist/style/shape/DarkModernStyle.d.ts +15 -0
- package/dist/style/shape/TreasureMapStyle.d.ts +23 -0
- package/dist/style/shape/index.d.ts +2 -0
- 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/MapState.ts","../src/scene/elements/GridLayout.ts","../src/export/clipSceneToViewport.ts","../src/rendering/SceneManager.ts","../src/render/canvasGradient.ts","../src/render/RecordingLayer.ts","../src/scene/Shape.ts","../src/render/shapeToRecording.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/shape/StainedGlassStyle.ts","../src/style/shape/GraphPaperStyle.ts","../src/style/shape/TopographicStyle.ts","../src/style/shape/WatercolorStyle.ts","../src/style/index.ts","../src/rendering/MapRenderer.ts","../src/draw/DrawCommandBuilder.ts","../src/render/SvgRenderer.ts","../src/render/CanvasRenderer.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 * @deprecated Use {@link shape} instead. Only consulted when `shape` is\n * `'match'` (or omitted): when true (the default) the highlight follows the\n * current roomShape (rectangle / roundedRectangle / circle); when false it\n * is always a circle.\n */\n matchRoomShape?: boolean;\n\n /**\n * Outline shape of the highlight. `'match'` (the default when omitted)\n * follows the current roomShape; the other values force that specific shape\n * regardless of the room's shape.\n */\n shape?: 'match' | 'rectangle' | 'roundedRectangle' | 'circle';\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 shape: 'match',\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","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 = {\n /** Highlight colours; one draws a ring, two or more split it into pie wedges. */\n colors: string[];\n /** @deprecated Use {@link colors}. Kept for back-compat; equals `colors[0]`. */\n color: string;\n area: number;\n z: number;\n};\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: {\n roomId: number;\n colors: string[] | undefined;\n /** @deprecated Use {@link colors}. Equals `colors?.[0]`. */\n color: string | undefined;\n };\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 | string[]): boolean {\n const room = this.mapReader.getRoom(roomId);\n if (!room) return false;\n const colors = Array.isArray(color) ? (color.length > 0 ? [...color] : ['#ffffff']) : [color];\n this.highlights.set(roomId, {colors, color: colors[0], area: room.area, z: room.z});\n this.events.emit('highlight', {roomId, colors, color: colors[0]});\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, colors: undefined, 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 | 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.colors});\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 {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","/**\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 * Resolve a paint's effective dash pattern. Returns the dash only when it is\n * present AND not explicitly disabled (`dashEnabled === false` blanks it).\n * Renderers call this so every shape kind — rect, circle, line — honours\n * `dashEnabled` identically.\n */\nexport function resolveDash(\n dash: number[] | undefined,\n dashEnabled: boolean | undefined,\n): number[] | undefined {\n return (dashEnabled !== false && dash) ? dash : undefined;\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, resolveDash, 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: resolveDash(shape.paint.dash, shape.paint.dashEnabled),\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: resolveDash(shape.paint.dash, shape.paint.dashEnabled),\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: resolveDash(shape.paint.dash, shape.paint.dashEnabled),\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 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 {LayerOffset} 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 private coordinateLayerOffset: LayerOffset | undefined;\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 const layerOffset: LayerOffset | undefined = style.sceneLayerOffset\n ? (layer) => style.sceneLayerOffset!(layer)\n : undefined;\n\n this._coordinateTransform = forward;\n this.coordinateInverse = newInverse;\n this.coordinateLayerOffset = layerOffset;\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, layerOffset);\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, overlay.sceneSpace);\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, colors}) => {\n this.syncHighlight(roomId, colors);\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 * `bypassStyle` skips the Style transform entirely — used for overlays whose\n * geometry is already in rendered space (e.g. hit-area debug visualisation).\n */\n private addStyledShape(\n shape: Shape,\n layerNode: {addNode(node: RecordingGroupNode): void},\n bypassStyle = false,\n ): RecordingGroupNode | undefined {\n const styled = bypassStyle || 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, this.coordinateLayerOffset);\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, colors: string[] | undefined) {\n const existing = this.highlightShapes.get(roomId);\n if (existing) {\n existing.destroy();\n this.highlightShapes.delete(roomId);\n }\n if (colors !== 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, colors, 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.colors, 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\n/**\n * Convert 0..255 RGB to HSL with hue in degrees and saturation/lightness in\n * [0, 1]. Achromatic colours report hue 0. Shared by the newer styles\n * (StainedGlass, …); Neon and SciFi keep their own local copies.\n */\nexport function 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\n/** Build an `rgb(...)` / `rgba(...)` string from HSL (hue degrees, s/l in [0,1]). */\nexport function 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 return formatRgb(\n Math.round((r + m) * 255),\n Math.round((g + m) * 255),\n Math.round((b + m) * 255),\n a,\n );\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 // Link-layer groups are lowered by the cube depth in render space (see\n // the group case in `transform`). Report that here so HitTester places\n // exit/stub hit zones on the drawn connectors, not a cube-height above.\n sceneLayerOffset(layer) {\n return depth > 0 && layer === \"link\" ? {x: 0, y: depth} : {x: 0, y: 0};\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","import type {Shape, Paint, FillStyle} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {hslToRgbString, mapFill, parseRgb, rgbToHsl} from \"./paintMap\";\n\n/** Near-black \"came\" colour for the lead between panes. */\nconst LEADING = \"#0a0a0a\";\n/** Pale glass-white used for text so it reads over dark leading. */\nconst TEXT_COLOR = \"#f4f0e0\";\n\n/** Minimum saturation a pane gets — turns dull rooms into coloured glass. */\nconst MIN_SATURATION = 0.6;\n/** Lightness window kept so panes glow rather than wash out or go muddy. */\nconst MIN_LIGHTNESS = 0.4;\nconst MAX_LIGHTNESS = 0.62;\n/** Stroke-width multiplier for the leading, with a floor so thin edges still lead. */\nconst LEADING_MULT = 2.2;\nconst LEADING_MIN = 0.06;\n\n/** Saturate a colour into a jewel-toned glass pane, preserving hue + alpha. */\nfunction toGlass(color: string): string {\n const c = parseRgb(color);\n if (!c) return color;\n const [h, s, l] = rgbToHsl(c.r, c.g, c.b);\n // Achromatic panes (grey) keep their neutral look — they read as frosted glass.\n const sat = s < 0.08 ? s : Math.max(s, MIN_SATURATION);\n const light = Math.min(MAX_LIGHTNESS, Math.max(MIN_LIGHTNESS, l));\n return hslToRgbString(h, sat, light, c.a);\n}\n\n/** Leading width for a pane, scaled up from the original stroke. */\nfunction leadingWidth(strokeWidth: number | undefined): number {\n return Math.max((strokeWidth ?? 0.04) * LEADING_MULT, LEADING_MIN);\n}\n\n/**\n * Rewrite a filled shape's paint into a saturated pane with fat dark leading.\n * Panes always get leading even if the source had no stroke, so every room is\n * framed like a piece of glass.\n */\nfunction glassPaint(paint: Paint): Paint {\n const fill: FillStyle | undefined = mapFill(paint.fill, toGlass);\n const leaded = fill !== undefined || paint.stroke !== undefined;\n return {\n ...paint,\n fill,\n stroke: leaded ? LEADING : undefined,\n strokeWidth: leaded ? leadingWidth(paint.strokeWidth) : paint.strokeWidth,\n // Leading is opaque cames — drop any dash so the frame stays solid.\n dash: undefined,\n dashEnabled: undefined,\n };\n}\n\n/**\n * Stained-glass aesthetic as a {@link Style} — jewel-toned panes separated by\n * thick near-black leading, glowing on a dark backdrop.\n *\n * - Fills are pushed to high saturation in a mid-lightness window so rooms read\n * as coloured glass; grey rooms stay neutral (frosted panes). Gradient fills\n * recolour per stop.\n * - Strokes become fat near-black leading; filled panes gain leading even when\n * the source had none.\n * - Exit lines become slim leading too, tying the panes together.\n * - Text uses pale glass-white for legibility over the dark cames.\n * - Images and groups pass through unchanged.\n */\nexport const stainedGlassShapeStyle: Style = {\n transform(shape: Shape): Shape {\n switch (shape.type) {\n case \"rect\":\n case \"circle\":\n case \"polygon\":\n return {...shape, paint: glassPaint(shape.paint)};\n case \"line\":\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {\n ...shape.paint,\n stroke: LEADING,\n strokeWidth: leadingWidth(shape.paint.strokeWidth),\n }\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} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {formatRgb, luminance, mapFill, parseRgb} from \"./paintMap\";\n\n/** Dark navy ink for room outlines, exits, and text. */\nconst INK = \"#1c3f6e\";\n/** Slightly fattened ink so outlines read like a felt-tip on graph paper. */\nconst INK_MULT = 1.4;\nconst INK_MIN = 0.05;\n\n// Room fill range: dark rooms become pale slate-blue, light rooms near-white.\n// The whole range stays light so navy ink and the blue grid stay legible.\nconst DARK_R = 198, DARK_G = 212, DARK_B = 230;\nconst LIGHT_R = 248, LIGHT_G = 250, LIGHT_B = 255;\n\n/** Map a colour to the pale graph-paper fill range by luminance. */\nfunction toPaper(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 stroke to navy 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\nfunction inkWidth(strokeWidth: number | undefined): number {\n return Math.max((strokeWidth ?? 0.04) * INK_MULT, INK_MIN);\n}\n\nfunction rewriteFillStroke(paint: Paint): Paint {\n return {\n ...paint,\n fill: mapFill(paint.fill, toPaper),\n stroke: paint.stroke ? toInk(paint.stroke) : paint.stroke,\n strokeWidth: paint.stroke ? inkWidth(paint.strokeWidth) : paint.strokeWidth,\n };\n}\n\n/**\n * Old-school graph-paper dungeon aesthetic as a {@link Style} — the classic\n * hand-drawn D&D look: pale rooms inked in navy over blue grid paper.\n *\n * - Fills map to a pale slate-blue → near-white range by luminance, so rooms\n * stay light and the blue grid reads through. Gradient stops recolour per stop.\n * - Strokes become fattened navy ink.\n * - Exit lines become navy ink (kept thin where the source was thin).\n * - Text uses navy ink.\n * - Images and groups pass through unchanged.\n *\n * Pair with a cream/white background and a light-blue grid in settings for the\n * full graph-paper effect.\n */\nexport const graphPaperShapeStyle: 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 if (!shape.paint.stroke) return shape;\n // Grid lines stay thin; room/exit connectors get the inked width.\n return {\n ...shape,\n paint: {\n ...shape.paint,\n stroke: toInk(shape.paint.stroke),\n strokeWidth: shape.grid ? shape.paint.strokeWidth : inkWidth(shape.paint.strokeWidth),\n },\n };\n }\n case \"text\":\n return {...shape, fill: INK};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","import type {Shape, Paint, RectShape, CircleShape, PolygonShape} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {formatRgb, luminance, mapFill, parseRgb} from \"./paintMap\";\n\n/** Contour-line brown drawn as concentric rings inside each room. */\nconst CONTOUR = \"#7a6a48\";\n/** Muted brown for exit lines. */\nconst PATH = \"#8a7a55\";\n/** Dark sepia for text. */\nconst TEXT_COLOR = \"#4a3f28\";\n\n// Earthy elevation palette: low/dark rooms read mossy green, high/light rooms\n// pale tan — like a shaded relief map.\nconst DARK_R = 78, DARK_G = 93, DARK_B = 52;\nconst LIGHT_R = 221, LIGHT_G = 207, LIGHT_B = 166;\n\n/** Number of inset contour rings drawn inside each room. */\nconst RINGS = 2;\n/** Gap between rings as a fraction of the shape's smaller half-extent. */\nconst RING_GAP = 0.26;\n/** Thin contour stroke width. */\nconst CONTOUR_WIDTH = 0.02;\n\n/** Map a colour to the earthy elevation palette by luminance. */\nfunction toEarth(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 earthPaint(paint: Paint): Paint {\n return {\n ...paint,\n fill: mapFill(paint.fill, toEarth),\n stroke: paint.stroke ? CONTOUR : paint.stroke,\n };\n}\n\n/** A stroke-only contour-ring paint. */\nconst ringPaint: Paint = {stroke: CONTOUR, strokeWidth: CONTOUR_WIDTH};\n\n/**\n * Topographic / contour-map aesthetic as a {@link Style}. Rooms get an earthy\n * elevation palette and a set of concentric inset \"contour\" rings, so each room\n * reads like a hill on a shaded relief map.\n *\n * - Rect / circle / polygon: emit the earth-toned base shape, then up to\n * {@link RINGS} stroke-only rings inset toward the centre. Rings collapse and\n * are skipped for rooms too small to hold them.\n * - Exit lines become muted brown paths.\n * - Text uses dark sepia.\n * - Images and groups pass through unchanged.\n */\nexport const topographicShapeStyle: Style = {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n const base: RectShape = {...shape, paint: earthPaint(shape.paint)};\n if (!shape.paint.fill) return base;\n const out: Shape[] = [base];\n const gap = Math.min(shape.width, shape.height) / 2 * RING_GAP;\n const minSide = Math.min(shape.width, shape.height) * 0.12;\n for (let k = 1; k <= RINGS; k++) {\n const inset = gap * k;\n const w = shape.width - inset * 2;\n const h = shape.height - inset * 2;\n if (w <= minSide || h <= minSide) break;\n out.push({\n type: \"rect\",\n x: shape.x + inset,\n y: shape.y + inset,\n width: w,\n height: h,\n cornerRadius: shape.cornerRadius,\n paint: ringPaint,\n layer: shape.layer,\n noScale: shape.noScale,\n });\n }\n return out;\n }\n case \"circle\": {\n const base: CircleShape = {...shape, paint: earthPaint(shape.paint)};\n if (!shape.paint.fill) return base;\n const out: Shape[] = [base];\n const gap = shape.radius * RING_GAP;\n for (let k = 1; k <= RINGS; k++) {\n const r = shape.radius - gap * k;\n if (r <= shape.radius * 0.12) break;\n out.push({\n type: \"circle\",\n cx: shape.cx,\n cy: shape.cy,\n radius: r,\n paint: ringPaint,\n layer: shape.layer,\n noScale: shape.noScale,\n });\n }\n return out;\n }\n case \"polygon\": {\n const base: PolygonShape = {...shape, paint: earthPaint(shape.paint)};\n if (!shape.paint.fill) return base;\n const out: Shape[] = [base];\n const n = shape.vertices.length / 2;\n // Centroid of the vertices; rings scale vertices toward it.\n let cx = 0, cy = 0;\n for (let i = 0; i < n; i++) {\n cx += shape.vertices[i * 2];\n cy += shape.vertices[i * 2 + 1];\n }\n cx /= n; cy /= n;\n for (let k = 1; k <= RINGS; k++) {\n const scale = 1 - RING_GAP * k;\n if (scale <= 0.15) break;\n const verts: number[] = [];\n for (let i = 0; i < n; i++) {\n verts.push(\n cx + (shape.vertices[i * 2] - cx) * scale,\n cy + (shape.vertices[i * 2 + 1] - cy) * scale,\n );\n }\n out.push({\n type: \"polygon\",\n vertices: verts,\n paint: ringPaint,\n layer: shape.layer,\n noScale: shape.noScale,\n });\n }\n return out;\n }\n case \"line\":\n return shape.paint.stroke\n ? {...shape, paint: {...shape.paint, stroke: PATH}}\n : shape;\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, PolygonShape, FillStyle} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {mapFill, parseRgb} from \"./paintMap\";\nimport {\n createRng,\n hashCoords,\n wobbleCircle,\n wobblePolygonEdges,\n wobbleRect,\n} from \"./wobble\";\n\nexport interface WatercolorOptions {\n /**\n * Edge-bleed amount in map units (perpendicular wobble of pane edges).\n * Default 0.05.\n */\n bleed?: number;\n /**\n * Translucent washes stacked per filled shape. More layers = deeper, more\n * uneven colour where they overlap. Default 2 (clamped to 1..4).\n */\n layers?: number;\n /** Per-wash alpha multiplier so stacked washes build up colour. Default 0.4. */\n alpha?: number;\n}\n\n/** Muted ink used for text — like a fine brush over a dried wash. */\nconst TEXT_COLOR = \"#3a3a44\";\n\n/** Lower a fill's effective alpha by folding it into an rgba string. */\nfunction washFill(fill: FillStyle | undefined, alpha: number): FillStyle | undefined {\n return mapFill(fill, (color) => {\n const c = parseRgb(color);\n if (!c) return color;\n return `rgba(${c.r}, ${c.g}, ${c.b}, ${(c.a * alpha).toFixed(3)})`;\n });\n}\n\n/**\n * Hand-painted watercolour as a {@link Style}. Each filled shape becomes a\n * stack of translucent, edge-wobbled washes that bleed and deepen where they\n * overlap — soft and organic, with no crisp ink outline.\n *\n * - Rect / circle / polygon fills → N wobbly low-alpha polygon washes (seeded,\n * so re-renders are identical). Hard strokes are dropped; the soft edges come\n * from the overlapping washes.\n * - Lines (exits) keep their colour but lose alpha so they read as a thin wet\n * brushstroke under the rooms.\n * - Text is repainted in muted ink.\n * - Images and groups pass through unchanged.\n *\n * Unfilled stroked shapes (e.g. exit arrowheads) fall back to a single faint\n * wobbled outline rather than vanishing.\n */\nexport function watercolorShapeStyle(options: WatercolorOptions = {}): Style {\n const bleed = options.bleed ?? 0.05;\n const layers = Math.max(1, Math.min(4, options.layers ?? 2));\n const alpha = options.alpha ?? 0.4;\n\n /** Build `layers` wobbly washes from a base polygon + paint. */\n function washes(\n baseVerts: (rng: () => number) => number[],\n paint: Paint,\n seed: number,\n layer: Shape[\"layer\"],\n hit: Shape[\"hit\"],\n noScale: boolean | undefined,\n ): PolygonShape[] {\n const fill = washFill(paint.fill, alpha);\n const out: PolygonShape[] = [];\n for (let i = 0; i < layers; i++) {\n // Distinct seed per wash so each layer wobbles differently and the\n // overlaps stay uneven (the watercolour \"pooling\" look).\n const rng = createRng(seed + i * 0x9e3779b9);\n out.push({\n type: \"polygon\",\n vertices: baseVerts(rng),\n paint: {fill, strokeWidth: 0},\n layer,\n // Only the first wash carries hit info — avoids duplicate pick zones.\n hit: i === 0 ? hit : undefined,\n noScale,\n });\n }\n return out;\n }\n\n return {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n if (!shape.paint.fill) {\n // Stroke-only rect → single faint wobbled outline.\n const rng = createRng(hashCoords(shape.x, shape.y, shape.width, shape.height));\n return {\n type: \"polygon\",\n vertices: wobbleRect(shape.x, shape.y, shape.width, shape.height, bleed, rng),\n paint: {stroke: shape.paint.stroke, strokeWidth: shape.paint.strokeWidth ?? 0, alpha: 0.5},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n }\n return washes(\n (rng) => wobbleRect(shape.x, shape.y, shape.width, shape.height, bleed, rng),\n shape.paint,\n hashCoords(shape.x, shape.y, shape.width, shape.height),\n shape.layer, shape.hit, shape.noScale,\n );\n }\n case \"circle\": {\n if (!shape.paint.fill) {\n const rng = createRng(hashCoords(shape.cx, shape.cy, shape.radius));\n return {\n type: \"polygon\",\n vertices: wobbleCircle(shape.cx, shape.cy, shape.radius, bleed, rng),\n paint: {stroke: shape.paint.stroke, strokeWidth: shape.paint.strokeWidth ?? 0, alpha: 0.5},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n }\n return washes(\n (rng) => wobbleCircle(shape.cx, shape.cy, shape.radius, bleed, rng),\n shape.paint,\n hashCoords(shape.cx, shape.cy, shape.radius),\n shape.layer, shape.hit, shape.noScale,\n );\n }\n case \"polygon\": {\n if (!shape.paint.fill) {\n const rng = createRng(hashCoords(...shape.vertices.slice(0, 4)));\n return {\n ...shape,\n vertices: wobblePolygonEdges(shape.vertices, bleed, rng),\n paint: {...shape.paint, alpha: 0.5},\n };\n }\n return washes(\n (rng) => wobblePolygonEdges(shape.vertices, bleed, rng),\n shape.paint,\n hashCoords(...shape.vertices.slice(0, 4)),\n shape.layer, shape.hit, shape.noScale,\n );\n }\n case \"line\":\n return {...shape, paint: {...shape.paint, alpha: (shape.paint.alpha ?? 1) * 0.6}};\n case \"text\":\n return {...shape, fill: TEXT_COLOR};\n case \"image\":\n case \"group\":\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\";\nimport {stainedGlassShapeStyle} from \"./shape/StainedGlassStyle\";\nimport {graphPaperShapeStyle} from \"./shape/GraphPaperStyle\";\nimport {topographicShapeStyle} from \"./shape/TopographicStyle\";\nimport {watercolorShapeStyle, type WatercolorOptions} from \"./shape/WatercolorStyle\";\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\n/** Stained-glass aesthetic — saturated panes framed by fat near-black leading. */\nexport const StainedGlass: Style = stainedGlassShapeStyle;\n\n/** Old-school graph-paper dungeon — pale rooms inked in navy over blue grid. */\nexport const GraphPaper: Style = graphPaperShapeStyle;\n\n/** Topographic relief — earthy rooms with concentric contour rings. */\nexport const Topographic: Style = topographicShapeStyle;\n\n/** Hand-painted watercolour — translucent edge-bled washes that pool on overlap. */\nexport function Watercolor(options: WatercolorOptions = {}): Style {\n return watercolorShapeStyle(options);\n}\n\nexport type {SketchyOptions, IsometricOptions, IsometricRotation, GradientRoomsOptions, WatercolorOptions};\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 /**\n * Register an interactive-only animated effect. Optional: backends that\n * cannot host animated effects omit it (the facade then no-ops). Both the\n * Konva and OffscreenCanvas backends implement it.\n */\n addLiveEffect?(id: string, effect: LiveEffect): void;\n /** Companion to {@link addLiveEffect}. */\n removeLiveEffect?(id: string): void;\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 /**\n * Highlight a room. Pass a single colour for the classic ring/marker, or an\n * array of colours to split the highlight into that many equal pie wedges\n * (one colour each).\n */\n renderHighlight(roomId: number, color: string | 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. Supported by the Konva and\n * OffscreenCanvas backends; no-ops on backends that don't host effects.\n * Does not appear in SVG/PNG exports — use {@link addSceneOverlay} for\n * overlays that must appear in exports.\n *\n * ```ts\n * renderer.addLiveEffect('rain', new RainEffect());\n * ```\n */\n addLiveEffect(id: string, effect: LiveEffect) {\n this.backend.addLiveEffect?.(id, effect);\n }\n\n removeLiveEffect(id: string) {\n this.backend.removeLiveEffect?.(id);\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 {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[], sceneSpace?: boolean) => {\n if (shapes.length === 0) return;\n lines.push(...svgFromBatches(buildDrawCommands(sceneSpace ? shapes : 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 | 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[], sceneSpace?: boolean) => {\n if (shapes.length === 0) return;\n renderToCanvas(ctx, buildDrawCommands(sceneSpace ? shapes : 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":[36,37,38,39,40],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/types/Settings.ts","../src/MapState.ts","../src/scene/elements/GridLayout.ts","../src/export/clipSceneToViewport.ts","../src/rendering/SceneManager.ts","../src/render/canvasGradient.ts","../src/render/RecordingLayer.ts","../src/scene/Shape.ts","../src/render/shapeToRecording.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/shape/StainedGlassStyle.ts","../src/style/shape/GraphPaperStyle.ts","../src/style/shape/TopographicStyle.ts","../src/style/shape/WatercolorStyle.ts","../src/style/shape/DarkModernStyle.ts","../src/style/shape/TreasureMapStyle.ts","../src/style/index.ts","../src/rendering/MapRenderer.ts","../src/draw/DrawCommandBuilder.ts","../src/render/SvgRenderer.ts","../src/render/CanvasRenderer.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","../src/overlay/RippleEffect.ts","../src/labelPlacement.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 * @deprecated Use {@link shape} instead. Only consulted when `shape` is\n * `'match'` (or omitted): when true (the default) the highlight follows the\n * current roomShape (rectangle / roundedRectangle / circle); when false it\n * is always a circle.\n */\n matchRoomShape?: boolean;\n\n /**\n * Outline shape of the highlight. `'match'` (the default when omitted)\n * follows the current roomShape; the other values force that specific shape\n * regardless of the room's shape.\n */\n shape?: 'match' | 'rectangle' | 'roundedRectangle' | 'circle';\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 shape: 'match',\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","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 = {\n /** Highlight colours; one draws a ring, two or more split it into pie wedges. */\n colors: string[];\n /** @deprecated Use {@link colors}. Kept for back-compat; equals `colors[0]`. */\n color: string;\n area: number;\n z: number;\n};\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: {\n roomId: number;\n colors: string[] | undefined;\n /** @deprecated Use {@link colors}. Equals `colors?.[0]`. */\n color: string | undefined;\n };\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 | string[]): boolean {\n const room = this.mapReader.getRoom(roomId);\n if (!room) return false;\n const colors = Array.isArray(color) ? (color.length > 0 ? [...color] : ['#ffffff']) : [color];\n this.highlights.set(roomId, {colors, color: colors[0], area: room.area, z: room.z});\n this.events.emit('highlight', {roomId, colors, color: colors[0]});\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, colors: undefined, 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 | 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.colors});\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 {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","/**\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 * Resolve a paint's effective dash pattern. Returns the dash only when it is\n * present AND not explicitly disabled (`dashEnabled === false` blanks it).\n * Renderers call this so every shape kind — rect, circle, line — honours\n * `dashEnabled` identically.\n */\nexport function resolveDash(\n dash: number[] | undefined,\n dashEnabled: boolean | undefined,\n): number[] | undefined {\n return (dashEnabled !== false && dash) ? dash : undefined;\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, resolveDash, 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: resolveDash(shape.paint.dash, shape.paint.dashEnabled),\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: resolveDash(shape.paint.dash, shape.paint.dashEnabled),\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: resolveDash(shape.paint.dash, shape.paint.dashEnabled),\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 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 {LayerOffset} 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 private coordinateLayerOffset: LayerOffset | undefined;\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 const layerOffset: LayerOffset | undefined = style.sceneLayerOffset\n ? (layer) => style.sceneLayerOffset!(layer)\n : undefined;\n\n this._coordinateTransform = forward;\n this.coordinateInverse = newInverse;\n this.coordinateLayerOffset = layerOffset;\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, layerOffset);\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, overlay.sceneSpace);\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, colors}) => {\n this.syncHighlight(roomId, colors);\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 * `bypassStyle` skips the Style transform entirely — used for overlays whose\n * geometry is already in rendered space (e.g. hit-area debug visualisation).\n */\n private addStyledShape(\n shape: Shape,\n layerNode: {addNode(node: RecordingGroupNode): void},\n bypassStyle = false,\n ): RecordingGroupNode | undefined {\n const styled = bypassStyle || 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, this.coordinateLayerOffset);\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, colors: string[] | undefined) {\n const existing = this.highlightShapes.get(roomId);\n if (existing) {\n existing.destroy();\n this.highlightShapes.delete(roomId);\n }\n if (colors !== 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, colors, 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.colors, 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\n/**\n * Convert 0..255 RGB to HSL with hue in degrees and saturation/lightness in\n * [0, 1]. Achromatic colours report hue 0. Shared by the newer styles\n * (StainedGlass, …); Neon and SciFi keep their own local copies.\n */\nexport function 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\n/** Build an `rgb(...)` / `rgba(...)` string from HSL (hue degrees, s/l in [0,1]). */\nexport function 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 return formatRgb(\n Math.round((r + m) * 255),\n Math.round((g + m) * 255),\n Math.round((b + m) * 255),\n a,\n );\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 // Link-layer groups are lowered by the cube depth in render space (see\n // the group case in `transform`). Report that here so HitTester places\n // exit/stub hit zones on the drawn connectors, not a cube-height above.\n sceneLayerOffset(layer) {\n return depth > 0 && layer === \"link\" ? {x: 0, y: depth} : {x: 0, y: 0};\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","import type {Shape, Paint, FillStyle} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {hslToRgbString, mapFill, parseRgb, rgbToHsl} from \"./paintMap\";\n\n/** Near-black \"came\" colour for the lead between panes. */\nconst LEADING = \"#0a0a0a\";\n/** Pale glass-white used for text so it reads over dark leading. */\nconst TEXT_COLOR = \"#f4f0e0\";\n\n/** Minimum saturation a pane gets — turns dull rooms into coloured glass. */\nconst MIN_SATURATION = 0.6;\n/** Lightness window kept so panes glow rather than wash out or go muddy. */\nconst MIN_LIGHTNESS = 0.4;\nconst MAX_LIGHTNESS = 0.62;\n/** Stroke-width multiplier for the leading, with a floor so thin edges still lead. */\nconst LEADING_MULT = 2.2;\nconst LEADING_MIN = 0.06;\n\n/** Saturate a colour into a jewel-toned glass pane, preserving hue + alpha. */\nfunction toGlass(color: string): string {\n const c = parseRgb(color);\n if (!c) return color;\n const [h, s, l] = rgbToHsl(c.r, c.g, c.b);\n // Achromatic panes (grey) keep their neutral look — they read as frosted glass.\n const sat = s < 0.08 ? s : Math.max(s, MIN_SATURATION);\n const light = Math.min(MAX_LIGHTNESS, Math.max(MIN_LIGHTNESS, l));\n return hslToRgbString(h, sat, light, c.a);\n}\n\n/** Leading width for a pane, scaled up from the original stroke. */\nfunction leadingWidth(strokeWidth: number | undefined): number {\n return Math.max((strokeWidth ?? 0.04) * LEADING_MULT, LEADING_MIN);\n}\n\n/**\n * Rewrite a filled shape's paint into a saturated pane with fat dark leading.\n * Panes always get leading even if the source had no stroke, so every room is\n * framed like a piece of glass.\n */\nfunction glassPaint(paint: Paint): Paint {\n const fill: FillStyle | undefined = mapFill(paint.fill, toGlass);\n const leaded = fill !== undefined || paint.stroke !== undefined;\n return {\n ...paint,\n fill,\n stroke: leaded ? LEADING : undefined,\n strokeWidth: leaded ? leadingWidth(paint.strokeWidth) : paint.strokeWidth,\n // Leading is opaque cames — drop any dash so the frame stays solid.\n dash: undefined,\n dashEnabled: undefined,\n };\n}\n\n/**\n * Stained-glass aesthetic as a {@link Style} — jewel-toned panes separated by\n * thick near-black leading, glowing on a dark backdrop.\n *\n * - Fills are pushed to high saturation in a mid-lightness window so rooms read\n * as coloured glass; grey rooms stay neutral (frosted panes). Gradient fills\n * recolour per stop.\n * - Strokes become fat near-black leading; filled panes gain leading even when\n * the source had none.\n * - Exit lines become slim leading too, tying the panes together.\n * - Text uses pale glass-white for legibility over the dark cames.\n * - Images and groups pass through unchanged.\n */\nexport const stainedGlassShapeStyle: Style = {\n transform(shape: Shape): Shape {\n switch (shape.type) {\n case \"rect\":\n case \"circle\":\n case \"polygon\":\n return {...shape, paint: glassPaint(shape.paint)};\n case \"line\":\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {\n ...shape.paint,\n stroke: LEADING,\n strokeWidth: leadingWidth(shape.paint.strokeWidth),\n }\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} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {formatRgb, luminance, mapFill, parseRgb} from \"./paintMap\";\n\n/** Dark navy ink for room outlines, exits, and text. */\nconst INK = \"#1c3f6e\";\n/** Slightly fattened ink so outlines read like a felt-tip on graph paper. */\nconst INK_MULT = 1.4;\nconst INK_MIN = 0.05;\n\n// Room fill range: dark rooms become pale slate-blue, light rooms near-white.\n// The whole range stays light so navy ink and the blue grid stay legible.\nconst DARK_R = 198, DARK_G = 212, DARK_B = 230;\nconst LIGHT_R = 248, LIGHT_G = 250, LIGHT_B = 255;\n\n/** Map a colour to the pale graph-paper fill range by luminance. */\nfunction toPaper(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 stroke to navy 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\nfunction inkWidth(strokeWidth: number | undefined): number {\n return Math.max((strokeWidth ?? 0.04) * INK_MULT, INK_MIN);\n}\n\nfunction rewriteFillStroke(paint: Paint): Paint {\n return {\n ...paint,\n fill: mapFill(paint.fill, toPaper),\n stroke: paint.stroke ? toInk(paint.stroke) : paint.stroke,\n strokeWidth: paint.stroke ? inkWidth(paint.strokeWidth) : paint.strokeWidth,\n };\n}\n\n/**\n * Old-school graph-paper dungeon aesthetic as a {@link Style} — the classic\n * hand-drawn D&D look: pale rooms inked in navy over blue grid paper.\n *\n * - Fills map to a pale slate-blue → near-white range by luminance, so rooms\n * stay light and the blue grid reads through. Gradient stops recolour per stop.\n * - Strokes become fattened navy ink.\n * - Exit lines become navy ink (kept thin where the source was thin).\n * - Text uses navy ink.\n * - Images and groups pass through unchanged.\n *\n * Pair with a cream/white background and a light-blue grid in settings for the\n * full graph-paper effect.\n */\nexport const graphPaperShapeStyle: 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 if (!shape.paint.stroke) return shape;\n // Grid lines stay thin; room/exit connectors get the inked width.\n return {\n ...shape,\n paint: {\n ...shape.paint,\n stroke: toInk(shape.paint.stroke),\n strokeWidth: shape.grid ? shape.paint.strokeWidth : inkWidth(shape.paint.strokeWidth),\n },\n };\n }\n case \"text\":\n return {...shape, fill: INK};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n};\n","import type {Shape, Paint, RectShape, CircleShape, PolygonShape} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {formatRgb, luminance, mapFill, parseRgb} from \"./paintMap\";\n\n/** Contour-line brown drawn as concentric rings inside each room. */\nconst CONTOUR = \"#7a6a48\";\n/** Muted brown for exit lines. */\nconst PATH = \"#8a7a55\";\n/** Dark sepia for text. */\nconst TEXT_COLOR = \"#4a3f28\";\n\n// Earthy elevation palette: low/dark rooms read mossy green, high/light rooms\n// pale tan — like a shaded relief map.\nconst DARK_R = 78, DARK_G = 93, DARK_B = 52;\nconst LIGHT_R = 221, LIGHT_G = 207, LIGHT_B = 166;\n\n/** Number of inset contour rings drawn inside each room. */\nconst RINGS = 2;\n/** Gap between rings as a fraction of the shape's smaller half-extent. */\nconst RING_GAP = 0.26;\n/** Thin contour stroke width. */\nconst CONTOUR_WIDTH = 0.02;\n\n/** Map a colour to the earthy elevation palette by luminance. */\nfunction toEarth(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 earthPaint(paint: Paint): Paint {\n return {\n ...paint,\n fill: mapFill(paint.fill, toEarth),\n stroke: paint.stroke ? CONTOUR : paint.stroke,\n };\n}\n\n/** A stroke-only contour-ring paint. */\nconst ringPaint: Paint = {stroke: CONTOUR, strokeWidth: CONTOUR_WIDTH};\n\n/**\n * Topographic / contour-map aesthetic as a {@link Style}. Rooms get an earthy\n * elevation palette and a set of concentric inset \"contour\" rings, so each room\n * reads like a hill on a shaded relief map.\n *\n * - Rect / circle / polygon: emit the earth-toned base shape, then up to\n * {@link RINGS} stroke-only rings inset toward the centre. Rings collapse and\n * are skipped for rooms too small to hold them.\n * - Exit lines become muted brown paths.\n * - Text uses dark sepia.\n * - Images and groups pass through unchanged.\n */\nexport const topographicShapeStyle: Style = {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n const base: RectShape = {...shape, paint: earthPaint(shape.paint)};\n if (!shape.paint.fill) return base;\n const out: Shape[] = [base];\n const gap = Math.min(shape.width, shape.height) / 2 * RING_GAP;\n const minSide = Math.min(shape.width, shape.height) * 0.12;\n for (let k = 1; k <= RINGS; k++) {\n const inset = gap * k;\n const w = shape.width - inset * 2;\n const h = shape.height - inset * 2;\n if (w <= minSide || h <= minSide) break;\n out.push({\n type: \"rect\",\n x: shape.x + inset,\n y: shape.y + inset,\n width: w,\n height: h,\n cornerRadius: shape.cornerRadius,\n paint: ringPaint,\n layer: shape.layer,\n noScale: shape.noScale,\n });\n }\n return out;\n }\n case \"circle\": {\n const base: CircleShape = {...shape, paint: earthPaint(shape.paint)};\n if (!shape.paint.fill) return base;\n const out: Shape[] = [base];\n const gap = shape.radius * RING_GAP;\n for (let k = 1; k <= RINGS; k++) {\n const r = shape.radius - gap * k;\n if (r <= shape.radius * 0.12) break;\n out.push({\n type: \"circle\",\n cx: shape.cx,\n cy: shape.cy,\n radius: r,\n paint: ringPaint,\n layer: shape.layer,\n noScale: shape.noScale,\n });\n }\n return out;\n }\n case \"polygon\": {\n const base: PolygonShape = {...shape, paint: earthPaint(shape.paint)};\n if (!shape.paint.fill) return base;\n const out: Shape[] = [base];\n const n = shape.vertices.length / 2;\n // Centroid of the vertices; rings scale vertices toward it.\n let cx = 0, cy = 0;\n for (let i = 0; i < n; i++) {\n cx += shape.vertices[i * 2];\n cy += shape.vertices[i * 2 + 1];\n }\n cx /= n; cy /= n;\n for (let k = 1; k <= RINGS; k++) {\n const scale = 1 - RING_GAP * k;\n if (scale <= 0.15) break;\n const verts: number[] = [];\n for (let i = 0; i < n; i++) {\n verts.push(\n cx + (shape.vertices[i * 2] - cx) * scale,\n cy + (shape.vertices[i * 2 + 1] - cy) * scale,\n );\n }\n out.push({\n type: \"polygon\",\n vertices: verts,\n paint: ringPaint,\n layer: shape.layer,\n noScale: shape.noScale,\n });\n }\n return out;\n }\n case \"line\":\n return shape.paint.stroke\n ? {...shape, paint: {...shape.paint, stroke: PATH}}\n : shape;\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, PolygonShape, FillStyle} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {mapFill, parseRgb} from \"./paintMap\";\nimport {\n createRng,\n hashCoords,\n wobbleCircle,\n wobblePolygonEdges,\n wobbleRect,\n} from \"./wobble\";\n\nexport interface WatercolorOptions {\n /**\n * Edge-bleed amount in map units (perpendicular wobble of pane edges).\n * Default 0.05.\n */\n bleed?: number;\n /**\n * Translucent washes stacked per filled shape. More layers = deeper, more\n * uneven colour where they overlap. Default 2 (clamped to 1..4).\n */\n layers?: number;\n /** Per-wash alpha multiplier so stacked washes build up colour. Default 0.4. */\n alpha?: number;\n}\n\n/** Muted ink used for text — like a fine brush over a dried wash. */\nconst TEXT_COLOR = \"#3a3a44\";\n\n/** Lower a fill's effective alpha by folding it into an rgba string. */\nfunction washFill(fill: FillStyle | undefined, alpha: number): FillStyle | undefined {\n return mapFill(fill, (color) => {\n const c = parseRgb(color);\n if (!c) return color;\n return `rgba(${c.r}, ${c.g}, ${c.b}, ${(c.a * alpha).toFixed(3)})`;\n });\n}\n\n/**\n * Hand-painted watercolour as a {@link Style}. Each filled shape becomes a\n * stack of translucent, edge-wobbled washes that bleed and deepen where they\n * overlap — soft and organic, with no crisp ink outline.\n *\n * - Rect / circle / polygon fills → N wobbly low-alpha polygon washes (seeded,\n * so re-renders are identical). Hard strokes are dropped; the soft edges come\n * from the overlapping washes.\n * - Lines (exits) keep their colour but lose alpha so they read as a thin wet\n * brushstroke under the rooms.\n * - Text is repainted in muted ink.\n * - Images and groups pass through unchanged.\n *\n * Unfilled stroked shapes (e.g. exit arrowheads) fall back to a single faint\n * wobbled outline rather than vanishing.\n */\nexport function watercolorShapeStyle(options: WatercolorOptions = {}): Style {\n const bleed = options.bleed ?? 0.05;\n const layers = Math.max(1, Math.min(4, options.layers ?? 2));\n const alpha = options.alpha ?? 0.4;\n\n /** Build `layers` wobbly washes from a base polygon + paint. */\n function washes(\n baseVerts: (rng: () => number) => number[],\n paint: Paint,\n seed: number,\n layer: Shape[\"layer\"],\n hit: Shape[\"hit\"],\n noScale: boolean | undefined,\n ): PolygonShape[] {\n const fill = washFill(paint.fill, alpha);\n const out: PolygonShape[] = [];\n for (let i = 0; i < layers; i++) {\n // Distinct seed per wash so each layer wobbles differently and the\n // overlaps stay uneven (the watercolour \"pooling\" look).\n const rng = createRng(seed + i * 0x9e3779b9);\n out.push({\n type: \"polygon\",\n vertices: baseVerts(rng),\n paint: {fill, strokeWidth: 0},\n layer,\n // Only the first wash carries hit info — avoids duplicate pick zones.\n hit: i === 0 ? hit : undefined,\n noScale,\n });\n }\n return out;\n }\n\n return {\n transform(shape: Shape): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n if (!shape.paint.fill) {\n // Stroke-only rect → single faint wobbled outline.\n const rng = createRng(hashCoords(shape.x, shape.y, shape.width, shape.height));\n return {\n type: \"polygon\",\n vertices: wobbleRect(shape.x, shape.y, shape.width, shape.height, bleed, rng),\n paint: {stroke: shape.paint.stroke, strokeWidth: shape.paint.strokeWidth ?? 0, alpha: 0.5},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n }\n return washes(\n (rng) => wobbleRect(shape.x, shape.y, shape.width, shape.height, bleed, rng),\n shape.paint,\n hashCoords(shape.x, shape.y, shape.width, shape.height),\n shape.layer, shape.hit, shape.noScale,\n );\n }\n case \"circle\": {\n if (!shape.paint.fill) {\n const rng = createRng(hashCoords(shape.cx, shape.cy, shape.radius));\n return {\n type: \"polygon\",\n vertices: wobbleCircle(shape.cx, shape.cy, shape.radius, bleed, rng),\n paint: {stroke: shape.paint.stroke, strokeWidth: shape.paint.strokeWidth ?? 0, alpha: 0.5},\n layer: shape.layer,\n hit: shape.hit,\n noScale: shape.noScale,\n };\n }\n return washes(\n (rng) => wobbleCircle(shape.cx, shape.cy, shape.radius, bleed, rng),\n shape.paint,\n hashCoords(shape.cx, shape.cy, shape.radius),\n shape.layer, shape.hit, shape.noScale,\n );\n }\n case \"polygon\": {\n if (!shape.paint.fill) {\n const rng = createRng(hashCoords(...shape.vertices.slice(0, 4)));\n return {\n ...shape,\n vertices: wobblePolygonEdges(shape.vertices, bleed, rng),\n paint: {...shape.paint, alpha: 0.5},\n };\n }\n return washes(\n (rng) => wobblePolygonEdges(shape.vertices, bleed, rng),\n shape.paint,\n hashCoords(...shape.vertices.slice(0, 4)),\n shape.layer, shape.hit, shape.noScale,\n );\n }\n case \"line\":\n return {...shape, paint: {...shape.paint, alpha: (shape.paint.alpha ?? 1) * 0.6}};\n case \"text\":\n return {...shape, fill: TEXT_COLOR};\n case \"image\":\n case \"group\":\n return shape;\n }\n },\n };\n}\n","import type {Shape, Paint, RectShape, CircleShape} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport {hslToRgbString, mapFill, parseRgb, rgbToHsl} from \"./paintMap\";\n\n/** Light, slightly translucent border for room outlines. */\nconst BORDER = \"rgba(255, 255, 255, 0.14)\";\n/** Muted grey-blue for exit connectors. */\nconst LINE_INK = \"#5b6573\";\n/** Off-white used for text / room characters. */\nconst TEXT_COLOR = \"#e6e8ec\";\n/** Drop-shadow colour. */\nconst SHADOW_FILL = \"rgba(0, 0, 0, 0.38)\";\n\n/** Shadow offset as a fraction of the active room size. */\nconst SHADOW_OFFSET = 0.08;\n/** Saturation ceiling — keeps the palette muted / flat. */\nconst MAX_SATURATION = 0.34;\n\n/**\n * Recolour a fill into the flat dark palette: hue preserved, saturation\n * capped, lightness pulled into a narrow mid-low band so rooms stay legible\n * on a dark canvas while looking uniform and modern.\n */\nfunction toDarkFill(color: string): string {\n const c = parseRgb(color);\n if (!c) return \"rgb(38, 42, 48)\";\n const [h, s, l] = rgbToHsl(c.r, c.g, c.b);\n const sat = Math.min(s, MAX_SATURATION);\n // Map source lightness into a flat 0.22..0.40 band.\n const light = 0.22 + l * 0.18;\n return hslToRgbString(h, sat, light, c.a);\n}\n\n/** Apply the dark-palette rewrites to a Paint record. */\nfunction darkPaint(paint: Paint): Paint {\n return {\n ...paint,\n fill: mapFill(paint.fill, toDarkFill),\n stroke: paint.stroke ? BORDER : paint.stroke,\n };\n}\n\n/** True for the primary room body shapes that should cast an elevation shadow. */\nfunction isRoomBody(shape: RectShape | CircleShape): boolean {\n return shape.hit?.kind === \"room\" && shape.paint.fill !== undefined;\n}\n\n/**\n * Flat dark \"modern UI\" aesthetic as a {@link Style}.\n *\n * - Room fills are flattened into a muted dark palette (hue kept, saturation\n * capped, lightness compressed) so the map reads as a calm dark dashboard.\n * - Room outlines become a subtle translucent light border.\n * - Each room body casts a soft offset drop-shadow (emitted **before** the\n * body, like Neon's glow, so it renders underneath) for a sense of\n * elevation.\n * - Exit lines become a muted grey-blue; text is off-white.\n * - Images and groups pass through unchanged (the caller walks group\n * children separately).\n */\nexport const darkModernShapeStyle: Style = {\n transform(shape: Shape, ctx): Shape | Shape[] {\n switch (shape.type) {\n case \"rect\": {\n const main: RectShape = {...shape, paint: darkPaint(shape.paint)};\n if (!isRoomBody(shape)) return main;\n const off = ctx.roomSize * SHADOW_OFFSET;\n const shadow: RectShape = {\n type: \"rect\",\n x: shape.x + off,\n y: shape.y + off,\n width: shape.width,\n height: shape.height,\n cornerRadius: shape.cornerRadius,\n paint: {fill: SHADOW_FILL},\n layer: shape.layer,\n noScale: shape.noScale,\n };\n return [shadow, main];\n }\n case \"circle\": {\n const main: CircleShape = {...shape, paint: darkPaint(shape.paint)};\n if (!isRoomBody(shape)) return main;\n const off = ctx.roomSize * SHADOW_OFFSET;\n const shadow: CircleShape = {\n type: \"circle\",\n cx: shape.cx + off,\n cy: shape.cy + off,\n radius: shape.radius,\n paint: {fill: SHADOW_FILL},\n layer: shape.layer,\n noScale: shape.noScale,\n };\n return [shadow, main];\n }\n case \"polygon\":\n return {...shape, paint: darkPaint(shape.paint)};\n case \"line\":\n return {\n ...shape,\n paint: shape.paint.stroke\n ? {...shape.paint, stroke: LINE_INK}\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} from \"../../scene/Shape\";\nimport type {Style} from \"../Style\";\nimport type {SceneOverlay, SceneOverlayContext} from \"../../overlay/SceneOverlay\";\nimport type {ViewportBounds} from \"../../types/Settings\";\nimport {formatRgb, luminance, mapFill, parseRgb} from \"./paintMap\";\n\n/** Aged ink brown used for strokes, outlines, and decorations. */\nconst INK = \"#5a3a22\";\n/** Darker brown for text legibility. */\nconst INK_TEXT = \"#43301c\";\n\n// Aged-map luminance range endpoints (deep tan → warm parchment tan).\nconst DARK_R = 120, DARK_G = 86, DARK_B = 50;\nconst LIGHT_R = 232, LIGHT_G = 205, LIGHT_B = 158;\n\n/**\n * Map a colour onto the aged treasure-map palette by perceived luminance —\n * a warmer, more saturated ramp than {@link parchmentShapeStyle} so it reads\n * as a weathered explorer's chart.\n */\nfunction toAged(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 aged 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, toAged),\n stroke: paint.stroke ? toInk(paint.stroke) : paint.stroke,\n };\n}\n\n/**\n * Aged treasure-map palette as a {@link Style}.\n *\n * Recolours rooms onto a warm weathered-paper ramp and inks every line in\n * faded brown. For the decorative compass rose and double border frame, add\n * {@link treasureMapDecorations} as a scene overlay alongside this style.\n *\n * ```ts\n * renderer.setStyle(TreasureMap);\n * renderer.addSceneOverlay('treasure-decor', treasureMapDecorations());\n * ```\n */\nexport const treasureMapShapeStyle: 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\n/** Build the eight-point compass star centred at (cx, cy). */\nfunction compassStar(cx: number, cy: number, radius: number): number[] {\n const verts: number[] = [];\n const shortR = radius * 0.42;\n for (let i = 0; i < 8; i++) {\n const angle = (-90 + i * 45) * (Math.PI / 180);\n const r = i % 2 === 0 ? radius : shortR;\n verts.push(cx + r * Math.cos(angle), cy + r * Math.sin(angle));\n }\n return verts;\n}\n\n/**\n * Decorative scene overlay for the treasure-map look: a double-line border\n * frame inset from the viewport and a compass rose in the top-right corner.\n *\n * Emitted in world space and pinned to the current viewport, so it tracks the\n * visible region as the user pans/zooms and appears in every export path.\n */\nexport function treasureMapDecorations(): SceneOverlay {\n let unsubscribe: (() => void) | undefined;\n return {\n attach(ctx: SceneOverlayContext) {\n unsubscribe = ctx.onViewportChange(() => ctx.invalidate());\n },\n detach() {\n unsubscribe?.();\n unsubscribe = undefined;\n },\n render(_state, bounds: ViewportBounds): Shape[] {\n const w = bounds.maxX - bounds.minX;\n const h = bounds.maxY - bounds.minY;\n if (w <= 0 || h <= 0) return [];\n const span = Math.min(w, h);\n const margin = span * 0.05;\n const gap = span * 0.012;\n const lineWidth = span * 0.006;\n\n const shapes: Shape[] = [];\n\n // Double border frame.\n for (const inset of [margin, margin + gap]) {\n shapes.push({\n type: \"rect\",\n x: bounds.minX + inset,\n y: bounds.minY + inset,\n width: w - inset * 2,\n height: h - inset * 2,\n paint: {stroke: INK, strokeWidth: lineWidth},\n layer: \"top\",\n });\n }\n\n // Compass rose, top-right corner.\n const radius = span * 0.07;\n const cx = bounds.maxX - margin - gap - radius * 1.4;\n const cy = bounds.minY + margin + gap + radius * 1.6;\n\n shapes.push({\n type: \"circle\",\n cx, cy, radius: radius * 1.25,\n paint: {stroke: INK, strokeWidth: lineWidth},\n layer: \"top\",\n });\n shapes.push({\n type: \"polygon\",\n vertices: compassStar(cx, cy, radius),\n paint: {fill: formatRgb(LIGHT_R, LIGHT_G, LIGHT_B, 0.85), stroke: INK, strokeWidth: lineWidth},\n layer: \"top\",\n });\n shapes.push({\n type: \"circle\",\n cx, cy, radius: radius * 0.16,\n paint: {fill: INK},\n layer: \"top\",\n });\n shapes.push({\n type: \"text\",\n x: cx,\n y: cy - radius * 1.25 - radius * 0.55,\n text: \"N\",\n fontSize: radius * 0.5,\n fontStyle: \"bold\",\n fill: INK_TEXT,\n align: \"center\",\n verticalAlign: \"middle\",\n layer: \"top\",\n });\n\n return shapes;\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\";\nimport {stainedGlassShapeStyle} from \"./shape/StainedGlassStyle\";\nimport {graphPaperShapeStyle} from \"./shape/GraphPaperStyle\";\nimport {topographicShapeStyle} from \"./shape/TopographicStyle\";\nimport {watercolorShapeStyle, type WatercolorOptions} from \"./shape/WatercolorStyle\";\nimport {darkModernShapeStyle} from \"./shape/DarkModernStyle\";\nimport {treasureMapShapeStyle, treasureMapDecorations} from \"./shape/TreasureMapStyle\";\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\n/** Stained-glass aesthetic — saturated panes framed by fat near-black leading. */\nexport const StainedGlass: Style = stainedGlassShapeStyle;\n\n/** Old-school graph-paper dungeon — pale rooms inked in navy over blue grid. */\nexport const GraphPaper: Style = graphPaperShapeStyle;\n\n/** Topographic relief — earthy rooms with concentric contour rings. */\nexport const Topographic: Style = topographicShapeStyle;\n\n/** Hand-painted watercolour — translucent edge-bled washes that pool on overlap. */\nexport function Watercolor(options: WatercolorOptions = {}): Style {\n return watercolorShapeStyle(options);\n}\n\n/** Flat dark \"modern UI\" theme — muted dark rooms with subtle elevation shadows. */\nexport const DarkModern: Style = darkModernShapeStyle;\n\n/**\n * Aged treasure-map palette — weathered-paper rooms inked in faded brown.\n * Pair with {@link treasureMapDecorations} (a scene overlay) for the compass\n * rose and double border frame:\n * ```ts\n * renderer.setStyle(TreasureMap);\n * renderer.addSceneOverlay('treasure-decor', treasureMapDecorations());\n * ```\n */\nexport const TreasureMap: Style = treasureMapShapeStyle;\n\nexport {treasureMapDecorations};\n\nexport type {SketchyOptions, IsometricOptions, IsometricRotation, GradientRoomsOptions, WatercolorOptions};\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 /**\n * Register an interactive-only animated effect. Optional: backends that\n * cannot host animated effects omit it (the facade then no-ops). Both the\n * Konva and OffscreenCanvas backends implement it.\n */\n addLiveEffect?(id: string, effect: LiveEffect): void;\n /** Companion to {@link addLiveEffect}. */\n removeLiveEffect?(id: string): void;\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 /**\n * Highlight a room. Pass a single colour for the classic ring/marker, or an\n * array of colours to split the highlight into that many equal pie wedges\n * (one colour each).\n */\n renderHighlight(roomId: number, color: string | 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. Supported by the Konva and\n * OffscreenCanvas backends; no-ops on backends that don't host effects.\n * Does not appear in SVG/PNG exports — use {@link addSceneOverlay} for\n * overlays that must appear in exports.\n *\n * ```ts\n * renderer.addLiveEffect('rain', new RainEffect());\n * ```\n */\n addLiveEffect(id: string, effect: LiveEffect) {\n this.backend.addLiveEffect?.(id, effect);\n }\n\n removeLiveEffect(id: string) {\n this.backend.removeLiveEffect?.(id);\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 {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[], sceneSpace?: boolean) => {\n if (shapes.length === 0) return;\n lines.push(...svgFromBatches(buildDrawCommands(sceneSpace ? shapes : 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 | 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[], sceneSpace?: boolean) => {\n if (shapes.length === 0) return;\n renderToCanvas(ctx, buildDrawCommands(sceneSpace ? shapes : 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","import Konva from \"konva\";\nimport type {CoordinateTransform, LiveEffect} from \"./LiveEffect\";\nimport type {ViewportBounds} from \"../types/Settings\";\n\nexport interface RippleOptions {\n /** Animation length in milliseconds. Default 400. */\n duration?: number;\n /** Ring colour (any CSS colour). Default \"#00e5b2\". */\n color?: string;\n /** Starting ring radius in world units. Default 0.3. */\n startRadius?: number;\n /** Final ring radius in world units. Default 1.2. */\n endRadius?: number;\n /** Ring stroke width in world units. Default 0.08. */\n strokeWidth?: number;\n /**\n * Invoked once when the animation finishes. The host should use this to\n * remove the (now spent) effect, e.g. `removeLiveEffect(id)`.\n */\n onComplete?: () => void;\n}\n\nconst now = () => (typeof performance !== \"undefined\" ? performance.now() : Date.now());\nconst raf: (cb: (t: number) => void) => number =\n typeof requestAnimationFrame !== \"undefined\"\n ? requestAnimationFrame\n : (cb) => setTimeout(() => cb(now()), 16) as unknown as number;\nconst caf: (id: number) => void =\n typeof cancelAnimationFrame !== \"undefined\"\n ? cancelAnimationFrame\n : (id) => clearTimeout(id as unknown as ReturnType<typeof setTimeout>);\n\n/**\n * One-shot expanding, fading ring centred on a world-space point — used to\n * pulse the player marker when it moves. Implements {@link LiveEffect}, so it\n * works on both the Konva and OffscreenCanvas backends (the latter composites\n * it on its main-thread effect stage).\n *\n * The ring lives in scene space (the host layer carries the camera transform),\n * so it tracks the map as the user pans/zooms. It self-animates and calls\n * {@link RippleOptions.onComplete} when done.\n */\nexport class RippleEffect implements LiveEffect {\n private ring?: Konva.Circle;\n private rafId?: number;\n private cancelled = false;\n private transform: CoordinateTransform = (x, y) => ({x, y});\n\n private readonly duration: number;\n private readonly color: string;\n private readonly startRadius: number;\n private readonly endRadius: number;\n private readonly strokeWidth: number;\n private readonly onComplete?: () => void;\n\n constructor(\n private readonly worldX: number,\n private readonly worldY: number,\n options: RippleOptions = {},\n ) {\n this.duration = options.duration ?? 400;\n this.color = options.color ?? \"#00e5b2\";\n this.startRadius = options.startRadius ?? 0.3;\n this.endRadius = options.endRadius ?? 1.2;\n this.strokeWidth = options.strokeWidth ?? 0.08;\n this.onComplete = options.onComplete;\n }\n\n attach(layer: Konva.Layer): void {\n const p = this.transform(this.worldX, this.worldY);\n this.ring = new Konva.Circle({\n x: p.x,\n y: p.y,\n radius: this.startRadius,\n stroke: this.color,\n strokeWidth: this.strokeWidth,\n listening: false,\n });\n layer.add(this.ring);\n layer.batchDraw();\n\n const apply = (t: number) => {\n if (!this.ring) return;\n this.ring.radius(this.startRadius + (this.endRadius - this.startRadius) * t);\n this.ring.opacity(1 - t);\n layer.batchDraw();\n };\n\n if (this.duration <= 0) {\n apply(1);\n this.onComplete?.();\n return;\n }\n\n const start = now();\n const step = (time: number) => {\n if (this.cancelled) return;\n const t = Math.min((time - start) / this.duration, 1);\n apply(t);\n if (t < 1) {\n this.rafId = raf(step);\n } else {\n this.rafId = undefined;\n this.onComplete?.();\n }\n };\n this.rafId = raf(step);\n }\n\n updateViewport(_bounds: ViewportBounds, _scale: number, coordinateTransform: CoordinateTransform): void {\n this.transform = coordinateTransform;\n if (this.ring) {\n const p = coordinateTransform(this.worldX, this.worldY);\n this.ring.position({x: p.x, y: p.y});\n }\n }\n\n destroy(): void {\n this.cancelled = true;\n if (this.rafId !== undefined) {\n caf(this.rafId);\n this.rafId = undefined;\n }\n this.ring?.destroy();\n this.ring = undefined;\n }\n}\n","/**\n * Point-feature label placement.\n *\n * Given a set of anchored labels (e.g. waypoints on rooms) and obstacles\n * (rooms, exits, already-placed labels), choose for each label the candidate\n * slot around its anchor that best avoids collisions. Pure and deterministic —\n * the same inputs always produce the same layout — so it's reusable beyond\n * waypoints and easy to test.\n *\n * For each slot the label is scanned outward (up to {@link extend}); the spot\n * with the most CLEARANCE — distance to the nearest room, exit line, or other\n * placed label — wins, lightly penalised by how far it had to push. This puts\n * labels in open space, off every line, and only as far out as openness needs.\n * Positions overlapping a room/label are skipped; sitting on an exit just reads\n * as zero clearance. Cardinal-over-diagonal and an optional preferred direction\n * break ties between equally-open slots.\n *\n * Placement is greedy: items are processed in input order and each placed label\n * becomes an obstacle for the ones after it.\n */\n\nexport type Direction8 = \"n\" | \"s\" | \"e\" | \"w\" | \"ne\" | \"nw\" | \"se\" | \"sw\";\n\n/** Unit vectors per slot, in world space (y-down: \"n\" is up on screen). */\nconst DIRS: Record<Direction8, {x: number; y: number}> = {\n n: {x: 0, y: -1}, s: {x: 0, y: 1}, e: {x: 1, y: 0}, w: {x: -1, y: 0},\n ne: {x: Math.SQRT1_2, y: -Math.SQRT1_2}, nw: {x: -Math.SQRT1_2, y: -Math.SQRT1_2},\n se: {x: Math.SQRT1_2, y: Math.SQRT1_2}, sw: {x: -Math.SQRT1_2, y: Math.SQRT1_2},\n};\n/** Cardinals first so they win ties over diagonals. */\nconst SLOT_ORDER: Direction8[] = [\"n\", \"s\", \"e\", \"w\", \"ne\", \"nw\", \"se\", \"sw\"];\nconst DIAGONALS = new Set<Direction8>([\"ne\", \"nw\", \"se\", \"sw\"]);\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** An obstacle the placement should avoid. `kind` weights how hard to avoid it. */\nexport interface Obstacle extends Rect {\n kind?: \"room\" | \"label\" | \"exit\" | string;\n}\n\nexport interface LabelPlacementItem {\n /** Anchor point (e.g. room centre), world coords. */\n x: number;\n y: number;\n /** Label box size, world units. */\n width: number;\n height: number;\n /** Optional preferred direction — nudges the choice, never forces overlap. */\n preferred?: Direction8;\n /** Optional stable id, echoed back on the result. */\n id?: string | number;\n}\n\nexport interface PlacedLabel {\n id?: string | number;\n /** Top-left of the placed label box, world coords. */\n x: number;\n y: number;\n width: number;\n height: number;\n /** Chosen slot. */\n direction: Direction8;\n /** Leader endpoint on the box edge nearest the anchor (for drawing a connector). */\n leader: {x: number; y: number};\n}\n\n/** Per-slot diagnostic from {@link PlaceLabelsOptions.onScored}. */\nexport interface SlotScore {\n dir: Direction8;\n /** False when no non-overlapping position exists in this slot. */\n valid: boolean;\n /** Clearance (distance to nearest obstacle) at the best position found. */\n clearance: number;\n /** Outward offset of the best position. */\n off: number;\n /** Final cost (lower = better; Infinity when invalid). */\n cost: number;\n /** The label box at the best position. */\n box: Rect;\n}\n\nexport interface PlaceLabelsOptions {\n /** Gap between the anchor and the near edge of the label box, world units. Default 0.6. */\n offset?: number;\n /** Use only N/S/E/W (4) or all 8 slots. Default 8. */\n slots?: 4 | 8;\n /**\n * How far (world units) a label may be pushed outward along a slot, beyond\n * the base {@link offset}, while searching for the most open spot. 0 keeps\n * labels at the base offset; larger lets them float into open pockets away\n * from rooms/exit lines.\n */\n extend?: number;\n /**\n * Clearance is only rewarded up to this many world units — past it a spot is\n * \"open enough\" and extra distance isn't worth pushing further for. Defaults\n * to the label's larger side.\n */\n clearCap?: number;\n /**\n * When the best slot's clearance is below this (world units), the room is\n * treated as \"surrounded\": instead of floating out for a marginal gain, the\n * label sits close at the base offset on an exit, preferring north. Default 0\n * (disabled — always go for max clearance).\n */\n minClearance?: number;\n /**\n * Debug hook — called once per item with the score of every candidate slot\n * and the chosen direction. For visualising/diagnosing placement decisions.\n */\n onScored?: (item: LabelPlacementItem, slots: SlotScore[], chosen: Direction8) => void;\n /** Cost weights (sane defaults; overlap dominates, then openness, then direction). */\n weights?: Partial<typeof DEFAULT_WEIGHTS>;\n}\n\nconst DEFAULT_WEIGHTS = {\n /**\n * Reward per world unit of clearance (distance from the label box to the\n * nearest room/exit/label), capped at {@link PlaceLabelsOptions.clearCap}.\n * The dominant term — pushes labels into open space, off every line.\n */\n clear: 10,\n /**\n * Cardinal-over-diagonal preference — a TRUE tie-break only. Kept tiny so\n * any real clearance advantage (≳0.03 units) overrides it: a clearly more\n * open diagonal always beats an on-a-line cardinal.\n */\n diagonal: 0.3,\n /** Bonus (subtracted from cost) when a slot matches the item's preferred direction. */\n preferred: 1,\n /**\n * Penalty per world unit the label is pushed out — keeps it close, but small\n * enough not to cancel the clearance gained by pushing into open space.\n */\n distance: 0.05,\n};\n\nconst CARDINALS: Direction8[] = [\"n\", \"s\", \"e\", \"w\"];\n/** Fallback order for surrounded rooms — north first, then cardinals, then diagonals. */\nconst SURROUND_ORDER: Direction8[] = [\"n\", \"s\", \"e\", \"w\", \"ne\", \"nw\", \"se\", \"sw\"];\n\nfunction intersectionArea(a: Rect, b: Rect): number {\n const x = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));\n const y = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));\n return x * y;\n}\n\n/** Gap between two rectangles (0 if they touch or overlap). */\nfunction rectDistance(a: Rect, b: Rect): number {\n const dx = Math.max(a.x - (b.x + b.width), b.x - (a.x + a.width), 0);\n const dy = Math.max(a.y - (b.y + b.height), b.y - (a.y + a.height), 0);\n return Math.hypot(dx, dy);\n}\n\n/**\n * Box for a slot.\n *\n * Cardinals: the box centre sits so the near edge is `offset` from the anchor,\n * centred on the axis.\n *\n * Diagonals: the box is anchored by its room-facing CORNER at `offset` from the\n * anchor, extending outward. This keeps wide labels hugging the corner — a small\n * vertical gap with the width fanning out sideways — instead of the 45° centre\n * projection flinging them far up/down (the width dominates `halfAlong`).\n */\nfunction slotBox(item: LabelPlacementItem, dir: Direction8, offset: number): Rect {\n const v = DIRS[dir];\n if (DIAGONALS.has(dir)) {\n const nx = item.x + v.x * offset; // near corner\n const ny = item.y + v.y * offset;\n return {\n x: v.x < 0 ? nx - item.width : nx,\n y: v.y < 0 ? ny - item.height : ny,\n width: item.width, height: item.height,\n };\n }\n const halfAlong = Math.abs(v.x) * item.width / 2 + Math.abs(v.y) * item.height / 2;\n const d = offset + halfAlong;\n const cx = item.x + v.x * d;\n const cy = item.y + v.y * d;\n return {x: cx - item.width / 2, y: cy - item.height / 2, width: item.width, height: item.height};\n}\n\n/**\n * Place each item at its best-scoring slot. Returns one {@link PlacedLabel} per\n * input item, in the same order.\n */\nexport function placeLabels(\n items: LabelPlacementItem[],\n obstacles: Obstacle[],\n options: PlaceLabelsOptions = {},\n): PlacedLabel[] {\n const offset = options.offset ?? 0.6;\n const slotList = (options.slots === 4 ? CARDINALS : SLOT_ORDER);\n const w = {...DEFAULT_WEIGHTS, ...options.weights};\n\n // Placed labels accumulate as obstacles for later items (greedy).\n const dynamic: Obstacle[] = [];\n const results: PlacedLabel[] = [];\n\n for (const item of items) {\n const maxExtend = options.extend ?? 0;\n const clearCap = options.clearCap ?? Math.max(item.width, item.height);\n const step = Math.max(0.1, Math.min(item.width, item.height) * 0.5);\n const all = obstacles.concat(dynamic);\n\n // Coarse pre-filter: only obstacles near this anchor can matter. Drop the\n // waypoint's OWN room (the room obstacle containing the anchor) — a label\n // is meant to sit beside its own room. Exits are kept even when they\n // start at the own room: a label sitting on its room's exit IS bad.\n const reach = offset + Math.max(item.width, item.height) + maxExtend + clearCap;\n const near = all.filter(o =>\n !(o.kind !== \"exit\" && item.x >= o.x && item.x <= o.x + o.width && item.y >= o.y && item.y <= o.y + o.height) &&\n o.x < item.x + reach && o.x + o.width > item.x - reach &&\n o.y < item.y + reach && o.y + o.height > item.y - reach);\n\n let best: {box: Rect; dir: Direction8; off: number; cost: number; clearance: number} | undefined;\n const debug: SlotScore[] = [];\n\n for (const dir of slotList) {\n // Scan outward along the slot; keep the position with the most\n // clearance (distance to the nearest room/exit/label), lightly\n // penalising distance so the label stays as close as openness allows.\n let slotBest: {box: Rect; off: number; value: number; clearance: number} | undefined;\n for (let o = offset; o <= offset + maxExtend + 1e-9; o += step) {\n const box = slotBox(item, dir, o);\n let blocked = false;\n let clearance = Infinity;\n for (const ob of near) {\n if (ob.kind !== \"exit\" && intersectionArea(box, ob) > 0) { blocked = true; break; }\n const d = rectDistance(box, ob);\n if (d < clearance) clearance = d;\n }\n // Hard obstacle (room/label): can't sit here. Stop scanning further\n // out once we've already found a clear spot (beyond it is worse).\n if (blocked) { if (slotBest) break; else continue; }\n const value = Math.min(clearance, clearCap) - o * w.distance;\n if (!slotBest || value > slotBest.value) slotBest = {box, off: o, value, clearance};\n }\n if (!slotBest) {\n if (options.onScored) debug.push({dir, valid: false, clearance: 0, off: offset, cost: Infinity, box: slotBox(item, dir, offset)});\n continue;\n }\n\n let dirCost = 0;\n if (item.preferred) {\n if (dir === item.preferred) dirCost -= w.preferred;\n } else if (DIAGONALS.has(dir)) {\n dirCost += w.diagonal;\n }\n\n const cost = -slotBest.value * w.clear + dirCost;\n if (options.onScored) debug.push({dir, valid: true, clearance: slotBest.clearance, off: slotBest.off, cost, box: slotBest.box});\n if (!best || cost < best.cost) best = {box: slotBest.box, dir, off: slotBest.off, cost, clearance: slotBest.clearance};\n }\n\n // Fallback (every slot blocked at every distance): sit at the base offset\n // of the first slot so we always emit a placement.\n if (!best) best = {box: slotBox(item, slotList[0], offset), dir: slotList[0], off: offset, cost: 0, clearance: 0};\n\n // Surrounded: when nothing reaches the minimum clearance, don't float out\n // for a marginal gain — sit CLOSE (base offset) on an exit, preferring\n // north, then the other cardinals, then diagonals.\n if (best.clearance < (options.minClearance ?? 0)) {\n for (const dir of SURROUND_ORDER) {\n const box = slotBox(item, dir, offset);\n if (!near.some(o => o.kind !== \"exit\" && intersectionArea(box, o) > 0)) {\n best = {box, dir, off: offset, cost: best.cost, clearance: best.clearance};\n break;\n }\n }\n }\n\n options.onScored?.(item, debug, best.dir);\n\n const v = DIRS[best.dir];\n results.push({\n id: item.id,\n x: best.box.x,\n y: best.box.y,\n width: item.width,\n height: item.height,\n direction: best.dir,\n // Near edge of the box, where a connector to the anchor should end.\n leader: {x: item.x + v.x * best.off, y: item.y + v.y * best.off},\n });\n dynamic.push({...best.box, kind: \"label\"});\n }\n\n return results;\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":[40,41,42,43,44],"mappings":"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"}
|