{"version":3,"file":"index.mjs","names":[],"sources":["../src/types/Settings.ts","../src/MapState.ts","../src/scene/elements/GridLayout.ts","../src/export/clipSceneToViewport.ts","../src/render/CullIndex.ts","../src/rendering/SceneManager.ts","../src/render/canvasGradient.ts","../src/render/RecordingLayer.ts","../src/scene/Shape.ts","../src/render/shapeToRecording.ts","../src/reader/ViewportDataSource.ts","../src/rendering/lod/RasterOverview.ts","../src/rendering/lod/LodController.ts","../src/rendering/lod/lodDecision.ts","../src/rendering/lod/roomsOnlyArea.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/exportViewport.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","../src/overlay/WaypointOverlay.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/reader/HashLookup.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, 225, 225)';\n\nexport type LabelRenderMode = \"image\" | \"data\" | \"none\";\n\nexport type CullingMode = \"none\" | \"basic\" | \"indexed\";\n\nexport type RoomShape = \"rectangle\" | \"circle\" | \"roundedRectangle\";\n\n/**\n * How to render rooms Mudlet has marked hidden (see {@link isRoomHidden}):\n * - `\"hide\"` — omit them from the scene entirely (Mudlet's default).\n * - `\"show\"` — draw them exactly like any other room.\n * - `\"faded\"` — draw them at reduced opacity so they read as de-emphasised.\n * - `\"dashed\"` — draw them at full opacity but with a dashed border, a more\n *   distinct \"hidden\" marker than a fade (which can read as just another shade).\n */\nexport type HiddenRoomMode = \"hide\" | \"show\" | \"faded\" | \"dashed\";\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\n/** Emitted after every LOD mode decision (see {@link Settings.lodEnabled}). */\nexport type LodEventDetail = {\n    /**\n     * \"vector\": rooms + exit lines, full detail. \"roomsOnly\": exit lines\n     * dropped, rooms still real vector shapes (bridge tier — see\n     * {@link Settings.lodExitBudget}). \"raster\": pixel overview.\n     */\n    mode: \"vector\" | \"roomsOnly\" | \"raster\";\n    /** Total rooms on the displayed (area, z) plane. */\n    planeRoomCount: number;\n    /** Cheap upper bound of the rooms inside the applied viewport. */\n    visibleEstimate: number;\n    /**\n     * Whether clicks/hover currently resolve to a room. Always false in\n     * raster mode; false in vector/roomsOnly mode once the room count exceeds\n     * {@link Settings.lodHitTestBudget}.\n     */\n    hitTestActive: boolean;\n};\n\nexport type RendererEventMap = {\n    roomclick: RoomClickEventDetail;\n    roomcontextmenu: RoomContextMenuEventDetail;\n    areaexitclick: AreaExitClickEventDetail;\n    mapclick: undefined;\n    pan: PanEventDetail;\n    zoom: ZoomChangeEventDetail;\n    lod: LodEventDetail;\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    /**\n     * When true, room bodies that share the same fill/stroke/shape are drawn in\n     * a single batched path (one `fill()` + one `stroke()` per style) instead of\n     * one draw per room. Big win on large areas with few environment colours;\n     * see {@link DrawCommandLayerNode}. Rooms with symbols, emboss, gradient\n     * fills, or coloured multi-rings fall back to per-room replay automatically.\n     *\n     * Off by default: coalescing reorders same-style rooms, which can shift\n     * sub-pixel border overlaps where rooms touch. Default: false\n     */\n    coalesceRooms: boolean;\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    /** How to render rooms Mudlet has marked hidden: \"hide\" | \"show\" | \"faded\". Default: \"hide\" */\n    hiddenRooms: HiddenRoomMode;\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    /** When true, rooms from neighbouring areas reachable within {@link neighborSpillDistance}\n     *  steps of the player are drawn (faded) across the boundary, projected into the current\n     *  area's coordinate space. Requires a known player position. Default: false */\n    neighborSpill: boolean;\n    /** Max number of steps from the player's room to spill neighbouring-area rooms across a\n     *  boundary (BFS depth over planar exits). Default: 20 */\n    neighborSpillDistance: number;\n    /**\n     * Level-of-detail for very dense planes: when the current plane holds more\n     * rooms than {@link lodRoomBudget} and the zoom is far enough out that a\n     * viewport could exceed that budget, the vector scene is replaced by a\n     * raster pixel overview (one filled box per room) painted on an underlay.\n     * Zooming in switches back to full vector detail. The mode is reported via\n     * the renderer's `lod` event. Interactive Konva backend only. Default: false\n     */\n    lodEnabled: boolean;\n    /**\n     * Max rooms the vector scene may hold before zoomed-out views switch to the\n     * raster overview; also the per-viewport budget the zoom threshold is\n     * derived from. Each rebuild at this density touches shape building, hit\n     * testing, and draw for the whole visible set, so lower this if rebuilds\n     * near the flip feel slow. Default: 16000\n     */\n    lodRoomBudget: number;\n    /**\n     * Above this many rooms in the current plane build (same unit as\n     * {@link lodRoomBudget}), the hit-test index is skipped instead of\n     * rebuilt — the scene still renders at full vector detail, but\n     * clicks/hover won't resolve to a room until you zoom in below this\n     * density. Rebuilding the hit index is a meaningful fraction of a large\n     * rebuild, and precise pointer interaction is rarely needed at very high\n     * densities anyway. Only takes effect while `lodEnabled` is true, and only\n     * below `lodRoomBudget` (above it the plane is raster and has no hit\n     * shapes at all). Default: 10000\n     */\n    lodHitTestBudget: number;\n    /**\n     * Above this many rooms in the current plane build (same unit as\n     * {@link lodRoomBudget}) and below `lodRoomBudget`, exit lines are\n     * dropped but rooms still render as full vector shapes — a bridge tier\n     * between full vector detail and the raster overview. Exit pairing and\n     * exit-line shape building are typically the single largest share of a\n     * dense rebuild's cost (exit count often runs ~2x room count in a\n     * well-connected area), so this buys back most of that cost while still\n     * showing real room shapes instead of raster pixels. Only takes effect\n     * while `lodEnabled` is true. Set above `lodRoomBudget` (or to `Infinity`)\n     * to disable this tier and jump straight from full vector to raster.\n     * Default: 12000\n     */\n    lodExitBudget: 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        coalesceRooms: false,\n        cullingBounds: null,\n        labelRenderMode: \"image\",\n        transparentLabels: false,\n        roomShape: \"rectangle\",\n        hiddenRooms: \"hide\",\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        neighborSpill: false,\n        neighborSpillDistance: 20,\n        lodEnabled: false,\n        lodRoomBudget: 16000,\n        lodHitTestBudget: 10000,\n        lodExitBudget: 12000,\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","/**\n * CullIndex — uniform-grid spatial index for viewport culling.\n *\n * Replaces the historical O(N) linear scan (every room + exit AABB-tested\n * against the viewport on every frame) with an O(visible-cells) query. Built\n * once per scene rebuild; queried once per camera change.\n *\n * Design notes:\n *  - Entries store **scene-space** AABBs (already projected through the active\n *    style's world→scene transform), so a query needs zero per-frame transform\n *    math even under warped styles like Isometric. The projection happens once\n *    at build time (see {@link projectBounds}).\n *  - Each entry is inserted into every grid cell its AABB overlaps, so an exit\n *    polyline that straddles a cell boundary is found from either side (no\n *    popping at edges).\n *  - Entries whose AABB spans more cells than {@link MAX_SPAN_CELLS} (very long\n *    cross-area exit arrows, full-width labels) go into an `oversized` bucket\n *    that is always tested — this bounds insertion cost and keeps the grid from\n *    degenerating when one giant shape would otherwise touch thousands of cells.\n *  - Below {@link LINEAR_THRESHOLD} entries the index falls back to a plain\n *    linear scan: the grid's bookkeeping isn't worth it for small scenes, and\n *    the result is identical.\n *\n * The precise in-viewport test is inclusive on every edge and matches\n * `buildCullingVisibilityMap` exactly, so swapping the linear scan for this\n * index does not change which shapes are considered visible.\n */\n\nimport type {Shape} from \"../scene/Shape\";\nimport type {ViewportBounds} from \"../types/Settings\";\nimport type {CoordFn} from \"../coord/CoordFn\";\nimport {IDENTITY_TRANSFORM} from \"../coord/CoordFn\";\n\n/** One indexed shape with its scene-space axis-aligned bounding box. */\nexport interface CullIndexEntry {\n    shape: Shape;\n    minX: number;\n    minY: number;\n    maxX: number;\n    maxY: number;\n}\n\n/** Scenes smaller than this skip the grid and use a linear scan. */\nconst LINEAR_THRESHOLD = 256;\n/** Aim for roughly this many entries per grid cell when sizing the grid. */\nconst TARGET_PER_CELL = 2;\n/** Entries whose AABB touches more cells than this go in the oversized bucket. */\nconst MAX_SPAN_CELLS = 32;\n/** Hard cap on total grid cells, to bound memory on sparse-but-huge maps. */\nconst MAX_CELLS = 1 << 20;\n\nexport class CullIndex {\n    private entries: CullIndexEntry[] = [];\n    private allShapes: Set<Shape> = new Set();\n\n    private linear = true;\n    private originX = 0;\n    private originY = 0;\n    private cellSize = 1;\n    private cols = 0;\n    private rows = 0;\n    /** Per-cell lists of entry indices (row-major, length cols*rows). */\n    private cells: number[][] = [];\n    /** Indices of entries too large to grid — always tested. */\n    private oversized: number[] = [];\n\n    /** Per-query dedup stamps so a multi-cell entry is tested at most once. */\n    private stamp: Int32Array = new Int32Array(0);\n    private queryGen = 0;\n\n    /**\n     * (Re)build the index from scene-space entries. `entries` is retained by\n     * reference; callers should not mutate it afterwards.\n     */\n    build(entries: CullIndexEntry[]): void {\n        this.entries = entries;\n        this.allShapes = new Set(entries.map(e => e.shape));\n        const n = entries.length;\n\n        if (n < LINEAR_THRESHOLD) {\n            this.linear = true;\n            return;\n        }\n        this.linear = false;\n\n        let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n        for (const e of entries) {\n            if (e.minX < minX) minX = e.minX;\n            if (e.minY < minY) minY = e.minY;\n            if (e.maxX > maxX) maxX = e.maxX;\n            if (e.maxY > maxY) maxY = e.maxY;\n        }\n        const width = Math.max(maxX - minX, 1e-6);\n        const height = Math.max(maxY - minY, 1e-6);\n\n        // Target ~TARGET_PER_CELL entries per cell: cellSize ≈ sqrt(area / cells).\n        let cell = Math.sqrt((width * height) / Math.max(n / TARGET_PER_CELL, 1));\n        if (!isFinite(cell) || cell <= 0) cell = Math.max(width, height);\n\n        let cols = Math.max(1, Math.ceil(width / cell));\n        let rows = Math.max(1, Math.ceil(height / cell));\n        // Grow the cell if the grid would be too large (sparse, far-flung rooms).\n        while (cols * rows > MAX_CELLS) {\n            cell *= 2;\n            cols = Math.max(1, Math.ceil(width / cell));\n            rows = Math.max(1, Math.ceil(height / cell));\n        }\n\n        this.originX = minX;\n        this.originY = minY;\n        this.cellSize = cell;\n        this.cols = cols;\n        this.rows = rows;\n        this.cells = Array.from({length: cols * rows}, () => [] as number[]);\n        this.oversized = [];\n        this.stamp = new Int32Array(n);\n        this.queryGen = 0;\n\n        for (let i = 0; i < n; i++) {\n            const e = entries[i];\n            const cx0 = this.clampCol(Math.floor((e.minX - minX) / cell));\n            const cx1 = this.clampCol(Math.floor((e.maxX - minX) / cell));\n            const cy0 = this.clampRow(Math.floor((e.minY - minY) / cell));\n            const cy1 = this.clampRow(Math.floor((e.maxY - minY) / cell));\n            const span = (cx1 - cx0 + 1) * (cy1 - cy0 + 1);\n            if (span > MAX_SPAN_CELLS) {\n                this.oversized.push(i);\n                continue;\n            }\n            for (let cy = cy0; cy <= cy1; cy++) {\n                const rowBase = cy * cols;\n                for (let cx = cx0; cx <= cx1; cx++) {\n                    this.cells[rowBase + cx].push(i);\n                }\n            }\n        }\n    }\n\n    /** Shapes known to this index (i.e. cullable). Order is build order. */\n    getAllShapes(): ReadonlySet<Shape> {\n        return this.allShapes;\n    }\n\n    /**\n     * Return the set of shapes whose AABB intersects `viewport`. Inclusive on\n     * every edge, matching `buildCullingVisibilityMap`.\n     */\n    queryVisible(viewport: ViewportBounds): Set<Shape> {\n        const visible = new Set<Shape>();\n        const {minX: vMinX, maxX: vMaxX, minY: vMinY, maxY: vMaxY} = viewport;\n\n        if (this.linear) {\n            for (const e of this.entries) {\n                if (e.maxX >= vMinX && e.minX <= vMaxX && e.maxY >= vMinY && e.minY <= vMaxY) {\n                    visible.add(e.shape);\n                }\n            }\n            return visible;\n        }\n\n        const gen = ++this.queryGen;\n        const stamp = this.stamp;\n        const entries = this.entries;\n        const cols = this.cols;\n\n        const cx0 = this.clampCol(Math.floor((vMinX - this.originX) / this.cellSize));\n        const cx1 = this.clampCol(Math.floor((vMaxX - this.originX) / this.cellSize));\n        const cy0 = this.clampRow(Math.floor((vMinY - this.originY) / this.cellSize));\n        const cy1 = this.clampRow(Math.floor((vMaxY - this.originY) / this.cellSize));\n\n        for (let cy = cy0; cy <= cy1; cy++) {\n            const rowBase = cy * cols;\n            for (let cx = cx0; cx <= cx1; cx++) {\n                const bucket = this.cells[rowBase + cx];\n                for (let k = 0; k < bucket.length; k++) {\n                    const i = bucket[k];\n                    if (stamp[i] === gen) continue;\n                    stamp[i] = gen;\n                    const e = entries[i];\n                    if (e.maxX >= vMinX && e.minX <= vMaxX && e.maxY >= vMinY && e.minY <= vMaxY) {\n                        visible.add(e.shape);\n                    }\n                }\n            }\n        }\n\n        for (let k = 0; k < this.oversized.length; k++) {\n            const e = entries[this.oversized[k]];\n            if (e.maxX >= vMinX && e.minX <= vMaxX && e.maxY >= vMinY && e.minY <= vMaxY) {\n                visible.add(e.shape);\n            }\n        }\n\n        return visible;\n    }\n\n    private clampCol(c: number): number {\n        return c < 0 ? 0 : c >= this.cols ? this.cols - 1 : c;\n    }\n\n    private clampRow(r: number): number {\n        return r < 0 ? 0 : r >= this.rows ? this.rows - 1 : r;\n    }\n}\n\n/**\n * Project a world-space AABB through `transform` into a scene-space AABB by\n * transforming its four corners and taking the min/max. For the identity\n * transform the box is returned unchanged. Mirrors the `transformedBbox`\n * helper in `clipSceneToViewport` so index bounds match the cull predicate.\n */\nexport function projectBounds(\n    minX: number, minY: number, maxX: number, maxY: number,\n    transform: CoordFn,\n): {minX: number; minY: number; maxX: number; maxY: number} {\n    if (transform === IDENTITY_TRANSFORM) {\n        return {minX, minY, maxX, maxY};\n    }\n    const c1 = transform(minX, minY);\n    const c2 = transform(maxX, minY);\n    const c3 = transform(maxX, maxY);\n    const c4 = transform(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","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} 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 {CullIndex, projectBounds, type CullIndexEntry} from \"../render/CullIndex\";\nimport type {GroupShape, Shape} from \"../scene/Shape\";\nimport type {IMapReader} from \"../reader/MapReader\";\nimport type {RoomLens} from \"../lens/RoomLens\";\nimport type {ExitDrawData} from \"../ExitRenderer\";\nimport type {NeighborSpill} from \"../scene/NeighborProjector\";\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    /** Spatial index over the current scene, built lazily and memoised per transform. */\n    private cullIndex?: CullIndex;\n    private cullIndexTransform?: CoordFn;\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, spill?: NeighborSpill): SceneBuildResult {\n        this.lastBuildResult = this.pipeline.buildScene(area, plane, zIndex, lens, spill);\n        this.standaloneExitShapeSet = new Set(\n            this.lastBuildResult.standaloneExitShapeRefs.map(r => r.shape),\n        );\n        this.cullIndex = undefined;\n        this.cullIndexTransform = undefined;\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        this.cullIndex = undefined;\n        this.cullIndexTransform = undefined;\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 the set of\n     * currently-visible managed shapes (rooms, exits, labels, stubs…) by\n     * querying the spatial {@link CullIndex} — O(visible cells), not O(all\n     * shapes). Shapes outside this set that are *known to the index* should be\n     * hidden; shapes the index has never heard of are unmanaged pass-throughs\n     * (always visible) and the caller must leave them alone.\n     *\n     * When culling is disabled the full managed-shape set is returned, so the\n     * caller's delta logic naturally reveals everything.\n     */\n    cullInteractive(coordinateTransform: CoordFn = IDENTITY_TRANSFORM): Set<Shape> {\n        if (!this.lastBuildResult) return new Set();\n        const index = this.ensureCullIndex(coordinateTransform);\n        if (!this.settings.cullingEnabled) return new Set(index.getAllShapes());\n        const viewport = this.camera.getCullingViewport(this.settings.cullingBounds);\n        return index.queryVisible(viewport);\n    }\n\n    /**\n     * Every shape the cull index can hide. Callers seed their \"previously\n     * visible\" set with this after a rebuild (all entries start visible) so the\n     * first cull pass hides the off-screen ones.\n     */\n    managedShapes(coordinateTransform: CoordFn = IDENTITY_TRANSFORM): Set<Shape> {\n        if (!this.lastBuildResult) return new Set();\n        return new Set(this.ensureCullIndex(coordinateTransform).getAllShapes());\n    }\n\n    /** Build (or reuse) the spatial index for the current scene + transform. */\n    private ensureCullIndex(coordinateTransform: CoordFn): CullIndex {\n        if (this.cullIndex && this.cullIndexTransform === coordinateTransform) {\n            return this.cullIndex;\n        }\n        const index = new CullIndex();\n        index.build(this.buildCullEntries(coordinateTransform));\n        this.cullIndex = index;\n        this.cullIndexTransform = coordinateTransform;\n        return index;\n    }\n\n    /**\n     * Project every cullable shape's world-space bounds into scene space and\n     * collect them as index entries. Mirrors the bounds used by\n     * `buildCullingVisibilityMap` exactly so the index and the export-path\n     * predicate agree on which shapes a viewport contains.\n     */\n    private buildCullEntries(coordinateTransform: CoordFn): CullIndexEntry[] {\n        const result = this.lastBuildResult!;\n        const half = this.settings.roomSize / 2;\n        const entries: CullIndexEntry[] = [];\n\n        const add = (shape: Shape, minX: number, minY: number, maxX: number, maxY: number) => {\n            const b = projectBounds(minX, minY, maxX, maxY, coordinateTransform);\n            entries.push({shape, minX: b.minX, minY: b.minY, maxX: b.maxX, maxY: b.maxY});\n        };\n\n        for (const {room, shape} of result.roomShapeRefs.values()) {\n            add(shape, room.x - half, room.y - half, room.x + half, room.y + half);\n        }\n        for (const {shape, bounds: b} of result.standaloneExitShapeRefs) {\n            add(shape, b.x, b.y, b.x + b.width, b.y + b.height);\n        }\n        for (const {shape, bounds: b} of result.labelShapeRefs) {\n            add(shape, b.x, b.y, b.x + b.width, b.y + b.height);\n        }\n        for (const {shape, bounds: b} of result.specialExitShapeRefs) {\n            add(shape, b.x, b.y, b.x + b.width, b.y + b.height);\n        }\n        for (const {shape, bounds: b} of result.stubShapeRefs) {\n            add(shape, b.x, b.y, b.x + b.width, b.y + b.height);\n        }\n        for (const {shape, bounds: b} of result.areaExitLabelShapeRefs) {\n            add(shape, b.x, b.y, b.x + b.width, b.y + b.height);\n        }\n\n        return entries;\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, LayerId} 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    /** Scene layer this group belongs to; drives coalesced draw ordering. */\n    layer?: LayerId;\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    layer?: LayerId;\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 */\n/**\n * Style key for a coalescable entry, or `null` if it must be replayed\n * individually. Batchable iff the entry is a single primitive drawn under the\n * normal camera transform with solid (non-gradient) paint and no dash:\n *   - rect / circle  → the common room body\n *   - line           → the common two-way exit link\n * Multi-command entries (symbols, emboss/multi-ring rooms, one-way arrows with\n * heads, doors, inner-exit triangles), gradient fills, dashed/alpha strokes,\n * and no-scale entries all return `null` and keep the per-entry replay path.\n */\nfunction batchKey(entry: DrawEntry): string | null {\n    if (entry.noScaling || entry.commands.length !== 1) return null;\n    const cmd = entry.commands[0];\n    if (cmd.type === \"rect\") {\n        if (typeof cmd.fill !== \"string\" && cmd.fill !== undefined) return null;\n        if (cmd.dash) return null;\n        return `r|${cmd.fill ?? \"\"}|${cmd.stroke ?? \"\"}|${cmd.sw}|${cmd.cr}`;\n    }\n    if (cmd.type === \"circle\") {\n        if (typeof cmd.fill !== \"string\" && cmd.fill !== undefined) return null;\n        if (cmd.dash) return null;\n        return `c|${cmd.fill ?? \"\"}|${cmd.stroke ?? \"\"}|${cmd.sw}|${cmd.r}`;\n    }\n    if (cmd.type === \"line\") {\n        if (cmd.dash || cmd.alpha !== undefined) return null;\n        return `l|${cmd.stroke ?? \"\"}|${cmd.sw}|${cmd.lineCap ?? \"\"}|${cmd.lineJoin ?? \"\"}`;\n    }\n    return null;\n}\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    /** Reusable bucket map for coalesced draws — cleared per frame, never reallocated. */\n    private readonly buckets = new Map<string, DrawEntry[]>();\n\n    constructor(konvaLayer: Konva.Layer, private readonly coalesce: () => boolean = () => false) {\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                if (self.coalesce()) {\n                    self.drawCoalesced(ctx);\n                } else {\n                    self.drawPerEntry(ctx);\n                }\n            },\n        });\n        konvaLayer.add(this.konvaShape);\n    }\n\n    /** Original path: one setTransform + replay per visible entry, in order. */\n    private drawPerEntry(ctx: CanvasRenderingContext2D): void {\n        const base = ctx.getTransform();\n        const a = base.a, b = base.b, c = base.c, d = base.d;\n        for (const entry of this.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    /**\n     * Fast path: same-style primitives are accumulated into one path per style\n     * and drawn with a single `fill()`/`stroke()`. Done in two sweeps — link\n     * entries (exit lines) first, then room entries — so rooms stay painted on\n     * top of exits, matching the per-entry insertion order. Within a sweep,\n     * non-batchable entries (one-way arrows, doors, symbols, emboss, inner-exit\n     * triangles…) replay per-entry; batched primitives flush afterwards. Exit\n     * lines and room bodies don't meaningfully overlap same-layer siblings, so\n     * reordering them within a sweep is visually immaterial.\n     */\n    private drawCoalesced(ctx: CanvasRenderingContext2D): void {\n        const base = ctx.getTransform();\n        // Sweep 1: everything that isn't a room (exit links) — drawn underneath.\n        this.sweep(ctx, base, (entry) => entry.layer !== \"room\");\n        // Sweep 2: rooms — drawn on top.\n        this.sweep(ctx, base, (entry) => entry.layer === \"room\");\n        ctx.setTransform(base);\n    }\n\n    private sweep(ctx: CanvasRenderingContext2D, base: DOMMatrix, accept: (e: DrawEntry) => boolean): void {\n        const a = base.a, b = base.b, c = base.c, d = base.d, e = base.e, f = base.f;\n        const buckets = this.buckets;\n        buckets.clear();\n\n        for (const entry of this.entries) {\n            if (!entry.visible || !accept(entry)) continue;\n            const key = batchKey(entry);\n            if (key === null) {\n                const tx = a * entry.x + c * entry.y + e;\n                const ty = b * entry.x + d * entry.y + 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) replayCommand(ctx, cmd);\n            } else {\n                let list = buckets.get(key);\n                if (!list) buckets.set(key, (list = []));\n                list.push(entry);\n            }\n        }\n        if (buckets.size === 0) return;\n\n        // Camera transform applied once; batched primitives are in world coords.\n        ctx.setTransform(a, b, c, d, e, f);\n        for (const list of buckets.values()) {\n            const cmd = list[0].commands[0];\n            ctx.beginPath();\n            if (cmd.type === \"rect\") {\n                const roundable = cmd.cr > 0 && typeof ctx.roundRect === \"function\";\n                for (const entry of list) {\n                    if (roundable) ctx.roundRect(entry.x + cmd.x, entry.y + cmd.y, cmd.w, cmd.h, cmd.cr);\n                    else ctx.rect(entry.x + cmd.x, entry.y + cmd.y, cmd.w, cmd.h);\n                }\n                if (cmd.fill) { ctx.fillStyle = cmd.fill as string; ctx.fill(); }\n                if (cmd.stroke && cmd.sw > 0) {\n                    ctx.strokeStyle = cmd.stroke; ctx.lineWidth = cmd.sw;\n                    ctx.setLineDash([]); ctx.stroke();\n                }\n            } else if (cmd.type === \"circle\") {\n                for (const entry of list) {\n                    const cx = entry.x + cmd.cx, cy = entry.y + cmd.cy;\n                    ctx.moveTo(cx + cmd.r, cy);\n                    ctx.arc(cx, cy, cmd.r, 0, Math.PI * 2);\n                }\n                if (cmd.fill) { ctx.fillStyle = cmd.fill as string; ctx.fill(); }\n                if (cmd.stroke && cmd.sw > 0) {\n                    ctx.strokeStyle = cmd.stroke; ctx.lineWidth = cmd.sw;\n                    ctx.setLineDash([]); ctx.stroke();\n                }\n            } else if (cmd.type === \"line\") {\n                for (const entry of list) {\n                    const pts = entry.commands[0] as typeof cmd;\n                    const p = pts.points;\n                    if (p.length < 4) continue;\n                    ctx.moveTo(p[0] + entry.x, p[1] + entry.y);\n                    for (let i = 2; i < p.length; i += 2) ctx.lineTo(p[i] + entry.x, p[i + 1] + entry.y);\n                }\n                if (cmd.stroke) ctx.strokeStyle = cmd.stroke;\n                ctx.lineWidth = cmd.sw;\n                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            }\n        }\n    }\n\n    addNode(node: RecordingGroupNode): void {\n        const entry: DrawEntry = {\n            x: node.x,\n            y: node.y,\n            noScaling: node.noScaling,\n            layer: node.layer,\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    node.layer = group.layer;\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 type {ViewportBounds} from \"../types/Settings\";\n\n/**\n * Optional capability interface for readers that materialise rooms\n * per-viewport instead of holding a full object graph (see\n * `SkeletonMapReader` in `src/bigmap/`).\n *\n * The interactive backend detects this capability (via\n * {@link isViewportDataSource}) and, when present:\n *  - pushes padded viewport bounds into the reader before every scene build,\n *    rebuilding the scene when the camera leaves the previously applied\n *    window (rebuild-on-pan), and\n *  - drives the raster LOD overview through {@link forEachInBounds}, which\n *    visits raw room positions without materialising `MapData.Room` objects.\n *\n * All bounds are renderer space (the space `getViewportBounds()` reports).\n */\nexport interface ViewportDataSource {\n    readonly viewportAware: true;\n    /**\n     * Set the window that subsequent `getPlane().getRooms()` /\n     * `getLinkExits()` calls materialise. Implementations must bump their\n     * areas' `getVersion()` when the bounds actually change.\n     */\n    setViewport(bounds: ViewportBounds): void;\n    getViewport(): ViewportBounds;\n    /** Total rooms on the (area, z) plane — input to the LOD mode decision. */\n    getPlaneRoomCount(areaId: number, z: number): number;\n    /** Cheap upper bound of the rooms inside `bounds` on that plane. */\n    estimateVisibleCount(areaId: number, z: number, bounds: ViewportBounds): number;\n    /** Visit every room inside `bounds` (renderer-space x/y + env id). */\n    forEachInBounds(\n        areaId: number, z: number, bounds: ViewportBounds,\n        fn: (x: number, y: number, envId: number) => void,\n    ): void;\n}\n\nexport function isViewportDataSource(reader: unknown): reader is ViewportDataSource {\n    return !!reader && (reader as ViewportDataSource).viewportAware === true;\n}\n","/**\n * Pure pixel painter for the raster LOD overview: each room becomes a small\n * filled box in an `ImageData`, written directly as packed 32-bit pixels.\n * Fast enough to repaint a million-room viewport in a few milliseconds —\n * which is exactly the regime where Konva nodes are not an option.\n */\n\nexport interface RasterPaintParams {\n    /** Camera scale — screen = map * scale + offset. */\n    scale: number;\n    offsetX: number;\n    offsetY: number;\n    /** Packed little-endian RGBA (see {@link cssToPackedRGBA}) for an env id. */\n    colorOf: (envId: number) => number;\n    /**\n     * `settings.roomSize` at the time of painting — how large a room's own\n     * footprint is relative to the ~1-map-unit spacing between room centres.\n     * Only ever GROWS the box beyond the gap-free minimum (when > 1, rooms\n     * overlap their neighbours in vector mode too); values ≤ 1 leave the box\n     * at the gap-free minimum, since shrinking to match a smaller footprint\n     * would reintroduce visible gaps/erosion in the overview raster is meant\n     * to avoid. Omit (or 1) for the previous gap-free-only behaviour.\n     */\n    roomSize?: number;\n}\n\n/**\n * Room box side in pixels for a given camera scale. Must always cover the\n * inter-room spacing (= `scale` px, fractional): `round(scale)` under-covers\n * at some zooms and the sub-pixel gaps read as a flickering grid, so\n * `ceil(scale)+1` guarantees overlap → consistently solid regions. This is\n * the FLOOR — `roomSize` above 1 (rooms configured larger than the spacing\n * between them) grows the box further so the raster overview's density\n * roughly matches how much vector mode's rooms overlap their neighbours;\n * `roomSize` at or below 1 never shrinks the box below that floor.\n */\nexport function rasterBoxSize(scale: number, roomSize = 1): number {\n    const gapFree = Math.ceil(scale) + 1;\n    const sized = Math.ceil(scale * roomSize) + 1;\n    return Math.max(1, Math.min(48, Math.max(gapFree, sized)));\n}\n\n/**\n * Paint every room `visit` yields into `img`. Pixels not covered by a room\n * are left untouched (alpha 0 on a fresh ImageData), so the layer composites\n * over the stage background — no background fill here.\n *\n * `visit` is typically `ViewportDataSource.forEachInBounds` curried with the\n * painted region's map-space bounds.\n */\nexport function paintRasterOverview(\n    img: ImageData,\n    visit: (fn: (x: number, y: number, envId: number) => void) => void,\n    p: RasterPaintParams,\n): void {\n    const W = img.width, H = img.height;\n    const data = new Uint32Array(img.data.buffer);\n    const s = rasterBoxSize(p.scale, p.roomSize);\n    const half = s >> 1;\n    visit((x, y, envId) => {\n        const sx = Math.round(x * p.scale + p.offsetX) - half;\n        const sy = Math.round(y * p.scale + p.offsetY) - half;\n        const packed = p.colorOf(envId);\n        for (let dy = 0; dy < s; dy++) {\n            const py = sy + dy;\n            if (py < 0 || py >= H) continue;\n            const row = py * W;\n            for (let dx = 0; dx < s; dx++) {\n                const px = sx + dx;\n                if (px >= 0 && px < W) data[row + px] = packed;\n            }\n        }\n    });\n}\n\n/** `#rgb` / `#rrggbb` / `rgb(a)(r,g,b[,a])` → packed little-endian RGBA uint32 (alpha 255). */\nexport function cssToPackedRGBA(css: string): number {\n    const c = css.trim();\n    let r = 0, g = 0, b = 0;\n    if (c[0] === \"#\") {\n        if (c.length === 4) {\n            r = parseInt(c[1] + c[1], 16);\n            g = parseInt(c[2] + c[2], 16);\n            b = parseInt(c[3] + c[3], 16);\n        } else {\n            r = parseInt(c.slice(1, 3), 16);\n            g = parseInt(c.slice(3, 5), 16);\n            b = parseInt(c.slice(5, 7), 16);\n        }\n    } else {\n        const m = c.match(/(\\d+)[,\\s]+(\\d+)[,\\s]+(\\d+)/);\n        if (m) {\n            r = +m[1];\n            g = +m[2];\n            b = +m[3];\n        }\n    }\n    return ((0xff << 24) | (b << 16) | (g << 8) | r) >>> 0;\n}\n","import Konva from \"konva\";\nimport type {Camera} from \"../../camera/Camera\";\nimport type {ViewportBounds} from \"../../types/Settings\";\nimport {cssToPackedRGBA, paintRasterOverview} from \"./RasterOverview\";\n\n/** Visit every room (renderer-space x/y + env id) inside `bounds`. */\nexport type RasterVisit = (\n    bounds: ViewportBounds,\n    fn: (x: number, y: number, envId: number) => void,\n) => void;\n\n/** Fraction of the viewport added on each side of the painted region. */\nconst REGION_PAD = 0.25;\n/** Hard cap on the offscreen canvas edge, in device pixels. */\nconst MAX_CANVAS_PX = 8192;\n\n/**\n * Owns the raster LOD underlay: an offscreen canvas painted with the room\n * overview, shown through a single map-space `Konva.Image` on the (bottom)\n * LOD layer. Because the image is positioned in map coordinates, the stage\n * transform pans and zooms it like any other node — panning inside the\n * painted region costs nothing, and a repaint is only needed when the camera\n * escapes the region or the zoom changes (RAF-coalesced; the stretched image\n * covers the frames in between).\n *\n * Note: the raster paints raw renderer-space coordinates — decorator styles\n * with a `worldToScene` transform (e.g. isometric) are not applied to it.\n */\nexport class LodController {\n    private readonly canvas: HTMLCanvasElement;\n    private image?: Konva.Image;\n    private visit: RasterVisit | null = null;\n    private paintedRegion: ViewportBounds | null = null;\n    private paintedScale = 0;\n    private repaintScheduled = false;\n    private destroyed = false;\n    private readonly packedCache = new Map<number, number>();\n\n    constructor(\n        private readonly layer: Konva.Layer,\n        private readonly camera: Camera,\n        private readonly colorCss: (envId: number) => string,\n        private readonly roomSizeOf: () => number,\n    ) {\n        this.canvas = Konva.Util.createCanvasElement();\n        this.layer.visible(false);\n    }\n\n    /** Activate the raster overview over `visit` and paint immediately. */\n    setSource(visit: RasterVisit): void {\n        this.visit = visit;\n        this.packedCache.clear();\n        this.paint();\n    }\n\n    /** Deactivate and hide the overview (vector mode). */\n    clear(): void {\n        if (this.visit === null && !this.layer.visible()) return;\n        this.visit = null;\n        this.paintedRegion = null;\n        this.layer.visible(false);\n        this.layer.batchDraw();\n    }\n\n    /**\n     * Camera moved: repaint (coalesced) if the viewport left the painted\n     * region or the zoom changed; otherwise the stage transform already\n     * moved the image correctly and there is nothing to do.\n     */\n    onViewportChange(): void {\n        if (!this.visit || this.repaintScheduled) return;\n        const r = this.paintedRegion;\n        if (r && this.camera.getScale() === this.paintedScale) {\n            const vp = this.camera.getViewportBounds();\n            if (vp.minX >= r.minX && vp.maxX <= r.maxX &&\n                vp.minY >= r.minY && vp.maxY <= r.maxY) return;\n        }\n        this.repaintScheduled = true;\n        const run = () => {\n            this.repaintScheduled = false;\n            if (!this.destroyed && this.visit) this.paint();\n        };\n        if (typeof requestAnimationFrame !== \"undefined\") requestAnimationFrame(run);\n        else queueMicrotask(run);\n    }\n\n    destroy(): void {\n        this.destroyed = true;\n        this.visit = null;\n    }\n\n    private colorOf = (envId: number): number => {\n        let v = this.packedCache.get(envId);\n        if (v === undefined) {\n            v = cssToPackedRGBA(this.colorCss(envId));\n            this.packedCache.set(envId, v);\n        }\n        return v;\n    };\n\n    private paint(): void {\n        const visit = this.visit;\n        if (!visit) return;\n        const vp = this.camera.getViewportBounds();\n        const scale = this.camera.getScale();\n        const padX = (vp.maxX - vp.minX) * REGION_PAD;\n        const padY = (vp.maxY - vp.minY) * REGION_PAD;\n        const region: ViewportBounds = {\n            minX: vp.minX - padX, maxX: vp.maxX + padX,\n            minY: vp.minY - padY, maxY: vp.maxY + padY,\n        };\n        const wPx = Math.min(MAX_CANVAS_PX, Math.max(1, Math.ceil((region.maxX - region.minX) * scale)));\n        const hPx = Math.min(MAX_CANVAS_PX, Math.max(1, Math.ceil((region.maxY - region.minY) * scale)));\n\n        this.canvas.width = wPx;\n        this.canvas.height = hPx;\n        const ctx = this.canvas.getContext(\"2d\")!;\n        const img = ctx.createImageData(wPx, hPx);\n        paintRasterOverview(img, fn => visit(region, fn), {\n            scale,\n            offsetX: -region.minX * scale,\n            offsetY: -region.minY * scale,\n            colorOf: this.colorOf,\n            roomSize: this.roomSizeOf(),\n        });\n        ctx.putImageData(img, 0, 0);\n\n        // The image lives in map space over the painted region; the stage\n        // transform does the rest.\n        if (!this.image) {\n            this.image = new Konva.Image({image: this.canvas, listening: false});\n            this.layer.add(this.image);\n        } else {\n            this.image.image(this.canvas);\n        }\n        this.image.position({x: region.minX, y: region.minY});\n        this.image.size({\n            width: region.maxX - region.minX,\n            height: region.maxY - region.minY,\n        });\n        this.paintedRegion = region;\n        this.paintedScale = scale;\n        this.layer.visible(true);\n        this.layer.batchDraw();\n    }\n}\n","export interface LodDecisionInput {\n    /** Total rooms on the whole (area, z) plane — NOT the viewport count. */\n    planeRoomCount: number;\n    /** Camera scale (screen pixels per map unit), `camera.getScale()`. */\n    scale: number;\n    stageWidth: number;\n    stageHeight: number;\n    /** Max rooms the vector (Konva) scene may hold, `settings.lodRoomBudget`. */\n    roomBudget: number;\n}\n\n/**\n * Decide raster overview vs full vector detail.\n *\n * If the WHOLE plane fits the budget, always draw vectors — a small area\n * never needs the raster overview. Genuinely large planes switch by ZOOM,\n * not by in-view room count: count varies with local density and would flip\n * modes mid-pan, while scale is constant during a pan. Bound: a fully-packed\n * viewport holds ~stageW*stageH/scale² rooms (one per map unit), so vectors\n * are safe once that worst case is within budget.\n */\nexport function shouldUseRaster(i: LodDecisionInput): boolean {\n    return i.planeRoomCount > i.roomBudget &&\n        i.scale * i.scale < (i.stageWidth * i.stageHeight) / i.roomBudget;\n}\n\nexport type LodMode = \"vector\" | \"roomsOnly\" | \"raster\";\n\nexport interface LodModeInput extends Omit<LodDecisionInput, \"roomBudget\"> {\n    /** Max rooms the vector scene may hold before switching to raster. */\n    roomBudget: number;\n    /**\n     * Above this many rooms (and below `roomBudget`), exit lines are dropped\n     * but rooms still render as full vector shapes — a bridge tier between\n     * full vector and raster. Exit pairing + exit-shape building is typically\n     * the single largest share of a dense rebuild's cost (exit count often\n     * runs ~2x room count), so this tier buys back most of that cost while\n     * keeping real vector rooms, not raster pixels. Must be ≤ `roomBudget`;\n     * treated as unlimited (never hides exits) if omitted.\n     */\n    exitBudget?: number;\n}\n\n/**\n * Decide the LOD tier: full vector (rooms + exits + hit-test-eligible),\n * rooms-only vector (exits dropped, rooms still real vector shapes), or\n * raster overview. Same pan-invariant zoom-based math as {@link shouldUseRaster}\n * applied at two budgets — `exitBudget` always yields \"roomsOnly\" no later\n * (i.e. at an equal or lower zoom-out) than `roomBudget` yields \"raster\",\n * since exitBudget ≤ roomBudget.\n */\nexport function computeLodMode(i: LodModeInput): LodMode {\n    if (shouldUseRaster({...i, roomBudget: i.roomBudget})) return \"raster\";\n    if (i.exitBudget !== undefined && shouldUseRaster({...i, roomBudget: i.exitBudget})) return \"roomsOnly\";\n    return \"vector\";\n}\n","import type {IArea} from \"../../reader/Area\";\n\n/**\n * Wrap an {@link IArea} so `getLinkExits` reports none, while every other\n * method delegates unchanged. Used for the \"rooms only\" LOD tier: a bridge\n * between full vector (rooms + exit lines) and the raster overview — rooms\n * still render as real vector shapes (env colour, border, shape), but exit\n * pairing and exit-line shape building are skipped entirely, which is a large\n * share of a dense rebuild's cost (pairing scales with exit count, typically\n * ~2x room count in a well-connected area).\n *\n * A plain object literal rather than a class: `IArea` implementations are\n * often plain objects or class instances whose methods live on the\n * prototype, so `{...area}` would silently drop them — explicit delegation\n * avoids that trap.\n */\nexport function withoutLinkExits(area: IArea): IArea {\n    return {\n        getAreaName: () => area.getAreaName(),\n        getAreaId: () => area.getAreaId(),\n        getVersion: () => area.getVersion(),\n        getPlane: (zIndex: number) => area.getPlane(zIndex),\n        getPlanes: () => area.getPlanes(),\n        getZLevels: () => area.getZLevels(),\n        getRooms: () => area.getRooms(),\n        getFullBounds: () => area.getFullBounds(),\n        getLinkExits: () => [],\n    };\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 {computeNeighborSpill, spillPositionMap, ProjectedMapReader} from \"../scene/NeighborProjector\";\nimport type {NeighborSpill} from \"../scene/NeighborProjector\";\nimport type {IMapReader} from \"../reader/MapReader\";\nimport {isViewportDataSource} from \"../reader/ViewportDataSource\";\nimport {projectBounds} from \"../render/CullIndex\";\nimport {LodController, type RasterVisit} from \"./lod/LodController\";\nimport {computeLodMode, type LodMode} from \"./lod/lodDecision\";\nimport {withoutLinkExits} from \"./lod/roomsOnlyArea\";\nimport {hiddenAwareLens} from \"../lens/hiddenAwareLens\";\nimport {defaultExitTreatment} from \"../lens/RoomLens\";\nimport {hiddenRoomLayoutOptions} from \"../scene/RoomFlags\";\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 * Fraction of the viewport added per side before pushing bounds to a\n * viewport-aware reader (see {@link KonvaRenderBackend.padViewport}). Shared\n * with the LOD decision so the \"should this plane be vector at this zoom\"\n * check accounts for the extra rooms the padding itself pulls in — without\n * this, the actual padded room count can exceed `lodRoomBudget` right at the\n * flip, since the raw decision only sizes the unpadded camera viewport.\n *\n * Deliberately generous (50% per side, not just enough to cover edge-room\n * popping): rebuild-on-pan only fires once the camera leaves this padded\n * region, and panning inside it is free (the existing viewport culling\n * already handles it) — so a bigger pad directly buys more pan distance\n * between rebuilds. This does NOT make an individual rebuild bigger at the\n * LOD flip: {@link VIEWPORT_PAD_AREA_FACTOR} shrinks the effective budget fed\n * into the flip decision by the same factor, so the flip point retreats to\n * compensate and the padded room count at the flip stays pinned near\n * `lodRoomBudget` regardless of how generous the padding is.\n */\nconst VIEWPORT_PAD_FRACTION = 0.5;\n/** Worst-case area inflation from padding every side by {@link VIEWPORT_PAD_FRACTION}. */\nconst VIEWPORT_PAD_AREA_FACTOR = (1 + 2 * VIEWPORT_PAD_FRACTION) ** 2;\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    /** Bottom-most layer: raster LOD overview underlay (see Settings.lodEnabled). */\n    readonly lodLayer: Konva.Layer;\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    /**\n     * Managed shapes visible after the last cull pass. applyClipping diffs the\n     * fresh visible set against this and flips only the entries that changed,\n     * so a pan touches O(shapes entering/leaving the viewport), not O(all).\n     * Seeded with every managed shape on rebuild (all entries start visible) so\n     * the first pass hides the off-screen ones.\n     */\n    private lastVisibleShapes: Set<Shape> = new Set();\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    /**\n     * Projected positions of the current build's spilled neighbouring-area\n     * rooms (roomId → current-area-frame x/y). Empty when neighbour spill is\n     * off or yields nothing. Drives {@link overlayReader} so highlights/paths\n     * on spilled rooms land at their projected spots.\n     */\n    private spilledRoomPositions: Map<number, {x: number; y: number}> = new Map();\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    // --- LOD + viewport-aware reader state ---\n    private readonly lodController: LodController;\n    /** Current LOD tier for the displayed plane — see {@link computeLodMode}. */\n    private lodMode: LodMode = \"vector\";\n    /** Padded bounds last pushed into a ViewportDataSource reader (rebuild-on-pan hysteresis). */\n    private lastAppliedViewport: ViewportBounds | null = null;\n    /** Camera scale at the last applied viewport — see {@link onCameraViewportChanged}. */\n    private lastAppliedScale: number | null = null;\n    private refreshScheduled = false;\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        // LOD raster underlay sits below everything else; pixels it does not\n        // paint stay transparent so the container background shows through.\n        this.lodLayer = new Konva.Layer({listening: false});\n        this.stage.add(this.lodLayer);\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, () => state.settings.coalesceRooms);\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        this.lodController = new LodController(\n            this.lodLayer, this.camera,\n            envId => this.state.mapReader.getColorValue(envId),\n            () => this.state.settings.roomSize,\n        );\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, () => this.state.settings.coalesceRooms);\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            if (this.hitTestBudgetExceeded()) {\n                this.hitTester.clear();\n            } else {\n                this.hitTester.build(this.lastHitShapes, this.state.settings.roomSize, forward, layerOffset);\n            }\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        this.lodController.destroy();\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.onCameraViewportChanged(vpBounds);\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    /**\n     * Camera-move follow-up for LOD and viewport-aware readers. A scene\n     * rebuild (RAF-coalesced) is scheduled when:\n     *  - the LOD mode decision flipped (zoom crossed the raster threshold), or\n     *  - a {@link ViewportDataSource} reader's camera left the padded viewport\n     *    applied on the last build (rebuild-on-pan with hysteresis — panning\n     *    inside the padding is covered by the existing culling), or\n     *  - the zoom has drifted materially (±20%) from the scale the padded\n     *    viewport was computed at. The generous (50%-per-side) padding means a\n     *    pure zoom-IN can leave the camera viewport spatially inside the old\n     *    padded region indefinitely — without this check the reader would\n     *    keep reporting the stale, much-too-wide room count (visibleEstimate,\n     *    the vector/hit-test budget decisions) forever while zooming in,\n     *    instead of narrowing to reflect what's actually worth materialising\n     *    at the new zoom.\n     * In raster mode the underlay repaints itself independently (also\n     * coalesced, and only when the painted region no longer covers the view).\n     */\n    private onCameraViewportChanged(vpBounds: ViewportBounds): void {\n        const {currentAreaInstance, currentZIndex} = this.state;\n        if (currentAreaInstance && currentZIndex !== undefined && this.hasRealViewport()) {\n            let needRefresh =\n                this.state.settings.lodEnabled && this.computeLodMode() !== this.lodMode;\n            if (!needRefresh && isViewportDataSource(this.state.mapReader)) {\n                const a = this.lastAppliedViewport;\n                const s = this.lastAppliedScale;\n                const scale = this.camera.getScale();\n                needRefresh = !a || s === null ||\n                    vpBounds.minX < a.minX || vpBounds.maxX > a.maxX ||\n                    vpBounds.minY < a.minY || vpBounds.maxY > a.maxY ||\n                    scale > s * 1.2 || scale < s / 1.2;\n            }\n            if (needRefresh) this.scheduleRefresh();\n        }\n        this.lodController.onViewportChange();\n    }\n\n    private scheduleRefresh(): void {\n        if (this.refreshScheduled) return;\n        this.refreshScheduled = true;\n        const run = () => {\n            this.refreshScheduled = false;\n            if (!this.destroyed) this.refresh();\n        };\n        if (typeof requestAnimationFrame !== \"undefined\") requestAnimationFrame(run);\n        else queueMicrotask(run);\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.lodController.clear();\n            this.lodMode = \"vector\";\n            this.stage.batchDraw();\n            return;\n        }\n        this.updateBackground();\n\n        // Viewport-aware readers get the padded camera bounds pushed BEFORE the\n        // build, so a plane never materialises more than the padded viewport.\n        // `padded` is in SCENE space (camera.getViewportBounds()); readers like\n        // SkeletonMapReader store raw WORLD-space room columns, so under a\n        // warped style (e.g. Isometric) the bounds must go through the\n        // inverse transform first — otherwise a scene-space window gets\n        // interpreted as a world-space one and, once zoomed in enough that the\n        // discrepancy no longer accidentally overlaps real rooms, the plane\n        // materialises nothing. Same corner-projection trick as CullIndex uses\n        // for the forward direction, just inverted.\n        const reader = this.state.mapReader;\n        if (isViewportDataSource(reader) && this.hasRealViewport()) {\n            const padded = this.padViewport(this.camera.getViewportBounds());\n            const worldBounds = projectBounds(padded.minX, padded.minY, padded.maxX, padded.maxY, this.coordinateInverse);\n            reader.setViewport(worldBounds);\n            this.lastAppliedViewport = padded;\n            this.lastAppliedScale = this.camera.getScale();\n        }\n\n        const mode = this.computeLodMode();\n        this.lodMode = mode;\n        if (mode === \"raster\") {\n            // Raster overview: the underlay paints every room as pixels; the\n            // vector scene is cleared instead of built. Position marker,\n            // highlights, paths and scene overlays still render below.\n            this.clearVectorScene();\n            this.lodController.setSource(this.rasterVisit(currentAreaInstance, plane, currentZIndex));\n        } else {\n            this.lodController.clear();\n            // \"roomsOnly\" swaps in an area that reports no link exits, so the\n            // pipeline skips exit pairing and exit-shape building entirely —\n            // rooms still build as full vector shapes.\n            const areaForBuild = mode === \"roomsOnly\" ? withoutLinkExits(currentAreaInstance) : currentAreaInstance;\n            this.buildScene(areaForBuild, plane, currentZIndex);\n            this.onSceneBuilt();\n        }\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        this.emitLodEvent(mode, currentAreaInstance, plane, currentZIndex);\n    }\n\n    // --- LOD helpers ---\n\n    /**\n     * Viewport clamping and LOD only engage once the camera has a real size.\n     * Container-backed stages always do; a headless backend starts at 1×1\n     * (a meaningless viewport that must not clamp exports) until the caller\n     * opts in via `camera.setSize(w, h)`.\n     */\n    private hasRealViewport(): boolean {\n        return this.camera.width > 1 || this.camera.height > 1;\n    }\n\n    /**\n     * Pad viewport bounds by 6% + 1 map unit per side: rooms are selected by\n     * centre, so a room just off-screen can still have half its body in view —\n     * the margin keeps edge rooms from popping and lets the regular culling\n     * reveal margin rooms between rebuilds (rebuild-on-pan hysteresis).\n     */\n    private padViewport(vp: ViewportBounds): ViewportBounds {\n        const padX = (vp.maxX - vp.minX) * VIEWPORT_PAD_FRACTION + 1;\n        const padY = (vp.maxY - vp.minY) * VIEWPORT_PAD_FRACTION + 1;\n        return {minX: vp.minX - padX, maxX: vp.maxX + padX, minY: vp.minY - padY, maxY: vp.maxY + padY};\n    }\n\n    private planeRoomCount(area: IArea, plane: IPlane, zIndex: number): number {\n        const reader = this.state.mapReader;\n        return isViewportDataSource(reader)\n            ? reader.getPlaneRoomCount(area.getAreaId(), zIndex)\n            : plane.getRooms().length;\n    }\n\n    private computeLodMode(): LodMode {\n        const settings = this.state.settings;\n        if (!settings.lodEnabled || !this.hasRealViewport()) return \"vector\";\n        const {currentAreaInstance, currentZIndex} = this.state;\n        if (!currentAreaInstance || currentZIndex === undefined) return \"vector\";\n        const plane = currentAreaInstance.getPlane(currentZIndex);\n        if (!plane) return \"vector\";\n        // The vector scene actually materialises the PADDED viewport\n        // (padViewport), not the raw camera one — shrink both effective\n        // budgets by the same overscan factor so each flip accounts for it\n        // instead of silently drawing ~25% more rooms than configured.\n        return computeLodMode({\n            planeRoomCount: this.planeRoomCount(currentAreaInstance, plane, currentZIndex),\n            scale: this.camera.getScale(),\n            stageWidth: this.camera.width,\n            stageHeight: this.camera.height,\n            roomBudget: settings.lodRoomBudget / VIEWPORT_PAD_AREA_FACTOR,\n            exitBudget: settings.lodExitBudget / VIEWPORT_PAD_AREA_FACTOR,\n        });\n    }\n\n    /** Room source for the raster painter (never materialises rooms for viewport-aware readers). */\n    private rasterVisit(area: IArea, plane: IPlane, zIndex: number): RasterVisit {\n        const reader = this.state.mapReader;\n        if (isViewportDataSource(reader)) {\n            const areaId = area.getAreaId();\n            return (b, fn) => reader.forEachInBounds(areaId, zIndex, b, fn);\n        }\n        return (b, fn) => {\n            for (const r of plane.getRooms()) {\n                if (r.x >= b.minX && r.x <= b.maxX && r.y >= b.minY && r.y <= b.maxY) {\n                    fn(r.x, r.y, r.env);\n                }\n            }\n        };\n    }\n\n    /** Tear down the vector scene (raster mode replaces it entirely). */\n    private clearVectorScene(): void {\n        this.shapeToDrawEntry = new Map();\n        this.shapeToGroup.clear();\n        this.sceneManager.reset();\n        this.hitTester.clear();\n        this.lastHitShapes = [];\n        this.lastVisibleShapes = new Set();\n        this.spilledRoomPositions = new Map();\n        this.sceneNode.destroyChildren();\n        this.topLabelLayerNode.destroyChildren();\n        this.stage.batchDraw();\n    }\n\n    /**\n     * Above `settings.lodHitTestBudget` rooms in the current plane build, skip\n     * rebuilding the hit index instead of paying for it on every rebuild — the\n     * vector scene still renders at full detail, but clicks/hover stop\n     * resolving to a room until the density drops back under the budget.\n     * Counted in rooms (same unit as `lodRoomBudget`), not raw hittable-shape\n     * count, so the two settings are directly comparable.\n     */\n    private hitTestBudgetExceeded(): boolean {\n        if (!this.state.settings.lodEnabled) return false;\n        const {currentAreaInstance, currentZIndex} = this.state;\n        if (!currentAreaInstance || currentZIndex === undefined) return false;\n        const plane = currentAreaInstance.getPlane(currentZIndex);\n        if (!plane) return false;\n        return plane.getRooms().length > this.state.settings.lodHitTestBudget;\n    }\n\n    private emitLodEvent(mode: LodMode, area: IArea, plane: IPlane, zIndex: number): void {\n        if (!this.state.settings.lodEnabled) return;\n        const reader = this.state.mapReader;\n        const planeRoomCount = this.planeRoomCount(area, plane, zIndex);\n        const bounds = this.lastAppliedViewport ?? this.camera.getViewportBounds();\n        const visibleEstimate = isViewportDataSource(reader)\n            ? reader.estimateVisibleCount(area.getAreaId(), zIndex, bounds)\n            : planeRoomCount;\n        const hitTestActive = mode === \"raster\" ? false : !this.hitTestBudgetExceeded();\n        this.events.emit('lod', {mode, planeRoomCount, visibleEstimate, hitTestActive});\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            // Neighbour spill is anchored to the player position, so it must be\n            // rebuilt on every move when enabled. This also covers area changes:\n            // the 'area' event fires (and refreshes) *before* MapState updates\n            // positionRoomId, so that refresh sees the old room — we rebuild here\n            // once the new position is current. (A within-area move gets no\n            // 'area' event, so this is its only rebuild.)\n            if (this.state.settings.neighborSpill && roomId !== undefined) {\n                this.refresh();\n            }\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 spill = this.computeNeighborSpill(area, zIndex);\n        this.spilledRoomPositions = spill ? spillPositionMap(spill) : new Map();\n        const result = this.sceneManager.rebuild(area, plane, zIndex, this.state.lens, spill);\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     * Compute neighbouring-area spill for the current build, or `undefined` when\n     * the feature is off / the player isn't on this area+plane. Driven by the\n     * live player position so it updates as the player nears a boundary.\n     */\n    private computeNeighborSpill(area: IArea, zIndex: number): NeighborSpill | undefined {\n        const settings = this.state.settings;\n        if (!settings.neighborSpill) return undefined;\n        const playerRoomId = this.state.positionRoomId;\n        if (playerRoomId === undefined) return undefined;\n        const room = this.state.mapReader.getRoom(playerRoomId);\n        if (!room || room.area !== area.getAreaId() || room.z !== zIndex) return undefined;\n        const lens = this.state.lens;\n        return computeNeighborSpill(\n            this.state.mapReader,\n            area.getAreaId(),\n            zIndex,\n            playerRoomId,\n            settings.neighborSpillDistance,\n            (room) => lens.isVisible(room),\n        );\n    }\n\n    /**\n     * The map reader the overlay code (highlights, paths) should use. When\n     * neighbour spill produced rooms, this is a {@link ProjectedMapReader} that\n     * makes those spilled rooms look like current-area rooms at their projected\n     * positions; otherwise it's the plain reader.\n     */\n    private overlayReader(): IMapReader {\n        if (this.spilledRoomPositions.size === 0\n            || this.state.currentArea === undefined\n            || this.state.currentZIndex === undefined) {\n            return this.state.mapReader;\n        }\n        return new ProjectedMapReader(\n            this.state.mapReader,\n            this.state.currentArea,\n            this.state.currentZIndex,\n            this.spilledRoomPositions,\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        if (this.hitTestBudgetExceeded()) {\n            this.hitTester.clear();\n        } else {\n            this.hitTester.build(this.lastHitShapes, this.state.settings.roomSize, this._coordinateTransform, this.coordinateLayerOffset);\n        }\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        // All freshly-built entries start visible; treat the whole managed set\n        // as \"previously visible\" so the first cull hides the off-screen ones.\n        this.lastVisibleShapes = this.sceneManager.managedShapes(this._coordinateTransform);\n        this.applyClipping();\n        this.stage.batchDraw();\n    }\n\n    private applyClipping(): void {\n        if (!this.sceneManager.lastResult) return;\n\n        const visible = this.sceneManager.cullInteractive(this._coordinateTransform);\n        const prev = this.lastVisibleShapes;\n        let changed = false;\n\n        // Hide shapes that left the viewport since last pass.\n        for (const shape of prev) {\n            if (visible.has(shape)) continue;\n            const entry = this.shapeToDrawEntry.get(shape);\n            if (entry && entry.visible) {\n                entry.visible = false;\n                changed = true;\n            }\n        }\n        // Reveal shapes that entered the viewport since last pass.\n        for (const shape of visible) {\n            if (prev.has(shape)) continue;\n            const entry = this.shapeToDrawEntry.get(shape);\n            if (entry && !entry.visible) {\n                entry.visible = true;\n                changed = true;\n            }\n        }\n\n        this.lastVisibleShapes = visible;\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        // Raster LOD overview: the vector scene is suppressed, so skip the\n        // exit/neighbour enrichment too (on a viewport-aware reader zoomed far\n        // out it could materialise the entire plane). The marker still shows.\n        // (\"roomsOnly\" only drops the BULK scene's exit lines — this overlay\n        // looks up just the current room's own exits via the real (unwrapped)\n        // area, so it stays cheap and stays enabled in that tier.)\n        if (this.lodMode === \"raster\") {\n            if (this.positionMarker) this.positionMarker.moveToTop();\n            this.positionLayerNode.batchDraw();\n            return;\n        }\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        // Respect the active lens (exploration + hidden rooms) so the overlay\n        // never highlights exits/neighbours the main scene drops.\n        const lens = hiddenAwareLens(this.state.lens, settings.hiddenRooms === \"hide\");\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 roomA = this.state.mapReader.getRoom(exit.a);\n                const roomB = this.state.mapReader.getRoom(exit.b);\n                if (!roomA || !roomB) return;\n                const treatment = lens.getExitTreatment\n                    ? lens.getExitTreatment(exit, roomA, roomB)\n                    : defaultExitTreatment(lens, exit, roomA, roomB);\n                if (treatment !== \"full\") return; // skip hidden/partially-visible exits\n                const data = exitRenderer.renderDataWithColor(exit, currentRoomColor, this.state.currentZIndex!);\n                if (data) {\n                    // Match the main pass: a crossing into a spilled neighbour room\n                    // is shown as a plain connector, so suppress its cross-area arrow.\n                    if (data.targetRoomId !== undefined && this.spilledRoomPositions.has(data.targetRoomId)) return;\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                    // Keep a redrawn hidden room looking hidden (dashed/faded),\n                    // matching the main scene instead of overpainting it solid.\n                    ...hiddenRoomLayoutOptions(roomToRedraw, settings.hiddenRooms),\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            // overlayReader makes spilled neighbour rooms report the current\n            // area at their projected coords, so the visibility check below\n            // passes for them and the highlight lands across the boundary.\n            const room = this.overlayReader().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        const reader = this.overlayReader();\n        for (const [roomId] of this.state.highlights) {\n            // Resolve through overlayReader so spilled rooms are placed at their\n            // projected coords; gate on the resolved room's area/z (spilled\n            // rooms report the current area, current-area rooms pass as before).\n            const room = reader.getRoom(roomId);\n            if (!room || room.area !== this.state.currentArea || room.z !== this.state.currentZIndex) continue;\n            const entry = this.state.highlights.get(roomId)!;\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        const reader = this.overlayReader();\n        for (const path of this.state.paths) {\n            const data = computePathOverlay(\n                reader, 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        // lightenColor/darkenColor preserve any alpha channel in `fill`, so a\n        // translucent fill (e.g. a player marker with fillAlpha < 1) stays\n        // translucent through the gradient rather than going opaque.\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, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")\n        .replace(/\"/g, \"&quot;\").replace(/'/g, \"&#39;\");\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 {isViewportDataSource} from \"../reader/ViewportDataSource\";\nimport type {IMapReader} from \"../reader/MapReader\";\n\n/**\n * Headless exporters (`CanvasExporter`, `SvgExporter`) rebuild their scene\n * directly from `state.mapReader`, independent of the live interactive\n * camera. For a viewport-virtualized reader (`SkeletonMapReader`) that reader\n * only materialises rooms inside whatever viewport was LAST pushed onto it —\n * normally by the interactive backend, whose viewport push is itself\n * deferred to a `requestAnimationFrame` (see `KonvaRenderBackend.refresh()`),\n * so an export that runs synchronously right after a camera change (e.g. a\n * `fitArea()` call, as `MapController.renderArea()` does before generating a\n * preview thumbnail) can race it and capture a stale, wrong window instead\n * of the region it was actually asked to export.\n *\n * Pushes a viewport covering exactly `bounds` (world space, the same shape\n * `computeExportBounds` returns) before the caller builds its scene, and\n * returns a restore function that puts back whatever viewport was active\n * before — so the export never leaves the shared reader (and the interactive\n * backend's next incremental refresh) in a different state than it found it.\n */\nexport function pushExportViewport(\n    mapReader: IMapReader,\n    bounds: {x: number; y: number; w: number; h: number},\n): () => void {\n    if (!isViewportDataSource(mapReader)) return () => {};\n    const prev = mapReader.getViewport();\n    mapReader.setViewport({minX: bounds.x, maxX: bounds.x + bounds.w, minY: bounds.y, maxY: bounds.y + bounds.h});\n    return () => mapReader.setViewport(prev);\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\";\nimport {pushExportViewport} from \"./exportViewport\";\n\nconst IDENTITY_CAMERA = {scale: 1, offsetX: 0, offsetY: 0};\n\nfunction escapeXml(s: string): string {\n    return s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")\n        .replace(/\"/g, \"&quot;\").replace(/'/g, \"&#39;\");\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        // See pushExportViewport: a viewport-virtualized reader (SkeletonMapReader)\n        // only materialises rooms inside whatever viewport was last pushed onto\n        // it, which for the live interactive camera can be a render (rAF) behind\n        // — an export must push its own to be correct and reproducible.\n        const restoreViewport = pushExportViewport(state.mapReader, bounds);\n        try {\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        } finally {\n            restoreViewport();\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\";\nimport {pushExportViewport} from \"./exportViewport\";\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        // See pushExportViewport: a viewport-virtualized reader (SkeletonMapReader)\n        // only materialises rooms inside whatever viewport was last pushed onto\n        // it, which for the live interactive camera can be a render (rAF) behind\n        // — an export must push its own to be correct and reproducible.\n        const restoreViewport = pushExportViewport(state.mapReader, bounds);\n        try {\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        } finally {\n            restoreViewport();\n        }\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","import type {MapState} from \"../MapState\";\nimport type {ViewportBounds} from \"../types/Settings\";\nimport type {Shape} from \"../scene/Shape\";\nimport {placeLabels, type Direction8, type LabelPlacementItem, type Obstacle} from \"../labelPlacement\";\nimport type {SceneOverlay, SceneOverlayContext} from \"./SceneOverlay\";\n\nexport interface Waypoint {\n    roomId: number;\n    /** Label text. An array renders as stacked lines in one bubble. */\n    label: string | string[];\n    /** Marker / accent colour (hex). Default amber. */\n    color?: string;\n    /** Optional preferred label direction (nudge only). */\n    preferred?: Direction8;\n    /**\n     * Optional click handler. Fired when the waypoint's bubble is clicked.\n     * Wire pointer hits to it via {@link WaypointOverlay.hitTest} (see below).\n     * Waypoints without a handler are inert.\n     */\n    onClick?: (waypoint: Waypoint) => void;\n}\n\n/** A bubble rect placed in world space, paired with its waypoint. */\ninterface PlacedBubble {\n    wp: Waypoint;\n    x: number;\n    y: number;\n    width: number;\n    height: number;\n}\n\n/**\n * Persistent named markers anchored to rooms (shops, banks, quest givers …),\n * with auto-placed labels that dodge neighbouring rooms, exits, and each other\n * via {@link placeLabels}. A {@link SceneOverlay}, so it renders on the\n * interactive canvas and in every export.\n *\n * A room may carry more than one waypoint (e.g. a transport stop served by two\n * routes): the list is flat, so push several entries with the same `roomId` and\n * each gets its own auto-placed bubble. Alternatively give one waypoint a\n * multi-line `label` to list them in a single bubble.\n *\n * Waypoints can be clickable. Bubbles are overlay-layer shapes, so they are not\n * part of the renderer's {@link HitTester}; the overlay instead records the\n * rects it places and resolves them via {@link hitTest}. Wire pointer clicks to\n * it by converting the cursor to world space:\n *\n * ```ts\n * const waypoints = new WaypointOverlay();\n * waypoints.add({ roomId: 42, label: 'Bank', onClick: wp => console.log(wp.roomId) });\n * renderer.addSceneOverlay('waypoints', waypoints);\n *\n * container.addEventListener('click', e => {\n *     const p = renderer.camera.clientToMapPoint(e.clientX, e.clientY, container.getBoundingClientRect());\n *     if (!p) return;\n *     const wp = waypoints.hitTest(p.x, p.y);\n *     wp?.onClick?.(wp);\n * });\n * ```\n */\nexport class WaypointOverlay implements SceneOverlay {\n    private waypoints: Waypoint[] = [];\n    private ctx?: SceneOverlayContext;\n    /** Bubble rects from the last render, for click hit-testing (world space). */\n    private placed: PlacedBubble[] = [];\n\n    set(list: Waypoint[]) {\n        this.waypoints = [...list];\n        this.ctx?.invalidate();\n    }\n\n    add(waypoint: Waypoint) {\n        this.waypoints.push(waypoint);\n        this.ctx?.invalidate();\n    }\n\n    /** Remove every waypoint anchored to this room. */\n    remove(roomId: number) {\n        const before = this.waypoints.length;\n        this.waypoints = this.waypoints.filter(w => w.roomId !== roomId);\n        if (this.waypoints.length !== before) this.ctx?.invalidate();\n    }\n\n    has(roomId: number): boolean {\n        return this.waypoints.some(w => w.roomId === roomId);\n    }\n\n    /**\n     * Topmost waypoint whose bubble contains the given **world-space** point, or\n     * `undefined`. Hit-tests the bubbles placed by the last {@link render} (in\n     * reverse, so later-drawn bubbles win). Convert a pointer position to world\n     * space with `renderer.camera.clientToMapPoint(...)` before calling.\n     */\n    hitTest(worldX: number, worldY: number): Waypoint | undefined {\n        for (let i = this.placed.length - 1; i >= 0; i--) {\n            const b = this.placed[i];\n            if (worldX >= b.x && worldX <= b.x + b.width\n                && worldY >= b.y && worldY <= b.y + b.height) {\n                return b.wp;\n            }\n        }\n        return undefined;\n    }\n\n    attach(ctx: SceneOverlayContext) {\n        this.ctx = ctx;\n    }\n\n    detach() {\n        this.ctx = undefined;\n        this.placed = [];\n    }\n\n    render(state: MapState, _bounds: ViewportBounds): Shape[] {\n        // Recomputed below; clear stale hit-test rects so clicks never resolve\n        // to bubbles that are no longer drawn (plane switch, removal, …).\n        this.placed = [];\n        const areaId = state.currentArea;\n        const z = state.currentZIndex;\n        if (areaId === undefined || z === undefined || this.waypoints.length === 0) return [];\n        const plane = state.currentAreaInstance?.getPlane(z);\n        if (!plane) return [];\n\n        const rs = state.settings.roomSize;\n        const fontSize = rs * 0.6;\n        const pad = fontSize * 0.5;\n        const lineHeight = fontSize * 1.2;\n\n        // Obstacles: every room on the plane (hard) + exit lines (so labels\n        // don't sit on connectors). Exits are derived from room adjacency as\n        // thin centre-to-centre segments — reliable and in the same world space\n        // as the rooms.\n        const planeRooms = plane.getRooms();\n        const obstacles: Obstacle[] = planeRooms.map(r => ({\n            x: r.x - rs / 2, y: r.y - rs / 2, width: rs, height: rs, kind: \"room\",\n        }));\n        const seg = (x1: number, y1: number, x2: number, y2: number) => obstacles.push({\n            x: Math.min(x1, x2) - 0.03, y: Math.min(y1, y2) - 0.03,\n            width: Math.abs(x2 - x1) + 0.06, height: Math.abs(y2 - y1) + 0.06, kind: \"exit\",\n        });\n        for (const r of planeRooms) {\n            for (const tid of [...Object.values(r.exits), ...Object.values(r.specialExits)]) {\n                const t = state.mapReader.getRoom(tid);\n                if (t && t.area === areaId && t.z === z) seg(r.x, r.y, t.x, t.y);\n            }\n        }\n\n        // One placement item per waypoint that lives on the current plane.\n        // Multiple waypoints may share a room — each becomes its own item and the\n        // placer keeps them from stacking.\n        const items: LabelPlacementItem[] = [];\n        const meta: Array<{wp: Waypoint; rx: number; ry: number; lines: string[]}> = [];\n        for (const wp of this.waypoints) {\n            const room = state.mapReader.getRoom(wp.roomId);\n            if (!room || room.area !== areaId || room.z !== z) continue;\n            const lines = Array.isArray(wp.label) ? wp.label : [wp.label];\n            const longest = lines.reduce((m, l) => Math.max(m, l.length), 0);\n            const width = longest * fontSize * 0.6 + pad * 2;\n            const height = lines.length * lineHeight + fontSize * 0.4;\n            // Index (not roomId) as id, so rooms with several waypoints stay distinct.\n            items.push({x: room.x, y: room.y, width, height, id: items.length, preferred: wp.preferred});\n            meta.push({wp, rx: room.x, ry: room.y, lines});\n        }\n        if (items.length === 0) return [];\n\n        const bg = \"rgba(20, 20, 24, 0.82)\";\n        const placed = placeLabels(items, obstacles, {\n            offset: rs * 0.7,\n            // Allow escaping a junction, but cap clearance reward so labels stay\n            // close — only as clear as they need to be (off the lines), not far\n            // out in a void.\n            extend: rs * 4,\n            clearCap: rs * 1.5,\n            // If even the best spot is this cramped, the room is surrounded — sit\n            // close on an exit (north-preferred) rather than floating far out.\n            minClearance: rs * 0.7,\n        });\n\n        const shapes: Shape[] = [];\n\n        for (let i = 0; i < placed.length; i++) {\n            const p = placed[i];\n            const {wp, rx, ry} = meta[i];\n            const color = wp.color ?? \"#ffcc33\";\n\n            // Speech-bubble tail pointing at the room. For cardinal placements\n            // the base sits flush on the room-facing edge; for diagonal ones it\n            // spans the room-facing corner (a rotated corner tail).\n            const dx = rx - (p.x + p.width / 2), dy = ry - (p.y + p.height / 2);\n            const len = Math.hypot(dx, dy) || 1;\n            const ux = dx / len, uy = dy / len;          // unit vector toward the room\n            const tip = fontSize * 0.7, half = fontSize * 0.5;\n            const diagonal = p.direction.length === 2;   // ne / nw / se / sw\n            let vertices: number[];\n            if (diagonal) {\n                // Corner the room faces, and the two base points stepping back\n                // along each edge from it.\n                const cxn = ux < 0 ? p.x : p.x + p.width;\n                const cyn = uy < 0 ? p.y : p.y + p.height;\n                const leg = fontSize * 0.85;\n                vertices = [\n                    cxn + (ux < 0 ? leg : -leg), cyn,    // along the horizontal edge\n                    cxn + ux * tip, cyn + uy * tip,      // apex out the corner toward room\n                    cxn, cyn + (uy < 0 ? leg : -leg),    // along the vertical edge\n                ];\n            } else {\n                const adx = Math.abs(dx), ady = Math.abs(dy);\n                const onSide = (adx > 1e-6 ? (p.width / 2) / adx : Infinity)\n                    <= (ady > 1e-6 ? (p.height / 2) / ady : Infinity);\n                // Midpoint of the room-facing edge.\n                const ex = onSide ? (ux < 0 ? p.x : p.x + p.width) : p.x + p.width / 2;\n                const ey = onSide ? p.y + p.height / 2 : (uy < 0 ? p.y : p.y + p.height);\n                const tgx = onSide ? 0 : 1, tgy = onSide ? 1 : 0;  // base runs along the edge\n                vertices = [\n                    ex + tgx * half, ey + tgy * half,\n                    ex + ux * tip, ey + uy * tip,\n                    ex - tgx * half, ey - tgy * half,\n                ];\n            }\n            shapes.push({\n                type: \"polygon\", vertices,\n                paint: {fill: bg, stroke: color, strokeWidth: rs * 0.03},\n                layer: \"top\",\n            });\n            // Label background.\n            shapes.push({\n                type: \"rect\", x: p.x, y: p.y, width: p.width, height: p.height,\n                cornerRadius: fontSize * 0.3,\n                paint: {fill: bg, stroke: color, strokeWidth: rs * 0.03},\n                layer: \"top\",\n            });\n            // Record the bubble for click hit-testing (world space).\n            this.placed.push({wp, x: p.x, y: p.y, width: p.width, height: p.height});\n            // Label text — one shape per line, stacked and vertically centred so\n            // multi-line labels sit evenly inside the bubble.\n            const lines = meta[i].lines;\n            const topPad = (p.height - lines.length * lineHeight) / 2;\n            for (let li = 0; li < lines.length; li++) {\n                shapes.push({\n                    type: \"text\", x: p.x, y: p.y + topPad + li * lineHeight,\n                    width: p.width, height: lineHeight,\n                    text: lines[li], fontSize, fontFamily: state.settings.fontFamily,\n                    fill: \"#f4f4f6\", align: \"center\", verticalAlign: \"middle\", layer: \"top\",\n                });\n            }\n        }\n        return shapes;\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","/**\n * Optional capability interface for readers that can resolve a Mudlet room\n * content hash (`MapData.Room.hash`) directly to a room id.\n *\n * `SkeletonMapReader.getRooms()` deliberately always returns `[]` (see\n * {@link ViewportDataSource} — never materialise the full room list), so a\n * linear `getRooms().find(r => r.hash === hash)` scan — the only option\n * `IMapReader` supports on its own — can never find anything on a big/\n * streamed map. This interface gives such readers a way to opt into an O(1)\n * (or otherwise scan-free) hash lookup instead.\n */\nexport interface HashLookupCapable {\n    readonly hashLookupCapable: true;\n    /** Resolve a room's content hash to its id, or `undefined` if unknown. */\n    getRoomIdByHash(hash: string): number | undefined;\n}\n\nexport function isHashLookupCapable(reader: unknown): reader is HashLookupCapable {\n    return !!reader && (reader as HashLookupCapable).hashLookupCapable === true;\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":[48,49,50,51,52],"mappings":"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"}