@seatlayer/core 0.49.0 → 0.50.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/view3d/index.cjs +82 -4
- package/dist/view3d/index.cjs.map +1 -1
- package/dist/view3d/index.d.cts +11 -0
- package/dist/view3d/index.d.ts +11 -0
- package/dist/view3d/index.js +82 -4
- package/dist/view3d/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/view3d/index.ts","../../src/view3d/gl/context.ts","../../src/view3d/palette.ts","../../src/view3d/camera/orbit.ts","../../src/view3d/loop.ts","../../src/view3d/lod.ts","../../src/view3d/scene/nearField.ts","../../src/core/types.ts","../../src/core/units.ts","../../src/view3d/scene/geometry.ts","../../src/view3d/scene/seatChair.ts","../../src/view3d/scene/seatInstances.ts","../../src/view3d/theme.ts","../../src/view3d/labels.ts","../../src/view3d/scene/sceneModel.ts","../../src/core/venueStructure.ts","../../src/core/layout.ts","../../src/core/rake.ts","../../src/view3d/scene/deckBands.ts","../../src/view3d/scene/surface.ts","../../src/view3d/labelOverlay.ts","../../src/view3d/scene/build.ts","../../src/view3d/scene/materials.ts","../../src/view3d/pick/pickPipeline.ts","../../src/view3d/pick/encode.ts","../../src/view3d/pick/selection.ts","../../src/view3d/camera/cinematic.ts","../../src/view3d/camera/cinematicMath.ts","../../src/view3d/camera/seatViewPose.ts","../../src/view/panoramaDelivery.ts","../../src/view3d/crossfade/panorama.ts","../../src/view3d/scene/panoSphere.ts","../../src/view3d/crossfade/panoramaSphere.ts","../../src/view3d/analytics.ts","../../src/view3d/positioningContext.ts","../../src/view3d/prepareScene.ts"],"sourcesContent":["/**\n * view3d — the sole dynamic-import boundary for the lazy OGL venue-view chunk.\n *\n * const { mountVenue3D } = await import('../view3d');\n * const handle = mountVenue3D(container, { doc, seats }, { onSeatPick, getSeatView });\n * await handle.flyToSeat(seatId);\n *\n * Read-only 3D of any chart, fed entirely from the existing height contract.\n * Slice 1: orbit camera, extruded tiers/stage/GA, instanced seat dots, sub-range\n * availability, dispose + context-loss survival. Slice 2: GPU color-pick. Slice\n * 3: the fly-to-seat cinematic that dissolves into the view-from-seat panorama.\n */\n\nimport { Quat, Vec3 } from 'ogl';\nimport type { ChartDoc, ExpandedSeat } from '../core/types';\nimport { GLContext } from './gl/context';\nimport { OrbitCamera } from './camera/orbit';\nimport { RenderLoop, type RenderLoopStats } from './loop';\nimport { computeSeatLod, CHAIR_GATHER_M, CHAIR_MAX_INSTANCES, CHAIR_REBUILD_M } from './lod';\nimport { NearFieldIndex } from './scene/nearField';\nimport { SEAT_DOT_RADIUS_M } from './scene/seatInstances';\nimport { buildSceneModel, type SceneModel, type SceneZone, type SceneSection, type SceneFloor, type SceneRow } from './scene/sceneModel';\nimport { LabelOverlay } from './labelOverlay';\nimport { projectToScreen } from './labels';\nimport { buildGpuScene, type GpuScene } from './scene/build';\nimport { applySeatStates } from './scene/seatInstances';\nimport { PickPipeline } from './pick/pickPipeline';\nimport { pickPixelCoords } from './pick/encode';\nimport { diffSelection, mergeAvailabilityIntoSelection } from './pick/selection';\nimport { Cinematic, buildWaypoints, lookAtQuat, FLIGHT_DURATION_MS, FOV_END, type Vec3Arr } from './camera/cinematic';\nimport { seatViewPose } from './camera/seatViewPose';\nimport { mountPanorama, seatViewDisclosure, type PanoramaHandle, type SeatView } from './crossfade/panorama';\nimport { mountPanoramaSphere } from './crossfade/panoramaSphere';\nimport { Analytics3D, type Analytics3DCallback } from './analytics';\nimport { SEAT_STATES, type SeatState3D } from './palette';\nimport { establishPositioningContext } from './positioningContext';\nimport type { PreparedVenue3D } from './prepareScene';\n\nexport { prepareVenue3D } from './prepareScene';\n\nexport type { SeatState3D } from './palette';\nexport type { SeatView } from './crossfade/panorama';\nexport type { Analytics3DCallback, Scene3DPrepSource } from './analytics';\nexport { buildSceneModel } from './scene/sceneModel';\n\nexport interface Venue3DInput {\n doc: ChartDoc;\n /** Expanded seats (from `expandChart`) — carry x/y + resolved eyeHeightM. */\n seats: ExpandedSeat[];\n /** Optional initial per-seat state (default all available). */\n initialState?: (seat: ExpandedSeat) => SeatState3D;\n /** CPU scene prepared by `prepareVenue3D`; avoids rebuilding on mount. */\n prepared?: PreparedVenue3D;\n}\n\nexport interface Venue3DOptions {\n /** Fired on a tap that hits a seat (GPU color-pick). Not fired on empty taps. */\n onSeatPick?: (seatId: string) => void;\n /** Fired when the buyer taps a visible seat that cannot currently be selected.\n * The host can explain held/sold/filter state without turning inspection into\n * selection or briefly painting the unavailable seat as the buyer's choice. */\n onSeatInspect?: (seatId: string, state: SeatState3D) => void;\n /** Overview → section drill state. A stand tap reports its owning section;\n * Overview reports null so host navigation can stay in sync. */\n onSectionFocusChange?: (sectionId: string | null) => void;\n /** Camera target changes, including internal next/previous and Overview UI. */\n onViewTargetChange?: (seatId: string | null) => void;\n /**\n * Supplies the view-from-seat panorama for the cinematic hand-off. Decoupled:\n * the caller (app/harness) owns panorama generation; view3d never imports it.\n * Called at PICK time to pre-render, so flyToSeat has zero wait on landing.\n */\n getSeatView?: (seatId: string) => SeatView | Promise<SeatView>;\n /** Buyer-facing action for entering the supplied seat view. The caller knows\n * whether that source is live geometry or an authored panorama. */\n seatViewActionLabel?: (seatId: string) => string;\n /** Land at the modeled seated-eye pose instead of the elevated inspection\n * anchor. Useful when live geometry is the primary buyer view. */\n arriveAtSeatEye?: boolean;\n /**\n * Decoupled analytics sink. Emits the venue-view journey: `3d_opened`,\n * `3d_orbit_engaged` (first user gesture), `3d_seat_picked`,\n * `3d_cinematic_played`/`_skipped`/`_cancelled`, `3d_panorama_opened`/`_closed`.\n * Every invocation is wrapped in try/catch — a throwing sink never breaks\n * rendering. Absent = no events emitted.\n */\n onAnalytics?: Analytics3DCallback;\n /**\n * Let portrait overview framing crop the venue's far edges within a bounded\n * landscape fit so seats stay discoverable instead of shrinking the whole\n * bowl to fit. Opt-in because designer/editor previews still prefer strict\n * contain framing; validated for the buyer across `/dev/3d` fixtures.\n */\n portraitOverviewCrop?: boolean;\n}\n\nexport interface Venue3DStats extends RenderLoopStats {\n drawCalls: number;\n seatCount: number;\n}\n\nexport interface Venue3DHandle {\n dispose(): void;\n setAvailability(updates: { seatId: string; state: SeatState3D }[]): void;\n setSelection(seatIds: string[]): void;\n /** Fly the camera from the overview into `seatId` and dissolve into its\n * view-from-seat panorama. Resolves at flight end; a drag cancels it, a second\n * call retargets, dispose resolves early. Reduced-motion → a short fade. */\n flyToSeat(seatId: string): Promise<void>;\n /** Cancel a seat flight/panorama and frame the complete venue. */\n focusOverview(): void;\n resize(): void;\n stats(): Venue3DStats;\n loseContextForTest(): void;\n /** Test hook: force (or clear) the reduced-motion path. */\n setReducedMotionForTest(value: boolean | null): void;\n /** The venue's zones (id, label, colour, seat count) in authored order. */\n zones(): SceneZone[];\n /**\n * Frame a zone: the camera moves to sit over that zone looking at what the\n * zone faces. Returns false for an unknown or empty zone.\n *\n * This is the navigation the venue's own structure implies — a buyer picks\n * \"Grand Circle\", not a set of coordinates — and it is what the 2D renderer's\n * farthest LOD rung already offers. Approaching from the zone's focal side\n * means the seats face the camera rather than presenting their backs.\n */\n focusZone(zoneId: string): boolean;\n /** The venue's seated sections (id, name, seat count) in authored order. */\n sections(): SceneSection[];\n /**\n * Frame a section — the middle rung of the venue ladder, and the one every\n * chart has. Zones are optional and a chart may author none or one; sections\n * are what a buyer picks between when \"which part of the venue\" has already\n * been answered. Returns false for an unknown or empty section.\n */\n focusSection(sectionId: string): boolean;\n /** Rows in the venue, optionally narrowed to a section. */\n rows(sectionId?: string): SceneRow[];\n /** Seats in one row, in authored order. */\n seatsInRow(rowId: string): Array<{ id: string; label: string }>;\n /** Frame a row closely enough for its on-demand seat numbers to be legible. */\n focusRow(rowId: string): boolean;\n /** The venue's floors (id, name, seat count) in authored order. */\n floors(): SceneFloor[];\n /**\n * Isolate one floor, or pass null to show the whole venue.\n *\n * Every shipped multi-floor chart puts its floors at the same base height and\n * takes relief from the sections, so all three of an opera house draw at once\n * and the balcony sits over the parterre. Unfocused floors are DIMMED rather\n * than hidden, so the buyer keeps the venue as context while looking at the\n * level they are booking. Returns false for an unknown index.\n */\n focusFloor(index: number | null): boolean;\n}\n\nconst DEG = Math.PI / 180;\nconst TAP_SLOP = 6;\nconst TAP_MS = 500;\n\nexport function mountVenue3D(\n container: HTMLElement,\n input: Venue3DInput,\n opts: Venue3DOptions = {},\n): Venue3DHandle {\n const buildStartedAt = performance.now();\n const model: SceneModel = input.prepared?.model ?? buildSceneModel(input);\n const analytics = new Analytics3D(opts.onAnalytics);\n\n const seatIdByIndex: string[] = new Array(model.seats.count);\n for (const [id, idx] of model.seats.idToIndex) seatIdByIndex[idx] = id;\n const expandedSeatById = new Map(input.seats.map((seat) => [seat.id, seat]));\n const seatIdsByRow = new Map<string, string[]>();\n for (const seat of input.seats) {\n if (!seat.rowId) continue;\n const row = seatIdsByRow.get(seat.rowId);\n if (row) row.push(seat.id);\n else seatIdsByRow.set(seat.rowId, [seat.id]);\n }\n\n // Seat → owning section (for the 3d_seat_picked event); resolved from the\n // expanded seats that already carry sectionId.\n const sectionIdBySeatId = new Map<string, string | undefined>();\n for (const s of input.seats) sectionIdBySeatId.set(s.id, s.sectionId);\n\n // Whether the chart carries any real 3D relief (authored heights/rake or\n // elevated floors) vs. degrading to flat slabs — reported with 3d_opened.\n const hasHeights = ((): boolean => {\n if (input.doc.floors?.some((f) => (f.baseHeightM ?? 0) > 0)) return true;\n const objs = input.doc.floors?.flatMap((f) => f.objects) ?? input.doc.objects;\n return objs.some((o) => o.type === 'section'\n && (((o as { height?: number }).height ?? 0) > 0 || ((o as { rake?: number }).rake ?? 0) > 0));\n })();\n\n let gpu: GpuScene | null = null;\n let pick: PickPipeline | null = null;\n let contextLost = false;\n let frozen = false; // GL render paused while the panorama is up\n let disposed = false;\n let selection = new Map<string, number>();\n let panorama: PanoramaHandle | null = null;\n let panoramaLoadAbort: AbortController | null = null;\n let panoramaPriorZIndex: string | null = null;\n const prefetch = new Map<string, Promise<SeatView>>();\n let flightGen = 0;\n let reducedForced: boolean | null = null;\n /** Focused floor, or -1 for the whole venue. Survives a context restore. */\n let focusedFloor = -1;\n\n const raisePanoramaLayer = (): void => {\n if (panoramaPriorZIndex !== null) return;\n panoramaPriorZIndex = container.style.zIndex;\n // Buyer chrome sits outside this stacking context at z-index 5. A panorama\n // is modal, so raise the complete 3D layer while it owns the interaction.\n container.style.zIndex = '20';\n };\n const restorePanoramaLayer = (): void => {\n if (panoramaPriorZIndex === null) return;\n if (container.style.zIndex === '20') container.style.zIndex = panoramaPriorZIndex;\n panoramaPriorZIndex = null;\n };\n\n const rebuildGpu = (): void => {\n gpu = buildGpuScene(glctx.gl, model);\n pick = new PickPipeline(\n glctx.renderer,\n gpu.seatGeometry,\n gpu.chairGeometry,\n gpu.solidGeometry,\n model.seats.count,\n );\n // Apply the chart's authored theme to everything outside the scene graph:\n // the clear colour (visible for a frame before the background draws, and on\n // any frame the scene does not cover) and the pick pass's restore.\n glctx.setClearColor(model.theme.background.top);\n pick.setRestoreClear(model.theme.background.top);\n // Seat size is authored too (`ChartTheme.seatScale`) — bigger seats for\n // charts with longer labels.\n gpu.seatProgram.uniforms.uSeatRadius.value = SEAT_DOT_RADIUS_M * model.theme.seatScale;\n // A context restore rebuilds the GPU scene, so re-apply the focused floor\n // rather than silently reverting the buyer to the whole venue.\n gpu.seatProgram.uniforms.uFocusFloor.value = focusedFloor;\n gpu.solidProgram.uniforms.uFocusFloor.value = focusedFloor;\n gpu.chairProgram.uniforms.uFocusFloor.value = focusedFloor;\n // A rebuilt scene has an empty chair set; force the next frame to re-gather\n // instead of trusting the last camera position.\n lastGatherX = Infinity;\n };\n\n // --- near field ------------------------------------------------------------\n // The spatial index is constructed here but its grid is built lazily, on the\n // first gather — a session that never leaves the overview never pays for it.\n const nearIndex = new NearFieldIndex(model.seats.iPosition, model.seats.count);\n const nearBuf = new Int32Array(CHAIR_MAX_INSTANCES);\n let lastGatherX = Infinity;\n let lastGatherZ = Infinity;\n // Overview is the legible map rung: category/status dots only. Chair/person\n // detail belongs to section, row and exact-seat inspection. Keeping this as\n // explicit journey state also prevents a close row's gathered mesh surviving\n // a soft camera return and streaking across the overview near plane.\n let nearFieldDetailEnabled = false;\n let cameraDetailVisible = false;\n // Real panoramas render on a 500 m in-scene sphere. While it is mounted the\n // tight venue frustum must include that sphere; generated live-scene views do\n // not need the override.\n let panoramaSphereVisible = false;\n const setNearFieldDetailEnabled = (enabled: boolean): void => {\n nearFieldDetailEnabled = enabled;\n if (!enabled) cameraDetailVisible = false;\n lastGatherX = Infinity;\n lastGatherZ = Infinity;\n if (!enabled && gpu?.nearSeatCount()) gpu.setNearSeats(nearBuf, 0);\n };\n\n /**\n * Re-gather the near set when the camera has moved far enough to invalidate\n * it — not every frame.\n *\n * The cheap early-out is the venue bound: if the camera is further from the\n * whole seat cloud than the gather radius, nothing can qualify and the grid is\n * never even touched. That is the state the overview, the intro ease and every\n * frame of a wide orbit are in, so the common case costs one distance test.\n */\n const updateNearField = (detailVisible: boolean): void => {\n if (!gpu) return;\n if (!detailVisible) {\n if (gpu.nearSeatCount()) gpu.setNearSeats(nearBuf, 0);\n return;\n }\n const cam = orbit.camera.position;\n const moved = Math.hypot(cam.x - lastGatherX, cam.z - lastGatherZ);\n if (moved < CHAIR_REBUILD_M) return;\n lastGatherX = cam.x;\n lastGatherZ = cam.z;\n const outside = Math.hypot(cam.x - model.bounds.center[0], cam.z - model.bounds.center[2])\n - model.bounds.radius;\n if (outside > CHAIR_GATHER_M) {\n if (gpu.nearSeatCount()) gpu.setNearSeats(nearBuf, 0);\n return;\n }\n const n = nearIndex.gather(cam.x, cam.z, CHAIR_GATHER_M, nearBuf);\n if (n === 0 && gpu.nearSeatCount() === 0) return;\n gpu.setNearSeats(nearBuf, n);\n };\n\n const glctx = new GLContext(container, {\n onContextLost: () => {\n if (!disposed) analytics.contextLost();\n contextLost = true;\n loop.stop();\n gpu = null;\n pick = null;\n },\n onContextRestored: () => {\n rebuildGpu();\n contextLost = false;\n if (!disposed) analytics.contextRestored();\n loop.requestRender();\n },\n });\n\n const orbit = new OrbitCamera(\n glctx.gl,\n glctx.canvas,\n () => loop.requestRender(),\n () => analytics.orbitEngaged(), // first real drag/wheel/pinch (not the intro ease)\n );\n // setAspect BEFORE frame so the fit clears the horizontal FOV too (centred with\n // margin on a wide designer canvas rather than parked low-left).\n orbit.setAspect(glctx.aspect);\n // Enter from the stage side (camera behind the stage, every tier facing you).\n // When the focal sits at the centre (in-the-round) there is no stage side —\n // fall back to the fixed architectural angle.\n const stageAzimuth = ((): number | undefined => {\n const dx = model.focalWorld[0] - model.bounds.center[0];\n const dz = model.focalWorld[2] - model.bounds.center[2];\n return Math.hypot(dx, dz) > model.bounds.radius * 0.12 ? Math.atan2(dx, dz) : undefined;\n })();\n const reduceMotionOnMount = typeof window !== 'undefined' && !!window.matchMedia\n && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n orbit.frame(model.bounds, !reduceMotionOnMount, stageAzimuth, opts.portraitOverviewCrop === true);\n\n const cinematic = new Cinematic(orbit.camera);\n\n // Labels are DOM, projected from world anchors — see labels.ts for why. The\n // container must establish a positioning context or the overlay would anchor\n // to the page instead of the canvas.\n const restoreContainerPosition = establishPositioningContext(container);\n const labelOverlay = new LabelOverlay(container, {\n fontFamily: input.doc.theme?.fontFamily,\n ink: input.doc.theme?.textColor,\n });\n labelOverlay.setLabels(model.labels);\n const baseLabels = model.labels;\n // While a buyer is inside a section, taps are deliberately scoped to that\n // section. Nearby tiers remain visible as spatial context, but must not steal\n // a tap through the GPU depth buffer when their projected dots overlap.\n let focusedSectionId: string | null = null;\n const showSectionSeatLabels = (sectionId: string | null): void => {\n focusedSectionId = sectionId;\n labelOverlay.setForcedDense(!!sectionId);\n if (!sectionId) {\n labelOverlay.setLabels(baseLabels);\n return;\n }\n const sectionSeats = input.seats.filter((seat) => seat.sectionId === sectionId);\n const seatIds = new Set(sectionSeats.map((seat) => seat.id));\n const rowIds = new Set(sectionSeats.map((seat) => seat.rowId));\n const selectedIds = new Set(selection.keys());\n const selectedInSection = new Set(sectionSeats.filter((seat) => selectedIds.has(seat.id)).map((seat) => seat.id));\n const focusedBase = baseLabels.filter((label) => (\n label.kind !== 'row' && label.kind !== 'seat'\n ) || (\n label.kind === 'row'\n ? rowIds.has(label.id.slice('row:'.length))\n : seatIds.has(label.id.slice('seat:'.length))\n && (selectedInSection.size === 0 || selectedInSection.has(label.id.slice('seat:'.length)))\n ));\n if (model.seatCount <= 6_000 && selectedInSection.size === 0) {\n labelOverlay.setLabels(focusedBase);\n return;\n }\n const labelSeats = selectedInSection.size\n ? sectionSeats.filter((seat) => selectedInSection.has(seat.id))\n : sectionSeats;\n const generatedIds = new Set(labelSeats.map((seat) => `seat:${seat.id}`));\n const labels = labelSeats.flatMap((seat) => {\n const index = model.seats.idToIndex.get(seat.id);\n if (index === undefined) return [];\n const offset = index * 3;\n return [{\n id: `seat:${seat.id}`,\n kind: 'seat' as const,\n text: seat.displayLabel || seat.label,\n anchor: [\n model.seats.iPosition[offset],\n // Put the active identity on the chair back. A deck-level label can\n // sit a full row below a foreground chair in perspective, while DOM\n // labels for rows behind remain visible through the WebGL geometry.\n model.seats.iPosition[offset + 1] + (selectedIds.has(seat.id) ? 1.05 : 0.55),\n model.seats.iPosition[offset + 2],\n ] as [number, number, number],\n }];\n });\n labelOverlay.setLabels([\n ...focusedBase.filter((label) => !generatedIds.has(label.id)),\n ...labels,\n ]);\n };\n\n rebuildGpu();\n\n const loop = new RenderLoop((/* dt */) => {\n if (contextLost || !gpu || frozen) return false;\n const flying = cinematic.active;\n const moving = flying ? cinematic.update(performance.now()) : orbit.update();\n\n const lod = computeSeatLod(orbit.currentDistance, model.bounds.radius);\n // Cinematic and panorama poses move the OGL camera directly, so clipping\n // belongs in the render loop rather than only in OrbitCamera.frame(). The\n // same projection matrix is then used by display, label projection and pick.\n const clippingBounds = panoramaSphereVisible\n ? { center: [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z] as [number, number, number], radius: 520 }\n : model.renderBounds;\n orbit.updateClipping(clippingBounds);\n // Semantic section/row/seat focus permits detail, but camera scale decides\n // whether it is still legible. A buyer can wheel from a section back to a\n // whole-bowl view without pressing Fit venue; leaving thousands of dots and\n // chairs enabled there caused a history-dependent moiré/scattering state.\n // Hysteresis prevents flicker near the handover.\n const cameraDx = orbit.camera.position.x - model.renderBounds.center[0];\n const cameraDy = orbit.camera.position.y - model.renderBounds.center[1];\n const cameraDz = orbit.camera.position.z - model.renderBounds.center[2];\n const cameraVenueDistance = Math.hypot(cameraDx, cameraDy, cameraDz);\n const detailEnter = model.renderBounds.radius * 1.55;\n const detailExit = model.renderBounds.radius * 1.80;\n if (!nearFieldDetailEnabled) cameraDetailVisible = false;\n else if (cameraDetailVisible) cameraDetailVisible = cameraVenueDistance <= detailExit;\n else cameraDetailVisible = cameraVenueDistance <= detailEnter;\n\n const u = gpu.seatProgram.uniforms;\n u.uSeatScale.value = lod.scale;\n u.uSeatFade.value = lod.fade;\n u.uSeatDetail.value = cameraDetailVisible ? 1 : 0;\n gpu.solidProgram.uniforms.uStructureDetail.value = cameraDetailVisible ? 1 : 0;\n // Relax the dot's pixel floor as the venue recedes — see SeatLod.minPixels\n // for why holding 2.5 px all the way out is what welds distant rows into one\n // mass. The pick pass syncs this same value, so the hit mask never drifts\n // off the drawn dot.\n u.uMinPixels.value = lod.minPixels;\n u.uPixelToWorld.value = (2 * Math.tan((orbit.camera.fov * DEG) / 2)) / Math.max(1, glctx.pixelHeight);\n updateNearField(cameraDetailVisible);\n\n glctx.renderer.render({ scene: gpu.background, clear: true });\n glctx.renderer.render({ scene: gpu.main, camera: orbit.camera, clear: false });\n // After the render, so the camera's matrices are the ones just drawn with —\n // projecting from stale matrices makes labels lag the venue by a frame.\n labelOverlay.update(\n orbit.camera.projectionViewMatrix as unknown as ArrayLike<number>,\n glctx.canvas.clientWidth || 1,\n glctx.canvas.clientHeight || 1,\n orbit.currentDistance,\n model.bounds.radius,\n // The dense label rungs rank by real distance from the eye, not by the\n // orbit radius — at the arrival pose those are wildly different numbers.\n [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z],\n );\n return moving;\n });\n\n const ro = typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => handle.resize())\n : null;\n ro?.observe(container);\n\n const setSelection = (ids: string[]): void => {\n const baseStateIndex = (id: string): number | undefined => {\n const idx = model.seats.idToIndex.get(id);\n return idx === undefined ? undefined : model.seats.iState[idx];\n };\n const { updates, next } = diffSelection(selection, ids, baseStateIndex);\n selection = next;\n labelOverlay.setSelectedSeatIds(selection.keys());\n if (focusedSectionId) showSectionSeatLabels(focusedSectionId);\n if (updates.length) {\n const runs = applySeatStates(model.seats, updates);\n if (gpu) gpu.uploadSeatStateRuns(runs);\n loop.requestRender();\n }\n };\n\n // --- cinematic / panorama ---\n const reducedMotion = (): boolean => {\n if (reducedForced !== null) return reducedForced;\n return typeof window !== 'undefined' && !!window.matchMedia\n && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n };\n\n const PREFETCH_CAP = 8;\n const ensureSeatView = (seatId: string): Promise<SeatView> | null => {\n if (!opts.getSeatView) return null;\n let p = prefetch.get(seatId);\n if (!p) {\n p = Promise.resolve(opts.getSeatView(seatId));\n prefetch.set(seatId, p);\n // A network/decode failure must not poison this seat for the lifetime of\n // the 3D mount. Drop only the promise that actually failed; a newer retry\n // may already have replaced it by the time this rejection settles.\n void p.catch(() => {\n if (prefetch.get(seatId) === p) prefetch.delete(seatId);\n });\n // Bound the cache (LRU-ish): drop the oldest inserted entries past the cap.\n while (prefetch.size > PREFETCH_CAP) {\n const oldest = prefetch.keys().next().value as string | undefined;\n if (oldest === undefined) break;\n prefetch.delete(oldest);\n }\n }\n return p;\n };\n\n const resolvedSeatViewPose = (seatId: string, idx: number): { eye: Vec3Arr; focal: Vec3Arr } => {\n const seat = expandedSeatById.get(seatId);\n const floorIndex = Math.max(0, Math.round(model.seats.iFloor[idx] ?? 0));\n const floor = input.doc.floors?.[floorIndex];\n const focalPoint = seat?.focalPoint ?? floor?.focalPoint ?? input.doc.focalPoint;\n const deck: Vec3Arr = [\n model.seats.iPosition[idx * 3],\n model.seats.iPosition[idx * 3 + 1],\n model.seats.iPosition[idx * 3 + 2],\n ];\n return seatViewPose(deck, focalPoint, floor?.baseHeightM ?? 0, seat?.eyeHeightM);\n };\n\n const placeCameraFinal = (finalPos: Vec3Arr, focal: Vec3Arr, fov = FOV_END): void => {\n orbit.camera.position.set(finalPos[0], finalPos[1], finalPos[2]);\n orbit.camera.lookAt(new Vec3(focal[0], focal[1], focal[2]));\n orbit.camera.fov = fov;\n orbit.camera.updateProjectionMatrix();\n };\n\n // --- arrival chip: the flight HOLDS in the live scene; the painted 360 is\n // an explicit tap away. (Owner call 2026-07-24: the real scene at the seat\n // IS the payoff; the generated panorama undersold it as an auto-landing.)\n /** One narrow test for all of this module's chrome, measured off the CONTAINER\n * rather than the window — the 3D view mounts inside the picker, which itself\n * mounts inside a page column, so the window says nothing useful about how\n * much room these chips actually have. */\n const isNarrow = (): boolean => (container.clientWidth || glctx.canvas.clientWidth) <= 520;\n\n let arriveChip: HTMLDivElement | null = null;\n const layoutArriveChip = (): void => {\n if (!arriveChip) return;\n const narrow = isNarrow();\n Object.assign(arriveChip.style, narrow ? {\n left: '12px', right: '12px', bottom: '70px', transform: 'none', justifyContent: 'center',\n } : {\n left: '50%', right: 'auto', bottom: '18px', transform: 'translateX(-50%)', justifyContent: 'initial',\n });\n };\n const removeArriveChip = (): void => {\n arriveChip?.remove();\n arriveChip = null;\n };\n const showArriveChip = (seatId: string, gen: number, retry = false): HTMLDivElement | null => {\n removeArriveChip();\n if (disposed) return null;\n const controls = document.createElement('div');\n controls.setAttribute('role', 'group');\n Object.assign(controls.style, {\n position: 'absolute',\n display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '6px', zIndex: '4',\n } as Partial<CSSStyleDeclaration>);\n\n const seat = expandedSeatById.get(seatId);\n const section = seat?.sectionId ? model.sections.find((candidate) => candidate.id === seat.sectionId) : undefined;\n const seatLabel = seat?.label ?? seatId;\n controls.setAttribute('aria-label', `Explore views near ${section?.label ? `${section.label}, ` : ''}seat ${seatLabel}`);\n const location = document.createElement('span');\n location.textContent = ['Live 3D seat view', section?.label, seatLabel].filter(Boolean).join(' · ');\n Object.assign(location.style, {\n maxWidth: 'min(320px, calc(100vw - 32px))', padding: '5px 10px', borderRadius: '999px',\n overflow: 'hidden', color: '#d9e3f5', background: 'rgba(12,18,32,0.72)',\n font: '600 11px/1.2 inherit', textOverflow: 'ellipsis', whiteSpace: 'nowrap',\n backdropFilter: 'blur(6px)',\n } as Partial<CSSStyleDeclaration>);\n controls.appendChild(location);\n const actions = document.createElement('div');\n Object.assign(actions.style, {\n display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '6px',\n } as Partial<CSSStyleDeclaration>);\n controls.appendChild(actions);\n const row = seat?.rowId ? seatIdsByRow.get(seat.rowId) : undefined;\n const at = row?.indexOf(seatId) ?? -1;\n const neighbours = at >= 0 ? [row?.[at - 1], row?.[at + 1]] : [undefined, undefined];\n const addButton = (label: string, ariaLabel: string, action: () => void, primary = false): void => {\n const button = document.createElement('button');\n button.type = 'button';\n button.textContent = label;\n button.setAttribute('aria-label', ariaLabel);\n Object.assign(button.style, {\n minHeight: '44px', padding: primary ? '10px 16px' : '10px',\n borderRadius: '999px', background: 'rgba(12,18,32,0.78)', color: '#eef1f8',\n border: '1px solid rgba(150,165,205,0.4)', backdropFilter: 'blur(6px)',\n font: '600 13px/1 inherit', cursor: 'pointer', whiteSpace: 'nowrap',\n } as Partial<CSSStyleDeclaration>);\n if (!primary) button.style.minWidth = '44px';\n if (primary) button.dataset.panoramaTrigger = '';\n button.addEventListener('click', action);\n actions.appendChild(button);\n };\n if (neighbours[0]) {\n const label = expandedSeatById.get(neighbours[0])?.label ?? neighbours[0];\n addButton('‹', `View previous seat ${label}`, () => { void flyToSeat(neighbours[0]!); });\n }\n if (opts.getSeatView) {\n const sourceAction = opts.seatViewActionLabel?.(seatId) ?? 'Open 360° panorama';\n addButton(retry ? `↻ Retry ${sourceAction}` : `◉ ${sourceAction}`, `${retry ? 'Retry' : sourceAction} from seat ${seatLabel}`, () => {\n if (disposed || gen !== flightGen) { removeArriveChip(); return; }\n removeArriveChip();\n void openPanorama(seatId, 400, gen);\n }, true);\n }\n if (neighbours[1]) {\n const label = expandedSeatById.get(neighbours[1])?.label ?? neighbours[1];\n addButton('›', `View next seat ${label}`, () => { void flyToSeat(neighbours[1]!); });\n }\n if (!actions.childElementCount) return null;\n container.appendChild(controls);\n arriveChip = controls;\n layoutArriveChip();\n return controls;\n };\n\n // --- 3D control dock --------------------------------------------------------\n // Match the 2D picker's learned control position: one bottom-right home for\n // camera actions. Desktop keeps explicit Rotate/Move discovery; touch already\n // has one-finger orbit plus two-finger pan/zoom, so phones only need a compact\n // \"fit the venue\" escape and do not sacrifice the scene to redundant buttons.\n const navigationModeChip = document.createElement('div');\n let seatViewLocked = false;\n navigationModeChip.className = 'sl-3d-navigation-controls';\n Object.assign(navigationModeChip.style, {\n position: 'absolute', right: '14px', bottom: '14px', display: 'flex', zIndex: '4',\n flexDirection: 'column', alignItems: 'flex-end', gap: '7px',\n } as Partial<CSSStyleDeclaration>);\n const modeControls = document.createElement('div');\n modeControls.setAttribute('role', 'group');\n modeControls.setAttribute('aria-label', '3D drag mode');\n Object.assign(modeControls.style, {\n display: 'flex', flexDirection: 'column', gap: '6px', alignItems: 'flex-end',\n } as Partial<CSSStyleDeclaration>);\n const utilityControls = document.createElement('div');\n utilityControls.setAttribute('role', 'group');\n utilityControls.setAttribute('aria-label', '3D view controls');\n Object.assign(utilityControls.style, {\n display: 'flex', flexDirection: 'column', gap: '6px', alignItems: 'flex-end',\n } as Partial<CSSStyleDeclaration>);\n const rotateModeButton = document.createElement('button');\n rotateModeButton.type = 'button';\n rotateModeButton.textContent = '↻';\n rotateModeButton.setAttribute('aria-label', 'Drag to rotate the 3D venue');\n rotateModeButton.dataset.tooltip = 'Rotate venue';\n const moveModeButton = document.createElement('button');\n moveModeButton.type = 'button';\n moveModeButton.textContent = '✥';\n moveModeButton.setAttribute('aria-label', 'Drag to move the 3D venue left, right, up or down');\n moveModeButton.dataset.tooltip = 'Move venue';\n const zoomOutButton = document.createElement('button');\n zoomOutButton.type = 'button';\n zoomOutButton.textContent = '−';\n zoomOutButton.setAttribute('aria-label', 'Zoom out of the 3D venue');\n zoomOutButton.dataset.tooltip = 'Zoom out';\n const zoomInButton = document.createElement('button');\n zoomInButton.type = 'button';\n zoomInButton.textContent = '+';\n zoomInButton.setAttribute('aria-label', 'Zoom into the 3D venue');\n zoomInButton.dataset.tooltip = 'Zoom in';\n for (const button of [rotateModeButton, moveModeButton, zoomOutButton, zoomInButton]) {\n button.className = 'sl-3d-icon-control';\n Object.assign(button.style, {\n width: '44px', minWidth: '44px', minHeight: '44px', padding: '9px',\n border: '1px solid rgba(150,165,205,0.35)', borderRadius: '999px',\n color: '#c9d4ea', background: 'rgba(12,18,32,0.82)', font: '600 17px/1 inherit',\n backdropFilter: 'blur(6px)',\n cursor: 'pointer', whiteSpace: 'nowrap',\n } as Partial<CSSStyleDeclaration>);\n }\n modeControls.append(rotateModeButton, moveModeButton);\n utilityControls.append(zoomInButton, zoomOutButton);\n const setNavigationMode = (mode: 'orbit' | 'pan'): void => {\n orbit.setPrimaryDragMode(mode);\n const rotateActive = mode === 'orbit';\n rotateModeButton.setAttribute('aria-pressed', String(rotateActive));\n moveModeButton.setAttribute('aria-pressed', String(!rotateActive));\n rotateModeButton.style.background = rotateActive ? 'rgba(96,110,150,0.68)' : 'rgba(12,18,32,0.82)';\n moveModeButton.style.background = rotateActive ? 'rgba(12,18,32,0.82)' : 'rgba(96,110,150,0.68)';\n navigationModeChip.title = rotateActive\n ? 'Drag to rotate · Shift-drag to move'\n : 'Drag to move · Shift-drag to rotate';\n };\n rotateModeButton.addEventListener('click', () => setNavigationMode('orbit'));\n moveModeButton.addEventListener('click', () => setNavigationMode('pan'));\n zoomOutButton.addEventListener('click', () => orbit.zoomBy(1.22));\n zoomInButton.addEventListener('click', () => orbit.zoomBy(0.82));\n navigationModeChip.append(modeControls, utilityControls);\n setNavigationMode('orbit');\n container.appendChild(navigationModeChip);\n\n /* THE GESTURE IS THE CONTROL ON TOUCH.\n Desktop exposes Rotate/Move and +/− because alternate mouse gestures are\n not obvious. On a phone those controls only restate native one-finger orbit\n and two-finger pan/zoom, so the dock collapses to the single recovery action\n buyers still need: fit the whole venue back into view. */\n const layoutNavigationChip = (): void => {\n const narrow = isNarrow();\n navigationModeChip.style.display = seatViewLocked ? 'none' : 'flex';\n navigationModeChip.style.right = narrow ? '12px' : '14px';\n // Clear the standard bottom-right privacy trigger on phone-sized embeds.\n navigationModeChip.style.bottom = narrow\n ? 'calc(env(safe-area-inset-bottom, 0px) + 76px)'\n : '14px';\n modeControls.style.display = narrow ? 'none' : 'flex';\n zoomInButton.style.display = narrow ? 'none' : '';\n zoomOutButton.style.display = narrow ? 'none' : '';\n };\n layoutNavigationChip();\n const setSeatViewLocked = (locked: boolean): void => {\n seatViewLocked = locked;\n orbit.setInteractionEnabled(!locked);\n layoutNavigationChip();\n };\n\n // --- fit venue: the 3D equivalent of the 2D map's \"Fit to screen\". It exits\n // section/seat/panorama context and restores the whole-venue composition.\n const overviewChip = document.createElement('button');\n overviewChip.className = 'sl-3d-overview-control sl-3d-icon-control';\n overviewChip.type = 'button';\n overviewChip.textContent = '⌂';\n overviewChip.setAttribute('aria-label', 'Fit the whole venue in view');\n overviewChip.dataset.tooltip = 'Fit venue';\n Object.assign(overviewChip.style, {\n width: '44px', minWidth: '44px', minHeight: '44px', padding: '9px', borderRadius: '999px',\n background: 'rgba(12,18,32,0.72)', color: '#c9d4ea',\n border: '1px solid rgba(150,165,205,0.35)', backdropFilter: 'blur(6px)',\n font: '600 11px/1 inherit', cursor: 'pointer', whiteSpace: 'nowrap',\n } as Partial<CSSStyleDeclaration>);\n const layoutOverviewChip = (): void => {\n overviewChip.textContent = '⌂';\n };\n layoutOverviewChip();\n const focusOverview = (): void => {\n // The panorama owns the screen whenever it is up. Guarding on `frozen`\n // alone was equivalent only while every panorama froze the loop; the\n // in-scene sphere deliberately does not, so ask about the panorama itself.\n if (disposed) return;\n // Notify the host before any camera/panorama cleanup. A rendering exception\n // must not leave semantic buyer navigation stuck on the previous seat.\n opts.onViewTargetChange?.(null);\n opts.onSectionFocusChange?.(null);\n if (panorama) {\n panorama.dispose();\n panorama = null;\n analytics.panoramaClosed();\n }\n panoramaSphereVisible = false;\n panoramaLoadAbort?.abort();\n panoramaLoadAbort = null;\n restorePanoramaLayer();\n overviewChip.style.display = '';\n labelOverlay.setVisible(true);\n frozen = false;\n setNearFieldDetailEnabled(false);\n setSeatViewLocked(false);\n cancelFlight();\n removeArriveChip();\n setNavigationMode('orbit');\n if (reducedMotion()) orbit.frame(model.bounds, false, stageAzimuth, opts.portraitOverviewCrop === true);\n else orbit.frameSoft(model.bounds, stageAzimuth, opts.portraitOverviewCrop === true);\n showSectionSeatLabels(null);\n loop.requestRender();\n };\n overviewChip.addEventListener('click', focusOverview);\n utilityControls.appendChild(overviewChip);\n\n const openPanorama = async (seatId: string, fadeMs: number, gen: number): Promise<void> => {\n panoramaLoadAbort?.abort();\n const loadAbort = new AbortController();\n panoramaLoadAbort = loadAbort;\n const seatIndex = model.seats.idToIndex.get(seatId);\n if (seatIndex === undefined) { orbit.syncFromCamera(); return; }\n const seatLabel = expandedSeatById.get(seatId)?.label ?? seatId;\n const seatPose = resolvedSeatViewPose(seatId, seatIndex);\n const viewPromise = ensureSeatView(seatId);\n if (!viewPromise) {\n loadAbort.abort();\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n orbit.syncFromCamera();\n return;\n } // no panorama source\n frozen = true;\n loop.stop(); // freeze the GL at the seat pose; panorama fades in over it\n let view: SeatView;\n try {\n view = await viewPromise;\n } catch {\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n analytics.panoramaFailed(seatId, 'source');\n // Only unfreeze if we still own the flight — a retarget during the await\n // has already reset `frozen` and taken over the loop.\n if (!disposed && gen === flightGen) {\n frozen = false;\n orbit.resumeAfterFlight(seatPose.focal);\n loop.requestRender();\n const controls = showArriveChip(seatId, gen, true);\n requestAnimationFrame(() => controls?.querySelector<HTMLButtonElement>('[data-panorama-trigger]')?.focus());\n }\n return;\n }\n // Superseded during the await (retarget/cancel) or disposed: bail WITHOUT\n // touching frozen/loop — the newer flight owns the freeze state now, and\n // mounting this stale seat's panorama would be wrong.\n if (disposed || gen !== flightGen) {\n loadAbort.abort();\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n return;\n }\n removeArriveChip();\n const onClose = (): void => {\n loadAbort.abort();\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n panorama = null;\n panoramaSphereVisible = false;\n restorePanoramaLayer();\n overviewChip.style.display = '';\n labelOverlay.setVisible(!opts.arriveAtSeatEye);\n frozen = false;\n analytics.panoramaClosed();\n orbit.resumeAfterFlight(seatPose.focal);\n loop.requestRender();\n // Back in the live scene at the seat — offer the 360 again.\n const controls = showArriveChip(seatId, flightGen);\n requestAnimationFrame(() => controls?.querySelector<HTMLButtonElement>('[data-panorama-trigger]')?.focus());\n };\n // Prefer the in-scene sphere: it keeps the loop running, so the arrival is a\n // cross-fade between two views of the same place from the same camera\n // rather than a bitmap dropped over a frozen frame. It needs the image to\n // decode and a GL context that still exists, so it can decline — and the\n // DOM viewer stays as the fallback because a 360 that fails to mount must\n // never leave the buyer worse off than no 360 at all.\n // A GENERATED panorama is a 2048×1024 picture of the very scene already on\n // screen. Through a 106° window on a 3072-device-pixel viewport that is a\n // 5× magnification of ~600 source pixels, and it reads soft next to the\n // arrival frame the buyer was just looking at. So it is not drawn: the\n // camera looks around the real geometry instead, which is sharp at any\n // field of view and gets better every time the scene does. Real photographs\n // still go on the sphere, where their 5760–8192 px capture has detail worth\n // showing.\n const sceneMode = view.generated === true;\n const disclosure = seatViewDisclosure(view);\n raisePanoramaLayer();\n panoramaSphereVisible = !sceneMode;\n overviewChip.style.display = 'none';\n const effectiveFadeMs = reducedMotion() ? 0 : fadeMs;\n const spherical = gpu && !contextLost\n ? await mountPanoramaSphere(container, sceneMode ? null : view, {\n gl: glctx.gl,\n scene: gpu.main,\n camera: orbit.camera,\n requestRender: () => loop.requestRender(),\n cameraOriginWorld: sceneMode ? seatPose.eye : undefined,\n focalWorld: seatPose.focal,\n }, {\n fadeMs: effectiveFadeMs,\n seatLabel,\n disclosure,\n disclosurePrefix: sceneMode ? 'Live seat-eye view' : '360° panorama',\n onClose,\n signal: loadAbort.signal,\n })\n : null;\n // Superseded while the image was decoding — the newer flight owns the state.\n if (disposed || gen !== flightGen) {\n loadAbort.abort();\n panoramaSphereVisible = false;\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n spherical?.dispose();\n overviewChip.style.display = '';\n restorePanoramaLayer();\n return;\n }\n if (spherical) {\n // The venue keeps rendering behind the sphere, so the freeze that the DOM\n // path depends on must be lifted or the cross-fade has nothing to fade\n // from. The sphere itself pins the camera.\n //\n // Sync orbit to where the flight actually parked the camera FIRST. Its\n // damped pose is still whatever it was before the flight, so the next\n // `orbit.update()` would read that as movement, call `applyPosition()`\n // and yank the camera off the seat mid-fade. Syncing equalises pose and\n // target so `update()` reports \"not moving\" and leaves the camera alone —\n // which is what makes it safe to run the loop at all here.\n orbit.syncFromCamera();\n // Labels are DOM and the sphere is GL, so every row and seat label would\n // otherwise float on top of the panorama.\n labelOverlay.setVisible(false);\n frozen = false;\n loop.requestRender();\n panorama = spherical;\n } else if (sceneMode) {\n // Live geometry has no bitmap fallback. If the GL context disappeared\n // between the explicit action and mount, return to the live seat controls\n // rather than handing a sentinel URL to the DOM image viewer or silently\n // substituting unrelated media.\n restorePanoramaLayer();\n overviewChip.style.display = '';\n frozen = false;\n orbit.resumeAfterFlight(seatPose.focal);\n loop.requestRender();\n panorama = null;\n panoramaSphereVisible = false;\n analytics.panoramaFailed(seatId, 'mount');\n const controls = showArriveChip(seatId, flightGen, true);\n requestAnimationFrame(() => controls?.querySelector<HTMLButtonElement>('[data-panorama-trigger]')?.focus());\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n return;\n } else {\n // The DOM fallback does not use the GL sphere; return the venue renderer\n // to its tight render bound while the frozen overlay owns presentation.\n panoramaSphereVisible = false;\n analytics.panoramaFallback(seatId);\n panorama = mountPanorama(container, view, {\n fadeMs: effectiveFadeMs, seatLabel, disclosure, onClose, signal: loadAbort.signal,\n });\n }\n analytics.panoramaOpened();\n };\n\n const cancelFlight = (): void => {\n flightGen++; // supersede any pending .then(openPanorama)\n panoramaLoadAbort?.abort();\n panoramaLoadAbort = null;\n if (cinematic.active) {\n cinematic.cancel();\n orbit.resumeAfterFlight(model.focalWorld);\n }\n };\n\n const flyToSeat = (seatId: string): Promise<void> => {\n if (disposed || !gpu) return Promise.resolve();\n const idx = model.seats.idToIndex.get(seatId);\n if (idx === undefined) return Promise.resolve();\n setNearFieldDetailEnabled(true);\n opts.onViewTargetChange?.(seatId);\n // Reset the freeze unconditionally: a previous flight may have set frozen=true\n // inside openPanorama's pre-await window without a panorama ever mounting.\n if (panorama) {\n panorama.dispose();\n panorama = null;\n panoramaSphereVisible = false;\n overviewChip.style.display = '';\n restorePanoramaLayer();\n }\n panoramaLoadAbort?.abort();\n panoramaLoadAbort = null;\n frozen = false;\n setSeatViewLocked(false);\n\n const gen = ++flightGen;\n removeArriveChip();\n const { eye: seatEye, focal } = resolvedSeatViewPose(seatId, idx);\n const start: Vec3Arr = [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z];\n const flight = buildWaypoints(start, seatEye, focal, model.bounds.center, model.bounds.radius);\n // The inspection anchor sits behind the selected seat and can end up under\n // the next tier in a deep covered bowl. Live seat-eye experiences land at\n // the truthful modeled origin; legacy embeds keep the inspection anchor.\n const finalPos = opts.arriveAtSeatEye ? seatEye : flight.finalPos;\n const arrivalFov = opts.arriveAtSeatEye\n ? (container.clientWidth / Math.max(1, container.clientHeight) < 0.70 ? 90 : 65)\n : FOV_END;\n const waypoints: Vec3Arr[] = [flight.waypoints[0], flight.waypoints[1], finalPos];\n labelOverlay.setVisible(false);\n\n if (reducedMotion()) {\n // a11y: no flight — snap straight to the seat pose in the live scene.\n placeCameraFinal(finalPos, focal, arrivalFov);\n loop.requestRender();\n analytics.cinematicSkipped();\n if (!disposed) orbit.syncFromCamera();\n setSeatViewLocked(opts.arriveAtSeatEye === true);\n showArriveChip(seatId, gen);\n return Promise.resolve();\n }\n\n const startQuat = new Quat().copy(orbit.camera.quaternion);\n const endQuat = lookAtQuat(orbit.camera, finalPos, focal);\n loop.requestRender();\n return cinematic.start(waypoints, startQuat, endQuat, FLIGHT_DURATION_MS, arrivalFov).then(() => {\n if (disposed || gen !== flightGen) return; // disposed or superseded (retarget/cancel)\n analytics.cinematicPlayed(FLIGHT_DURATION_MS);\n // Arrive and HOLD in the live 3D scene — sitting in the crowd, looking at\n // the show. Exact seat-eye callers are pinned here; their explicit\n // look-around action rotates at this origin without handing the buyer the\n // venue-orbit controller. The real scene is the payoff moment.\n orbit.resumeAfterFlight(focal);\n setSeatViewLocked(opts.arriveAtSeatEye === true);\n loop.requestRender();\n showArriveChip(seatId, gen);\n });\n };\n\n /** Enter the stand itself. This is intentionally separate from picking a\n * ticket: overview tap → section, focused-section tap → seat. */\n const focusSectionCamera = (sectionId: string): boolean => {\n const sec = model.sections.find((candidate) => candidate.id === sectionId);\n if (!sec || sec.seatCount === 0) return false;\n setNearFieldDetailEnabled(true);\n setSeatViewLocked(false);\n cinematic.cancel();\n removeArriveChip();\n setNavigationMode('pan');\n showSectionSeatLabels(sectionId);\n const dx = sec.focalWorld[0] - sec.center[0];\n const dz = sec.focalWorld[2] - sec.center[2];\n const azimuth = Math.hypot(dx, dz) > sec.radius * 0.12 ? Math.atan2(dx, dz) : undefined;\n // Deliberately crop the section edges: this is an ENTER-section action,\n // not a second overview. Seats become practical tap targets and the row /\n // sampled seat numbers are readable.\n orbit.frame({ center: sec.center, radius: Math.max(5, sec.radius * 0.5) }, false, azimuth);\n opts.onSectionFocusChange?.(sectionId);\n loop.requestRender();\n return true;\n };\n\n // --- Tap → pick / flight-cancel ---\n let downX = 0, downY = 0, downT = 0, downId = -1, moved = false, suppressTap = false;\n const onDown = (e: PointerEvent): void => {\n if (seatViewLocked) return;\n if (downId !== -1) return;\n downId = e.pointerId; downX = e.clientX; downY = e.clientY; downT = performance.now(); moved = false;\n // A press during a flight cancels it (damped stop) instead of picking.\n suppressTap = cinematic.active;\n if (cinematic.active) {\n analytics.cinematicCancelled();\n cancelFlight();\n opts.onViewTargetChange?.(null);\n }\n };\n const onMove = (e: PointerEvent): void => {\n if (e.pointerId !== downId) return;\n if (Math.hypot(e.clientX - downX, e.clientY - downY) > TAP_SLOP) moved = true;\n };\n /**\n * Pick the visible seat target nearest a tap while drilled into a section.\n *\n * The overview uses the O(1) GPU colour pass. In a close raked section the\n * chair/deck depth geometry can win that pass even though the buyer clearly\n * tapped the large seat dot. Projecting just the focused section's seats is a\n * bounded, tap-only fallback (normally a few hundred points) and, critically,\n * prevents the neighbouring tier retained for context from stealing the tap.\n */\n const pickNearestProjectedSeat = (\n clientX: number,\n clientY: number,\n sectionId: string | null,\n maxDistance: number,\n bookableOnly: boolean,\n ): number => {\n const rect = glctx.canvas.getBoundingClientRect();\n const width = glctx.canvas.clientWidth || rect.width || 1;\n const height = glctx.canvas.clientHeight || rect.height || 1;\n const tapX = clientX - rect.left;\n const tapY = clientY - rect.top;\n // The caller chooses a section/overview tolerance. Distance wins; depth\n // only breaks near-ties.\n const maxDistanceSq = maxDistance * maxDistance;\n let bestIndex = -1;\n let bestDistanceSq = maxDistanceSq;\n let bestDepth = Infinity;\n for (const seat of input.seats) {\n if (sectionId && seat.sectionId !== sectionId) continue;\n const index = model.seats.idToIndex.get(seat.id);\n if (index === undefined) continue;\n // The focused view is a buying surface, not an inventory inspector. Snap\n // the finger target to the nearest bookable (or already-selected) seat;\n // held, sold and filtered seats stay visible but cannot produce a dead\n // optimistic pick that the authoritative picker immediately rejects.\n if (bookableOnly) {\n const state = Math.round(model.seats.iState[index] ?? 0);\n if (state !== 0 && state !== 3) continue;\n }\n const offset = index * 3;\n const screen = projectToScreen(\n orbit.camera.projectionViewMatrix as unknown as ArrayLike<number>,\n [\n model.seats.iPosition[offset],\n // Match the visible chair/number rather than the deck-level instance\n // origin. At strong zoom the vertical difference is dozens of pixels\n // and a perfectly reasonable click on the chair otherwise misses.\n model.seats.iPosition[offset + 1] + 0.55,\n model.seats.iPosition[offset + 2],\n ],\n width,\n height,\n );\n if (!screen.visible) continue;\n const dx = screen.x - tapX;\n const dy = screen.y - tapY;\n const distanceSq = dx * dx + dy * dy;\n if (\n distanceSq < bestDistanceSq\n || (Math.abs(distanceSq - bestDistanceSq) < 1 && screen.depth < bestDepth)\n ) {\n bestIndex = index;\n bestDistanceSq = distanceSq;\n bestDepth = screen.depth;\n }\n }\n return bestIndex;\n };\n const pickVisibleGeometry = (clientX: number, clientY: number): number => {\n if (!gpu || !pick) return -1;\n pick.syncFromSeatProgram(gpu.seatProgram);\n const rect = glctx.canvas.getBoundingClientRect();\n const dpr = glctx.renderer.dpr;\n const { x, y } = pickPixelCoords(\n clientX,\n clientY,\n rect,\n dpr,\n glctx.gl.drawingBufferWidth,\n glctx.gl.drawingBufferHeight,\n );\n const radius = Math.max(2, Math.round(8 * dpr));\n return pick.pick(orbit.camera, x, y, radius);\n };\n const onUp = (e: PointerEvent): void => {\n if (e.pointerId !== downId) return;\n const isTap = !moved && performance.now() - downT < TAP_MS;\n downId = -1;\n if (suppressTap) { suppressTap = false; return; }\n if (!isTap || !gpu || !pick) return;\n // Exact visible geometry first. The pick pass contains the same chairs and\n // people as the buyer view, with global seat IDs, so a foreground occupant\n // cannot be pierced by a projected anchor from a row behind.\n let idx = pickVisibleGeometry(e.clientX, e.clientY);\n if (focusedSectionId && idx >= 0) {\n const visibleSeatId = seatIdByIndex[idx];\n const visibleSectionId = visibleSeatId ? sectionIdBySeatId.get(visibleSeatId) : undefined;\n if (visibleSectionId && visibleSectionId !== focusedSectionId && focusSectionCamera(visibleSectionId)) return;\n }\n // Enlarged screen-space tolerance is only a fallback for the gaps between\n // exact geometry, preserving a forgiving touch target on mobile.\n if (focusedSectionId && idx < 0) {\n idx = pickNearestProjectedSeat(e.clientX, e.clientY, focusedSectionId, 44, false);\n }\n if (focusedSectionId && idx < 0) {\n // A buyer may wheel back out instead of pressing Overview. In that case\n // the first section is still the active seat-picking scope, but other\n // stands are visible and should remain valid navigation targets. If the\n // tap misses a bookable seat in the active section, transfer focus to the\n // visible stand under the pointer rather than silently retaining the old\n // section forever.\n // This is a stand-navigation target, not an exact ticket target: allow a\n // little space around dots so the tier/deck itself still feels clickable.\n const sectionIndex = pickNearestProjectedSeat(e.clientX, e.clientY, null, 72, false);\n const sectionSeatId = sectionIndex >= 0 ? seatIdByIndex[sectionIndex] : undefined;\n const nextSectionId = sectionSeatId ? sectionIdBySeatId.get(sectionSeatId) : undefined;\n if (nextSectionId && nextSectionId !== focusedSectionId && focusSectionCamera(nextSectionId)) return;\n }\n if (!focusedSectionId) {\n // Labels and the gaps between overview dots are still visibly part of a\n // stand. If the exact GPU mask misses, use a larger section-level target.\n if (idx < 0) idx = pickNearestProjectedSeat(e.clientX, e.clientY, null, 42, false);\n const overviewSeatId = idx >= 0 ? seatIdByIndex[idx] : undefined;\n const sectionId = overviewSeatId ? sectionIdBySeatId.get(overviewSeatId) : undefined;\n if (sectionId && focusSectionCamera(sectionId)) return;\n }\n if (idx < 0 || idx >= seatIdByIndex.length) {\n if (selection.size) {\n setSelection([]);\n opts.onViewTargetChange?.(null);\n }\n return;\n }\n const seatId = seatIdByIndex[idx];\n const seatState = SEAT_STATES[Math.round(model.seats.iState[idx] ?? 0)] ?? 'available';\n if (seatState !== 'available' && seatState !== 'selected') {\n // Held, sold and filter-dimmed inventory remains visible because it is\n // useful venue context. It must also be explainable when tapped, but it\n // must never receive the optimistic selection highlight or enter the\n // host's booking path.\n opts.onSeatInspect?.(seatId, seatState);\n return;\n }\n if (selection.has(seatId) && selection.size === 1) {\n setSelection([]);\n opts.onViewTargetChange?.(null);\n return;\n }\n setSelection([seatId]);\n ensureSeatView(seatId); // pre-render the panorama the moment the seat is picked\n analytics.seatPicked(seatId, sectionIdBySeatId.get(seatId));\n opts.onSeatPick?.(seatId);\n };\n glctx.canvas.addEventListener('pointerdown', onDown);\n glctx.canvas.addEventListener('pointermove', onMove);\n glctx.canvas.addEventListener('pointerup', onUp);\n glctx.canvas.addEventListener('pointercancel', onUp);\n\n loop.requestRender();\n\n const handle: Venue3DHandle = {\n setAvailability(updates) {\n const passthrough = mergeAvailabilityIntoSelection(selection, updates);\n const runs = applySeatStates(model.seats, passthrough);\n if (runs.length && gpu) gpu.uploadSeatStateRuns(runs);\n loop.requestRender();\n },\n setSelection,\n flyToSeat,\n focusOverview,\n resize() {\n const { width, height } = glctx.resize();\n orbit.setAspect(width / Math.max(1, height));\n layoutArriveChip();\n layoutNavigationChip();\n layoutOverviewChip();\n loop.requestRender();\n },\n stats() {\n return {\n ...loop.stats(),\n drawCalls: gpu ? gpu.drawCalls : 0,\n seatCount: model.seatCount,\n };\n },\n loseContextForTest() {\n glctx.simulateContextLossCycle();\n },\n floors(): SceneFloor[] {\n return model.floors;\n },\n focusFloor(index: number | null): boolean {\n if (index !== null && !model.floors.some((f) => f.index === index)) return false;\n const value = index ?? -1;\n // Floor/zone are overview rungs. Their result must not depend on whether\n // the buyer arrived from a detailed section (chairs already gathered) or\n // from venue overview (no detail gathered).\n setNearFieldDetailEnabled(false);\n setSeatViewLocked(false);\n if (gpu) {\n gpu.seatProgram.uniforms.uFocusFloor.value = value;\n gpu.solidProgram.uniforms.uFocusFloor.value = value;\n gpu.chairProgram.uniforms.uFocusFloor.value = value;\n }\n focusedFloor = value;\n setNavigationMode('orbit');\n if (index !== null) {\n const f = model.floors[index];\n if (f.seatCount > 0) {\n cinematic.cancel();\n orbit.frame({ center: f.center, radius: f.radius * 1.25 });\n }\n }\n loop.requestRender();\n return true;\n },\n zones(): SceneZone[] {\n return model.zones;\n },\n focusZone(zoneId: string): boolean {\n const zone = model.zones.find((z) => z.id === zoneId);\n if (!zone || zone.seatCount === 0) return false;\n setNearFieldDetailEnabled(false);\n setSeatViewLocked(false);\n cinematic.cancel();\n setNavigationMode('orbit');\n showSectionSeatLabels(null);\n opts.onSectionFocusChange?.(null);\n // Approach from the side the zone faces, so its seats present their fronts\n // rather than their backs — the same reasoning as the venue's intro shot.\n const dx = zone.focalWorld[0] - zone.center[0];\n const dz = zone.focalWorld[2] - zone.center[2];\n const azimuth = Math.hypot(dx, dz) > zone.radius * 0.12 ? Math.atan2(dx, dz) : undefined;\n // Padded past the zone's own radius: framing exactly to its edge reads as\n // cropped, and a buyer needs the neighbouring geometry to know where in the\n // venue they have landed.\n orbit.frame({ center: zone.center, radius: zone.radius * 1.25 }, false, azimuth);\n loop.requestRender();\n return true;\n },\n sections(): SceneSection[] {\n return model.sections;\n },\n focusSection(sectionId: string): boolean {\n return focusSectionCamera(sectionId);\n },\n rows(sectionId?: string): SceneRow[] {\n return sectionId ? model.rows.filter((row) => row.sectionId === sectionId) : model.rows;\n },\n seatsInRow(rowId: string): Array<{ id: string; label: string }> {\n return input.seats\n .filter((seat) => seat.rowId === rowId)\n .map((seat) => ({ id: seat.id, label: seat.displayLabel || seat.label }));\n },\n focusRow(rowId: string): boolean {\n const row = model.rows.find((candidate) => candidate.id === rowId);\n if (!row || row.seatCount === 0) return false;\n setNearFieldDetailEnabled(true);\n setSeatViewLocked(false);\n cinematic.cancel();\n setNavigationMode('pan');\n if (row.sectionId) {\n showSectionSeatLabels(row.sectionId);\n opts.onSectionFocusChange?.(row.sectionId);\n }\n orbit.frame({ center: row.center, radius: Math.max(2.5, row.radius * 0.65) });\n loop.requestRender();\n return true;\n },\n setReducedMotionForTest(value) {\n reducedForced = value;\n },\n dispose() {\n disposed = true;\n removeArriveChip();\n overviewChip.remove();\n navigationModeChip.remove();\n cancelFlight();\n loop.stop();\n labelOverlay.dispose();\n ro?.disconnect();\n if (panorama) { panorama.dispose(); panorama = null; }\n restorePanoramaLayer();\n restoreContainerPosition();\n glctx.canvas.removeEventListener('pointerdown', onDown);\n glctx.canvas.removeEventListener('pointermove', onMove);\n glctx.canvas.removeEventListener('pointerup', onUp);\n glctx.canvas.removeEventListener('pointercancel', onUp);\n orbit.dispose();\n if (pick) pick.dispose();\n if (gpu) gpu.dispose();\n gpu = null;\n pick = null;\n glctx.dispose();\n },\n };\n\n analytics.opened(\n model.seatCount,\n hasHeights,\n input.prepared?.buildMs ?? performance.now() - buildStartedAt,\n // Known synchronously at mount: either the caller handed us a scene\n // prepared by `prepareVenue3D` (which reports worker vs. main-thread\n // fallback) or we compiled it inline here. Never delays the event.\n input.prepared?.source ?? 'inline',\n );\n return handle;\n}\n","/**\n * OGL renderer + canvas lifecycle. Owns the single WebGL2 context (reused across\n * open/close so an embed never exhausts the browser's ~16-context cap), DPR\n * capping, resize, and WebGL context-loss survival.\n *\n * Context loss is handled by preventing the default (so the browser will restore)\n * and delegating rebuild to the caller: the JS-side SceneModel is the source of\n * truth, so `onContextRestored` re-uploads all GPU resources from it.\n */\n\nimport { Renderer } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\nimport { BACKGROUND } from '../palette';\n\nexport interface GLContextOptions {\n onContextLost: () => void;\n onContextRestored: () => void;\n}\n\n/** DPR ceiling: 2.0, or 1.5 on low-memory devices (fragment cost is DPR²). */\nfunction computeDpr(): number {\n const raw = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n const mem = (navigator as unknown as { deviceMemory?: number }).deviceMemory;\n const cap = typeof mem === 'number' && mem <= 4 ? 1.5 : 2.0;\n return Math.min(raw, cap);\n}\n\nexport class GLContext {\n readonly renderer: Renderer;\n readonly gl: OGLRenderingContext;\n readonly canvas: HTMLCanvasElement;\n private container: HTMLElement;\n /** Current clear colour, so a context restore repaints the themed background. */\n private clearRgb: [number, number, number] = [0, 0, 0];\n private lostHandler: (e: Event) => void;\n private restoredHandler: () => void;\n\n /**\n * Repaint the clear colour from the chart's theme.\n *\n * The clear shows for one frame before the background triangle draws, and on\n * any frame the scene does not cover — so leaving it at the library default\n * flashes SeatLayer grey into a white-labelled venue.\n */\n setClearColor(rgb: readonly number[]): void {\n this.clearRgb = [rgb[0], rgb[1], rgb[2]];\n this.gl.clearColor(rgb[0], rgb[1], rgb[2], 1);\n }\n\n /** The clear colour currently set (the pick pass restores through this). */\n get clearColor(): readonly number[] {\n return this.clearRgb;\n }\n\n constructor(container: HTMLElement, opts: GLContextOptions) {\n this.container = container;\n this.canvas = document.createElement('canvas');\n this.canvas.style.display = 'block';\n this.canvas.style.width = '100%';\n this.canvas.style.height = '100%';\n this.canvas.style.touchAction = 'none';\n this.canvas.tabIndex = 0;\n this.canvas.setAttribute('role', 'application');\n this.canvas.setAttribute(\n 'aria-label',\n 'Interactive 3D venue. Drag or use arrow keys to look around; pinch, scroll, plus or minus to zoom.',\n );\n\n this.renderer = new Renderer({\n canvas: this.canvas,\n dpr: computeDpr(),\n alpha: false,\n antialias: false,\n depth: true,\n stencil: false,\n powerPreference: 'high-performance',\n webgl: 2,\n });\n this.gl = this.renderer.gl;\n this.clearRgb = [BACKGROUND.top[0], BACKGROUND.top[1], BACKGROUND.top[2]];\n this.gl.clearColor(this.clearRgb[0], this.clearRgb[1], this.clearRgb[2], 1);\n\n container.appendChild(this.canvas);\n\n\n\n this.lostHandler = (e: Event) => {\n e.preventDefault();\n opts.onContextLost();\n };\n this.restoredHandler = () => opts.onContextRestored();\n this.canvas.addEventListener('webglcontextlost', this.lostHandler, false);\n this.canvas.addEventListener('webglcontextrestored', this.restoredHandler, false);\n\n this.resize();\n }\n\n /** Match the drawing buffer to the container's CSS box. */\n resize(): { width: number; height: number } {\n const w = Math.max(1, this.container.clientWidth || this.canvas.clientWidth || 1);\n const h = Math.max(1, this.container.clientHeight || this.canvas.clientHeight || 1);\n this.renderer.setSize(w, h);\n return { width: w, height: h };\n }\n\n get pixelHeight(): number {\n return this.renderer.height * this.renderer.dpr;\n }\n\n get aspect(): number {\n return this.renderer.width / Math.max(1, this.renderer.height);\n }\n\n dispose(): void {\n this.canvas.removeEventListener('webglcontextlost', this.lostHandler, false);\n this.canvas.removeEventListener('webglcontextrestored', this.restoredHandler, false);\n const ext = this.gl.getExtension('WEBGL_lose_context');\n if (ext) ext.loseContext();\n if (this.canvas.parentNode) this.canvas.parentNode.removeChild(this.canvas);\n }\n\n /**\n * Test hook: force a full loss→restore cycle. `restoreContext()` must be called\n * only AFTER the browser has dispatched `webglcontextlost` (calling it too soon\n * makes the browser drop the restore request), so we sequence it off a one-shot\n * listener rather than a fixed timeout.\n */\n simulateContextLossCycle(): void {\n const ext = this.gl.getExtension('WEBGL_lose_context') as\n | { loseContext(): void; restoreContext?: () => void }\n | null;\n if (!ext) return;\n ext.loseContext();\n // Chrome drops a restore requested too soon after loseContext(); a short\n // delay lets the loss settle before we ask for the context back.\n setTimeout(() => { if (ext.restoreContext) ext.restoreContext(); }, 300);\n }\n}\n","/**\n * view3d palette + seat-state model — the single source of colour truth for the\n * OGL venue view. Pure data (no GPU, no DOM) so the scene builder and the unit\n * tests share one definition. Colours are linear-ish RGB triplets in 0..1.\n *\n * Look brief (docs/3d-usp-strategy §3): desaturated cool greys for structure,\n * one warm accent for the stage, availability colours only on seats.\n */\n\nexport type SeatState3D = 'available' | 'held' | 'sold' | 'selected' | 'dimmed';\n\n/** Fixed LUT order — the per-instance `iState` float indexes this array, and the\n * fragment shader's `uStateColors` uniform is uploaded in exactly this order. */\nexport const SEAT_STATES: SeatState3D[] = ['available', 'held', 'sold', 'selected', 'dimmed'];\n\nexport function seatStateIndex(state: SeatState3D): number {\n const i = SEAT_STATES.indexOf(state);\n return i < 0 ? 0 : i;\n}\n\nexport type RGB = [number, number, number];\n\n/** Availability colours — the only saturated colours in the scene. */\nexport const SEAT_STATE_COLORS: Record<SeatState3D, RGB> = {\n available: [0.24, 0.82, 0.52],\n held: [0.95, 0.66, 0.22],\n sold: [0.34, 0.39, 0.45],\n selected: [0.24, 0.74, 1.0],\n dimmed: [0.28, 0.32, 0.37],\n};\n\n/**\n * A category colour as UPHOLSTERY — what that seat looks like from inside the\n * room rather than from above it.\n *\n * The same colour has two jobs at two ranges. On the map it is a price tier and\n * must be legible at a glance across a whole venue, so it is saturated. At eye\n * level it is a fabric-covered chair a metre away, and the saturated version\n * reads as moulded plastic: an amphitheatre whose tiers are pink, gold and blue\n * arrives at the seat looking like a toy.\n *\n * So the near-field chair wears a muted version — desaturated toward its own\n * luminance and darkened. Hue survives, which is the point: a buyer sitting in\n * the Terrace can still see where the Orchestra ends, they just are not sitting\n * in a bag of sweets. The DOT keeps the full colour, so nothing changes about\n * reading the map.\n */\nexport function upholsteryTone(rgb: RGB): RGB {\n const luma = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];\n const SAT = 0.42; // how much original hue survives\n const VALUE = 0.62; // and how much of its brightness\n return [\n (luma + (rgb[0] - luma) * SAT) * VALUE,\n (luma + (rgb[1] - luma) * SAT) * VALUE,\n (luma + (rgb[2] - luma) * SAT) * VALUE,\n ];\n}\n\n/** Flat LUT (5 × vec3) for the seat fragment shader uniform. */\nexport function seatStateColorLUT(): number[] {\n const out: number[] = [];\n for (const s of SEAT_STATES) out.push(...SEAT_STATE_COLORS[s]);\n return out;\n}\n\n/** Colour for a state index (from `iState`) — used to fill the per-instance\n * `iColor` attribute CPU-side, avoiding a dynamically-indexed array uniform. */\nexport function seatStateColorByIndex(index: number): RGB {\n const state = SEAT_STATES[index] ?? 'available';\n return SEAT_STATE_COLORS[state];\n}\n\n/** Structure palette — cool desaturated greys + one warm stage accent. */\nexport const STRUCTURE = {\n ground: [0.07, 0.085, 0.11] as RGB,\n tierTop: [0.24, 0.28, 0.34] as RGB,\n tierWall: [0.17, 0.20, 0.25] as RGB,\n /**\n * The lit performance surface.\n *\n * This was [0.42, 0.36, 0.26] and commented \"slightly emissive read\", which it\n * was not — under the scene's rig it resolved to a mid-brown slab, and from a\n * seat the stage was the DIMMEST large surface in a dark hall. That is exactly\n * backwards: the stage is the one place in a venue with light pointed at it,\n * and it is what a buyer's eye should land on when they arrive at their seat.\n *\n * Bright and warm enough to hold that role against the surrounding structure,\n * which sits around 0.24. An authored `fill` still tints it (see `buildShape`),\n * so an organizer's own stage colour survives — it is just no longer lit like\n * a basement.\n */\n stageTop: [0.94, 0.70, 0.38] as RGB,\n stageWall: [0.48, 0.33, 0.18] as RGB,\n decorTop: [0.22, 0.25, 0.29] as RGB,\n decorWall: [0.15, 0.17, 0.20] as RGB,\n gaTop: [0.24, 0.28, 0.33] as RGB,\n gaWall: [0.16, 0.19, 0.23] as RGB,\n /** Exhibition / trade-show booth stand. */\n boothTop: [0.30, 0.33, 0.38] as RGB,\n boothWall: [0.20, 0.23, 0.27] as RGB,\n /** Banquet table top — warmer, so a laid table reads apart from structure. */\n tableTop: [0.38, 0.34, 0.29] as RGB,\n tableWall: [0.24, 0.21, 0.18] as RGB,\n} as const;\n\n/** Background vertical gradient (matches the app's dark UI). */\nexport const BACKGROUND = {\n top: [0.05, 0.06, 0.08] as RGB,\n bottom: [0.10, 0.12, 0.15] as RGB,\n};\n\n/** Parse `#rrggbb` (or `#rgb`) to linear-ish 0..1 RGB; null on anything else. */\nexport function hexToRgb(hex: string | undefined): RGB | null {\n if (!hex) return null;\n let h = hex.trim();\n if (h[0] === '#') h = h.slice(1);\n if (h.length === 3) h = h.split('').map((c) => c + c).join('');\n if (h.length !== 6 || /[^0-9a-fA-F]/.test(h)) return null;\n const n = parseInt(h, 16);\n return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];\n}\n\n/** Mix two colours (a*(1-t) + b*t). */\nexport function mix(a: RGB, b: RGB, t: number): RGB {\n return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];\n}\n\n/** Desaturate toward its own luma by `amount` (0 = unchanged, 1 = grey). */\nexport function desaturate(c: RGB, amount: number): RGB {\n const l = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];\n return mix(c, [l, l, l], amount);\n}\n\n/** Scale a colour by a scalar (baked vertex AO), clamped to [0,1]. */\nexport function scaleRgb(c: RGB, k: number): RGB {\n return [Math.min(1, c[0] * k), Math.min(1, c[1] * k), Math.min(1, c[2] * k)];\n}\n","/**\n * Orbit + dolly + pan camera, damped. Drag = azimuth/polar; wheel/pinch = dolly;\n * right-drag, middle-drag or shift-drag = pan, as does a two-finger drag on\n * touch. Polar clamped [15°,80°]; distance clamped to bounds-derived limits; pan\n * clamped to a bounds-derived box; initial framing = a 3/4 view fitted to bounds.\n *\n * Pan was deliberately omitted in v1 (\"always centred on the venue focal point\"),\n * on the reasoning that a venue is a single object you orbit. That is true only\n * while the whole venue is in frame. Dolly in far enough to read seat labels — the\n * thing the low `minDist` exists to allow — and the pivot stays pinned to the\n * venue centre, so the upper tiers sit off-screen with no way to bring them back\n * except zooming out again. Reported from the marketing hero: \"if I want to move\n * this 3D down to see the upper part it is not possible.\"\n */\n\nimport { Camera, Vec3 } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\n\nconst DEG = Math.PI / 180;\nconst POLAR_MIN = 15 * DEG;\nconst POLAR_MAX = 80 * DEG;\nconst DAMP = 0.12;\nconst FOV = 35;\n/** Fit multiplier past a tight bounds-sphere fit. The 3/4 tilt makes the near\n * ground edge overhang below the fitted sphere, so a wide-shallow layout needs\n * more than a nominal 10% or its front row clips — this clears it while keeping\n * the venue centred with a comfortable margin. */\nconst FRAME_MARGIN = 1.25;\n\nexport interface OrbitBounds {\n center: [number, number, number];\n radius: number;\n}\n\nexport type PrimaryDragMode = 'orbit' | 'pan';\n\nexport class OrbitCamera {\n readonly camera: Camera;\n readonly fovY = FOV;\n private target = new Vec3();\n private azimuth = -30 * DEG;\n private polar = 55 * DEG;\n private distance = 10;\n private azT = -30 * DEG;\n private polT = 55 * DEG;\n private distT = 10;\n private minDist = 1;\n private maxDist = 100;\n private canvas: HTMLElement;\n private requestRender: () => void;\n /** Fired on the FIRST real user-driven orbit/dolly gesture (drag/wheel/pinch),\n * latched so it can drive a one-shot analytics event. Not the intro ease. */\n private onGesture?: () => void;\n private gestureFired = false;\n /** Seat-eye arrival is a fixed physical origin. The buyer may opt into the\n * dedicated look-around viewer, but venue orbit/dolly/pan must not pull the\n * camera outside the selected seat. */\n private interactionEnabled = true;\n\n private dragging = false;\n private panning = false;\n private lastX = 0;\n private lastY = 0;\n private activePointers = new Map<number, { x: number; y: number }>();\n private pinchDist = 0;\n private pinchCx = 0;\n private pinchCy = 0;\n /** Damped pan pivot. `target` chases this the way azimuth chases `azT`. */\n private targetT = new Vec3();\n /** Venue centre + radius, so pan can be clamped to somewhere still useful. */\n private panAnchor = new Vec3();\n private panLimit = 0;\n /** What an unmodified primary-button / one-finger drag does. Section drill-in\n * switches this to pan so a buyer can slide hidden seats into view without\n * discovering a modifier gesture. Shift temporarily inverts the mode. */\n private primaryDragMode: PrimaryDragMode = 'orbit';\n\n private onPointerDown: (e: PointerEvent) => void;\n private onPointerMove: (e: PointerEvent) => void;\n private onPointerUp: (e: PointerEvent) => void;\n private onWheel: (e: WheelEvent) => void;\n private onKeyDown: (e: KeyboardEvent) => void;\n private onContextMenu: (e: Event) => void;\n\n constructor(gl: OGLRenderingContext, canvas: HTMLElement, requestRender: () => void, onGesture?: () => void) {\n this.camera = new Camera(gl, { fov: FOV, near: 0.1, far: 5000, aspect: 1 });\n this.canvas = canvas;\n this.requestRender = requestRender;\n this.onGesture = onGesture;\n\n this.onPointerDown = (e) => {\n if (!this.interactionEnabled) return;\n // Guard: a synthetic/stale pointer id has no active pointer to capture.\n try { this.canvas.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (this.activePointers.size === 1) {\n // Secondary/middle always pans. For the primary button, Shift inverts\n // the visible Rotate/Move mode so both operations remain one gesture\n // away without a toolbar round-trip.\n const secondaryPan = e.button === 2 || e.button === 1;\n const primaryPan = e.button === 0\n && (e.shiftKey ? this.primaryDragMode === 'orbit' : this.primaryDragMode === 'pan');\n this.panning = secondaryPan || primaryPan;\n this.dragging = !this.panning;\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n } else if (this.activePointers.size === 2) {\n this.dragging = false;\n this.panning = false;\n this.pinchDist = this.currentPinchDistance();\n const c = this.pinchCentroid();\n this.pinchCx = c.x;\n this.pinchCy = c.y;\n }\n };\n this.onPointerMove = (e) => {\n if (!this.interactionEnabled) return;\n if (!this.activePointers.has(e.pointerId)) return;\n this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (this.activePointers.size >= 2) {\n const d = this.currentPinchDistance();\n // Fingers apart (d grows) → zoom in (distance shrinks).\n if (this.pinchDist > 0) { this.dollyBy(Math.exp((this.pinchDist - d) * 0.005)); this.fireGesture(); }\n this.pinchDist = d;\n // Two fingers moving TOGETHER pan; the pinch term above already took the\n // spreading component, so the centroid carries the translation.\n const c = this.pinchCentroid();\n this.panBy(c.x - this.pinchCx, c.y - this.pinchCy);\n this.pinchCx = c.x;\n this.pinchCy = c.y;\n return;\n }\n const dx = e.clientX - this.lastX;\n const dy = e.clientY - this.lastY;\n if (this.panning) {\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n if (dx !== 0 || dy !== 0) { this.panBy(dx, dy); this.fireGesture(); }\n return;\n }\n if (!this.dragging) return;\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n if (dx !== 0 || dy !== 0) this.fireGesture();\n this.azT -= dx * 0.006;\n this.polT = Math.max(POLAR_MIN, Math.min(POLAR_MAX, this.polT - dy * 0.006));\n this.requestRender();\n };\n this.onPointerUp = (e) => {\n this.activePointers.delete(e.pointerId);\n try { this.canvas.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n if (this.activePointers.size < 2) this.pinchDist = 0;\n if (this.activePointers.size === 0) { this.dragging = false; this.panning = false; }\n };\n // Right-drag pans, so the context menu must not interrupt the gesture.\n this.onContextMenu = (e) => { e.preventDefault(); };\n this.onWheel = (e) => {\n e.preventDefault();\n if (!this.interactionEnabled) return;\n // Normalise wheel delta across px / line / page modes, then clamp large\n // mouse-wheel bursts. Trackpads stay smooth while one wheel notch no\n // longer launches the camera through (or far away from) the venue.\n const unit = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? 100 : 1;\n const norm = Math.max(-2, Math.min(2, (e.deltaY * unit) / 100));\n this.dollyBy(Math.exp(norm * 0.22));\n this.fireGesture();\n };\n this.onKeyDown = (e) => {\n if (!this.interactionEnabled) return;\n const step = 7 * DEG;\n if (e.shiftKey && (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown')) {\n this.panBy(\n e.key === 'ArrowLeft' ? -32 : e.key === 'ArrowRight' ? 32 : 0,\n e.key === 'ArrowUp' ? -32 : e.key === 'ArrowDown' ? 32 : 0,\n );\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n this.azT += e.key === 'ArrowLeft' ? -step : step;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n this.polT = Math.max(POLAR_MIN, Math.min(POLAR_MAX, this.polT + (e.key === 'ArrowUp' ? -step : step)));\n } else if (e.key === '+' || e.key === '=') {\n this.dollyBy(0.82);\n } else if (e.key === '-' || e.key === '_') {\n this.dollyBy(1.22);\n } else {\n return;\n }\n e.preventDefault();\n this.fireGesture();\n this.requestRender();\n };\n\n canvas.addEventListener('pointerdown', this.onPointerDown);\n canvas.addEventListener('pointermove', this.onPointerMove);\n canvas.addEventListener('pointerup', this.onPointerUp);\n canvas.addEventListener('pointercancel', this.onPointerUp);\n canvas.addEventListener('wheel', this.onWheel, { passive: false });\n canvas.addEventListener('keydown', this.onKeyDown);\n canvas.addEventListener('contextmenu', this.onContextMenu);\n }\n\n private pinchCentroid(): { x: number; y: number } {\n const pts = [...this.activePointers.values()];\n if (!pts.length) return { x: 0, y: 0 };\n let x = 0;\n let y = 0;\n for (const p of pts) { x += p.x; y += p.y; }\n return { x: x / pts.length, y: y / pts.length };\n }\n\n /**\n * Slide the orbit pivot across the camera's own screen plane.\n *\n * Scaled by distance and FOV so a pixel of drag moves the same amount of VENUE\n * under the cursor whatever the zoom: at the overview a drag sweeps the whole\n * bowl, and pushed in among the seats it nudges. A fixed world-units-per-pixel\n * would be unusable at one end or the other.\n */\n private panBy(dxPx: number, dyPx: number): void {\n const h = (this.canvas as HTMLElement).clientHeight || 1;\n // World units spanned by one pixel at the pivot's depth.\n const perPx = (2 * this.distance * Math.tan((this.fovY * DEG) / 2)) / h;\n // Camera basis on the ground-plane projection: right is perpendicular to the\n // view azimuth; \"up\" on screen maps to the horizontal forward direction,\n // damped by the tilt so a top-down view pans in the plane you can see.\n const sinA = Math.sin(this.azimuth);\n const cosA = Math.cos(this.azimuth);\n const rightX = cosA;\n const rightZ = -sinA;\n const cp = Math.cos(this.polar);\n const sp = Math.sin(this.polar);\n const fwdX = -sinA * cp;\n const fwdZ = -cosA * cp;\n this.targetT.x += (-dxPx * rightX + dyPx * fwdX) * perPx;\n this.targetT.z += (-dxPx * rightZ + dyPx * fwdZ) * perPx;\n this.targetT.y += dyPx * sp * perPx;\n this.clampPan();\n this.requestRender();\n }\n\n /** Keep the pivot within a bounds-derived box so a stray drag cannot lose the\n * venue entirely — the Overview chip should never be the only way back. */\n private clampPan(): void {\n if (this.panLimit <= 0) return;\n const lim = this.panLimit;\n const cx = this.panAnchor.x;\n const cy = this.panAnchor.y;\n const cz = this.panAnchor.z;\n this.targetT.x = Math.max(cx - lim, Math.min(cx + lim, this.targetT.x));\n this.targetT.z = Math.max(cz - lim, Math.min(cz + lim, this.targetT.z));\n this.targetT.y = Math.max(cy - lim * 0.5, Math.min(cy + lim, this.targetT.y));\n }\n\n /** One-shot: notify the first real user gesture (drives 3d_orbit_engaged). */\n private fireGesture(): void {\n if (this.gestureFired) return;\n this.gestureFired = true;\n this.onGesture?.();\n }\n\n private currentPinchDistance(): number {\n const pts = [...this.activePointers.values()];\n if (pts.length < 2) return 0;\n return Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);\n }\n\n /** Multiply target distance by `factor` (proportional zoom — same feel near\n * and far), clamped so you can swoop right down among the seats. */\n private dollyBy(factor: number): void {\n this.distT = Math.max(this.minDist, Math.min(this.maxDist, this.distT * factor));\n this.requestRender();\n }\n\n /** Programmatic zoom used by the visible camera controls. Keeping it on the\n * same dolly path as wheel, pinch and keyboard preserves all distance limits. */\n zoomBy(factor: number): void {\n if (!this.interactionEnabled) return;\n this.dollyBy(factor);\n this.fireGesture();\n }\n\n /** Choose what a normal left/one-finger drag does. Shift temporarily performs\n * the other action. This is intentionally public so the view can make the\n * currently active gesture visible rather than hiding it in documentation. */\n setPrimaryDragMode(mode: PrimaryDragMode): void {\n this.primaryDragMode = mode;\n }\n\n /** Enable venue navigation or pin the current camera pose for seat-eye mode. */\n setInteractionEnabled(enabled: boolean): void {\n this.interactionEnabled = enabled;\n if (enabled) return;\n this.activePointers.clear();\n this.dragging = false;\n this.panning = false;\n this.pinchDist = 0;\n }\n\n /**\n * Fit a flattering 3/4 view to the bounds sphere. With `intro`, the camera\n * STARTS nearly top-down (matching the 2D map's orientation) and further out,\n * then the damped `update()` eases it up into the 3/4 architectural angle and\n * dollies in — the venue \"stands up\" instead of teleporting (~600ms).\n */\n frame(bounds: OrbitBounds, intro = false, stageAzimuth?: number, portraitCrop = false): void {\n this.target.set(bounds.center[0], bounds.center[1], bounds.center[2]);\n this.targetT.copy(this.target);\n this.panAnchor.copy(this.target);\n this.panLimit = Math.max(1, bounds.radius) * 1.5;\n const r = Math.max(1, bounds.radius);\n // Aspect-aware fit: the bounds sphere must clear BOTH the vertical and the\n // (aspect-narrowed) horizontal FOV, so a wide designer canvas frames the\n // chart centred with margin instead of parking it low-left. `setAspect` must\n // run before `frame` for the horizontal term to be correct.\n const halfV = (this.fovY * DEG) / 2;\n // A strict horizontal contain fit makes a wide bowl postage-stamp small in\n // portrait. UX prototypes may opt into a bounded crop: treat very narrow\n // screens as 0.82 aspect for OVERVIEW distance only, allowing the venue's\n // outer edges to sit just beyond the screen while its seats remain useful.\n const aspect = portraitCrop ? Math.max(0.82, this.camera.aspect || 1) : (this.camera.aspect || 1);\n const halfH = Math.atan(Math.tan(halfV) * aspect);\n const fit = Math.max(r / Math.tan(halfV), r / Math.tan(halfH));\n // Enter from the STAGE side when the caller knows where the stage is: the\n // camera stands behind the stage looking into the bowl, so every tier faces\n // you on arrival. A fixed azimuth made the entry view a coin flip — charts\n // oriented the other way opened staring at the back of the shell.\n this.azT = stageAzimuth ?? -30 * DEG;\n this.polT = 55 * DEG;\n this.distT = fit * FRAME_MARGIN;\n // Low min so you can swoop down close enough that seat dots are big, tappable\n // targets (\"into your section\"); generous max to pull right back out.\n this.minDist = Math.max(2, r * 0.12);\n this.maxDist = fit * 4;\n if (intro) {\n this.azimuth = this.azT; // no spin — just tilt up + dolly in\n this.polar = 12 * DEG; // near top-down, like the flat 2D view\n this.distance = this.distT * 1.7;\n } else {\n this.azimuth = this.azT;\n this.polar = this.polT;\n this.distance = this.distT;\n }\n this.applyPosition();\n }\n\n setAspect(aspect: number): void {\n this.camera.perspective({ aspect });\n }\n\n /**\n * Damped return to the framed overview from wherever the camera is now (a\n * seat, behind the shell, anywhere): re-pivot on the venue centre without\n * moving the camera, then glide targets back to the 3/4 architectural pose.\n */\n frameSoft(bounds: OrbitBounds, stageAzimuth?: number, portraitCrop = false): void {\n this.target.set(bounds.center[0], bounds.center[1], bounds.center[2]);\n this.targetT.copy(this.target);\n this.panAnchor.copy(this.target);\n this.panLimit = Math.max(1, bounds.radius) * 1.5;\n this.syncFromCamera(); // re-derive pose around the new pivot — no snap\n this.camera.perspective({ fov: this.fovY, aspect: this.camera.aspect });\n const r = Math.max(1, bounds.radius);\n const halfV = (this.fovY * DEG) / 2;\n const aspect = portraitCrop ? Math.max(0.82, this.camera.aspect || 1) : (this.camera.aspect || 1);\n const halfH = Math.atan(Math.tan(halfV) * aspect);\n const fit = Math.max(r / Math.tan(halfV), r / Math.tan(halfH));\n this.azT = stageAzimuth ?? this.azimuth;\n this.polT = 55 * DEG;\n this.distT = fit * FRAME_MARGIN;\n }\n\n /**\n * Tighten the depth range around the actual venue bounds for the current pose.\n *\n * A fixed 0.1..5000 m frustum gives an ordinary arena centimetre-scale depth\n * precision at overview distance—coarser than the separation between a row\n * tread and its structural cap. The resulting z quantisation is the black\n * radial comb previously seen while zooming. A padded bounds sphere contains\n * every venue solid while preserving useful precision outside the bowl; when\n * the camera enters that sphere, the near plane safely returns to 5 cm.\n */\n updateClipping(bounds: OrbitBounds): void {\n const dx = this.camera.position.x - bounds.center[0];\n const dy = this.camera.position.y - bounds.center[1];\n const dz = this.camera.position.z - bounds.center[2];\n const distance = Math.hypot(dx, dy, dz);\n const paddedRadius = Math.max(1, bounds.radius) * 1.18;\n const near = Math.max(0.05, distance - paddedRadius);\n const far = Math.max(near + 10, distance + paddedRadius);\n this.camera.perspective({ near, far, aspect: this.camera.aspect });\n }\n\n /** Damp toward targets; returns true while still moving. */\n update(): boolean {\n const da = this.azT - this.azimuth;\n const dp = this.polT - this.polar;\n const dd = this.distT - this.distance;\n const tx = this.targetT.x - this.target.x;\n const ty = this.targetT.y - this.target.y;\n const tz = this.targetT.z - this.target.z;\n // Pan is damped on the same curve as the rest of the pose, and its epsilon is\n // scaled by distance: 1e-4 world units is imperceptible at the overview but\n // would keep a pushed-in camera redrawing forever.\n const panEps = Math.max(1e-4, this.distance * 1e-4);\n const moving = Math.abs(da) > 1e-4 || Math.abs(dp) > 1e-4 || Math.abs(dd) > 1e-4\n || Math.abs(tx) > panEps || Math.abs(ty) > panEps || Math.abs(tz) > panEps;\n this.azimuth += da * DAMP;\n this.polar += dp * DAMP;\n this.distance += dd * DAMP;\n this.target.x += tx * DAMP;\n this.target.y += ty * DAMP;\n this.target.z += tz * DAMP;\n if (moving) this.applyPosition();\n return moving;\n }\n\n /** Distance from camera to target (for LOD). */\n get currentDistance(): number {\n return this.distance;\n }\n\n /**\n * Re-derive the orbit's spherical state from the camera's CURRENT pose (after a\n * cinematic flight leaves it somewhere arbitrary), so a subsequent drag damps\n * from where it actually is with no snap. Does not move the camera.\n */\n syncFromCamera(): void {\n const dx = this.camera.position.x - this.target.x;\n const dy = this.camera.position.y - this.target.y;\n const dz = this.camera.position.z - this.target.z;\n const dist = Math.hypot(dx, dy, dz) || 1;\n const polar = Math.max(POLAR_MIN, Math.min(POLAR_MAX, Math.acos(Math.max(-1, Math.min(1, dy / dist)))));\n // Distance is NOT clamped here: a flight can park closer than minDist, and\n // clamping would jump the camera radially on the very first drag. The clamp\n // applies lazily from the next user-driven dolly (see dollyBy).\n this.distance = this.distT = dist;\n this.polar = this.polT = polar;\n this.azimuth = this.azT = Math.atan2(dx, dz);\n }\n\n /** Point the orbit pivot at a new world target without moving the camera. */\n setTarget(target: [number, number, number]): void {\n this.target.set(target[0], target[1], target[2]);\n this.targetT.copy(this.target);\n }\n\n /** Restore the base FOV (a flight ends pushed-in) and re-sync orbit state. A\n * flight ends looking at `target` (the venue focal), so re-pivot there first —\n * otherwise the first drag would `lookAt(bounds.center)` and pop the aim. */\n resumeAfterFlight(target?: [number, number, number]): void {\n this.camera.perspective({ fov: this.fovY, aspect: this.camera.aspect });\n if (target) this.target.set(target[0], target[1], target[2]);\n this.targetT.copy(this.target);\n this.syncFromCamera();\n }\n\n private applyPosition(): void {\n const sp = Math.sin(this.polar);\n const x = this.target.x + this.distance * sp * Math.sin(this.azimuth);\n const y = this.target.y + this.distance * Math.cos(this.polar);\n const z = this.target.z + this.distance * sp * Math.cos(this.azimuth);\n this.camera.position.set(x, y, z);\n this.camera.lookAt(this.target);\n }\n\n dispose(): void {\n this.canvas.removeEventListener('pointerdown', this.onPointerDown);\n this.canvas.removeEventListener('pointermove', this.onPointerMove);\n this.canvas.removeEventListener('pointerup', this.onPointerUp);\n this.canvas.removeEventListener('pointercancel', this.onPointerUp);\n this.canvas.removeEventListener('wheel', this.onWheel);\n this.canvas.removeEventListener('keydown', this.onKeyDown);\n this.canvas.removeEventListener('contextmenu', this.onContextMenu);\n this.activePointers.clear();\n }\n}\n","/**\n * Dirty-flag render loop. A frame is drawn only while the camera is moving /\n * damping or an availability update arrived; when idle, no rAF is scheduled at\n * all (zero CPU/GPU when parked). Tracks an FPS EMA over frames actually\n * rendered — it reads as \"idle\" when nothing is scheduled.\n */\n\nexport interface RenderLoopStats {\n fps: number;\n rendered: number;\n idle: boolean;\n}\n\nexport class RenderLoop {\n private frame: (dt: number) => boolean;\n private rafId = 0;\n private running = false;\n private lastTime = 0;\n private fpsEma = 0;\n private rendered = 0;\n\n /** `frame(dt)` renders one frame and returns true if another is needed. */\n constructor(frame: (dt: number) => boolean) {\n this.frame = frame;\n }\n\n requestRender(): void {\n if (this.running) return;\n this.running = true;\n this.lastTime = 0;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n private tick = (now: number): void => {\n const dt = this.lastTime ? (now - this.lastTime) / 1000 : 1 / 60;\n this.lastTime = now;\n if (dt > 0) {\n const instFps = 1 / dt;\n this.fpsEma = this.fpsEma ? this.fpsEma * 0.9 + instFps * 0.1 : instFps;\n }\n this.rendered++;\n const again = this.frame(dt);\n if (again) {\n this.rafId = requestAnimationFrame(this.tick);\n } else {\n this.running = false;\n this.rafId = 0;\n }\n };\n\n stats(): RenderLoopStats {\n return { fps: this.running ? Math.round(this.fpsEma) : 0, rendered: this.rendered, idle: !this.running };\n }\n\n stop(): void {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.running = false;\n }\n}\n","/**\n * The seat level-of-detail ladder.\n *\n * Three rungs, all driven by how far a seat is from the CAMERA rather than by a\n * global switch, so one venue can be showing all three at once — chairs in the\n * row you are sitting in, dots across the bowl, a dissolving tint on the far\n * side:\n *\n * near (≤ CHAIR_FULL_M) extruded chair silhouettes (scene/seatChair.ts)\n * band (…CHAIR_NONE_M) chair scales in / dot fades out — no pop\n * far the instanced billboard dot, as before\n *\n * Everything below is pure so the thresholds can be reasoned about (and tested)\n * without a GL context. The shaders re-derive the same weight per instance from\n * view depth; these constants are the single source both sides read.\n */\n\nexport interface SeatLod {\n /** Multiplier on the seat world radius (1 = full). */\n scale: number;\n /** Fade toward the tier/fade colour (0 = pure state colour). */\n fade: number;\n /**\n * Minimum screen size a dot is allowed to demand, in pixels.\n *\n * The shader holds this floor by GROWING a dot's world radius with depth, and\n * that growth is what smears neighbouring rows into one mass at range: measured\n * mean seat spacing is 0.53–0.58 m against a 0.44 m dot diameter, so a dot only\n * has to grow ~20 % before it touches its neighbour. `iMaxRadius` caps each dot\n * at its own pitch, but a capped dot then sits at its ceiling across the whole\n * far half of the venue, which is exactly the solid-mass look.\n *\n * So the DEMAND is tapered as well as the growth capped. Past the far knee the\n * requested floor relaxes toward SEAT_MIN_PIXELS_FAR, the dots stop pressing\n * against their ceilings, and the gap between rows survives to the back wall.\n * Legibility past that point is the tier tint's job, not the dot's — which is\n * what `fade` above is for.\n */\n minPixels: number;\n}\n\n/** Pixel floor a dot demands in the near/mid field — unchanged from v1. */\nexport const SEAT_MIN_PIXELS_NEAR = 2.5;\n/**\n * Pixel floor a dot demands at full distance.\n *\n * Not zero: a dot that demands nothing shrinks below a pixel, aliases, and\n * shimmers as the camera moves. 1.15 keeps a sub-pixel dot anchored while\n * leaving it far enough under its pitch ceiling that its neighbour stays\n * separate. The fragment stage already dissolves anything that cannot afford\n * even this (`vBudget`).\n */\nexport const SEAT_MIN_PIXELS_FAR = 1.15;\n\n/**\n * View depth, in metres, at or below which a seat draws as a full chair.\n *\n * Six metres is close enough for the chair silhouette to be readable. The old\n * 12 m threshold turned aligned chair edges into black moiré streaks while a\n * buyer was still looking at an entire bowl.\n */\nexport const CHAIR_FULL_M = 6;\n\n/**\n * View depth at and beyond which a seat is a dot again.\n *\n * Beyond ten metres the dot is the clearer representation. A smooth four-metre\n * handover still avoids popping during flight without introducing tiny chair\n * geometry at overview or ordinary section distance.\n */\nexport const CHAIR_NONE_M = 10;\n\n/**\n * Occupants are a close-range detail, not an overview availability marker.\n *\n * At the chair handover distance a person is only a few pixels tall. Thousands\n * of those tiny dark silhouettes alias into radial streaks across a bowl, so\n * people use a tighter band than the furniture they sit on. Seat dots continue\n * to carry availability/state at every distance.\n */\nexport const OCCUPANT_FULL_M = 6;\nexport const OCCUPANT_NONE_M = 9.5;\n/** Camera comfort bubble: do not let a foreground head swallow a seat-eye view. */\nexport const OCCUPANT_NEAR_NONE_M = 0.9;\nexport const OCCUPANT_NEAR_FULL_M = 2.2;\n\n/**\n * Radius, in metres, of the CPU gather that decides which seats are handed to\n * the chair mesh at all.\n *\n * Strictly greater than CHAIR_NONE_M + CHAIR_REBUILD_M. That inequality is the\n * whole no-pop argument: a seat always enters the instanced set while it is\n * still beyond the fade band (weight 0, drawn as nothing), so by the time it is\n * close enough to be visible it has already been in the set for a while. A seat\n * can never blink into existence mid-band, whatever the camera does between two\n * gathers. `chairGatherCoversFadeBand()` asserts it.\n */\nexport const CHAIR_GATHER_M = 30;\n\n/**\n * How far the camera may travel before the gathered set is rebuilt.\n *\n * The gather is the only per-frame CPU cost the near field adds, so it is not\n * run per frame. Four metres against the large gather/fade slack leaves ample\n * room for a seat to enter invisibly before its chair begins to grow.\n */\nexport const CHAIR_REBUILD_M = 4;\n\n/**\n * Hard ceiling on instances handed to the chair mesh.\n *\n * The cap is what makes the near field's cost bounded rather than proportional\n * to venue size, and seats are gathered nearest-first, so it only ever drops the\n * OUTER edge of the disc.\n *\n * Its value is not a round number picked for comfort — it is sized against\n * measured venue density, because a cap that bites inside the fade band would\n * silently reintroduce popping on exactly the biggest venues. Worst-case seats\n * within a radius, measured by standing on 60 sampled seats of each shipped\n * chart:\n *\n * 21 m 26 m 30 m\n * megaStadium 4,278 5,905 7,297\n * uberArena 2,670 3,690 4,585\n * amphitheatre 2,326 2,873 3,211\n *\n * megaStadium's figures fell slightly when its ROW_GAP went 28 → 30 to clear the\n * corner-row spacing floor (see `src/core/megaStadium.ts`): rows further apart\n * is strictly less dense, so the headroom below only grew.\n *\n * The original, more distant chair band required coverage through 26 m, which\n * megaStadium met at 5,905 instances. The tighter readable-detail band now only\n * requires CHAIR_NONE_M + CHAIR_REBUILD_M; retaining the measured 8,192 cap\n * therefore adds substantial safety margin for denser charts.\n * `chairCapCoversFadeBand()` is the\n * executable form of this.\n */\nexport const CHAIR_MAX_INSTANCES = 8192;\n\n/**\n * The near/far ladder invariant, as a value rather than a comment.\n *\n * Exported so the test suite asserts the actual constants instead of restating\n * them: if someone widens the fade band or slows the rebuild without widening\n * the gather, seats start popping in mid-band and this goes false.\n */\nexport function chairGatherCoversFadeBand(): boolean {\n return CHAIR_GATHER_M > CHAIR_NONE_M + CHAIR_REBUILD_M;\n}\n\n/** The radius the gathered set must still reach after the cap has bitten. */\nexport const CHAIR_REQUIRED_COVER_M = CHAIR_NONE_M + CHAIR_REBUILD_M;\n\n/**\n * Does a venue holding `seatsWithinRequiredCover` seats inside\n * CHAIR_REQUIRED_COVER_M still get every one of them into the capped set?\n *\n * The companion to `chairGatherCoversFadeBand`. That one checks the RADIUS is\n * generous enough; this checks the CAP is, which is the failure mode that only\n * appears on the densest chart and is therefore the one most likely to ship.\n */\nexport function chairCapCoversFadeBand(seatsWithinRequiredCover: number): boolean {\n return seatsWithinRequiredCover <= CHAIR_MAX_INSTANCES;\n}\n\n/** GLSL `smoothstep`, so the JS side and the shaders agree exactly. */\nfunction smoothstep(edge0: number, edge1: number, x: number): number {\n const t = Math.min(1, Math.max(0, (x - edge0) / Math.max(1e-6, edge1 - edge0)));\n return t * t * (3 - 2 * t);\n}\n\n/**\n * How much chair a seat at `depth` metres of view depth shows: 1 = full\n * silhouette, 0 = nothing drawn (the dot carries it).\n *\n * The chair SCALES IN rather than fading in. A cross-fade would need the chairs\n * to be transparent through the whole band, and a transparent chair either\n * writes depth (and punches holes in the chairs behind it) or does not (and\n * shows its own back panel through its own seat pad). Growing an OPAQUE chair\n * out of the seat point has neither problem, and it lands where the dot already\n * is: at weight 0 the chair is a zero-size nub exactly at the seat centre, under\n * a dot at full opacity.\n *\n * Mirrored in CHAIR_VERT / SEAT_VERT — keep the three in step.\n */\nexport function chairWeight(depth: number): number {\n return 1 - smoothstep(CHAIR_FULL_M, CHAIR_NONE_M, depth);\n}\n\n/**\n * Display weight for signed view depth. An anchor behind/on the camera must be\n * collapsed, not treated as a zero-distance chair: a near-plane-crossing chair\n * expands into long black triangles during overview/zoom transitions.\n */\nexport function chairViewWeight(viewDepth: number): number {\n return viewDepth > 0.05 ? chairWeight(viewDepth) : 0;\n}\n\n/** How much readable occupant detail is shown at `depth`: 1 = full, 0 = none. */\nexport function occupantWeight(depth: number): number {\n const near = smoothstep(OCCUPANT_NEAR_NONE_M, OCCUPANT_NEAR_FULL_M, depth);\n const far = 1 - smoothstep(OCCUPANT_FULL_M, OCCUPANT_NONE_M, depth);\n return near * far;\n}\n\n/** The dot's share of a seat: the exact complement of `chairWeight`. */\nexport function dotWeight(depth: number): number {\n return 1 - chairWeight(depth);\n}\n\n/**\n * The chair's linear size at `depth`, as a fraction of full size.\n *\n * NOT the same curve as `chairWeight`, and the difference is what the middle of\n * the band actually looks like. Scaling linearly with the weight put a\n * quarter-size chair inside a full-opacity dot for most of the crossover: the\n * dot is ~0.44 m across and holds its size until it is handed over, so a chair\n * at 25 % read as a speck floating inside a blob rather than as a seat.\n *\n * The square root front-loads the growth — half a weight is 71 % of full size —\n * so by the time the dot has faded to half, the chair underneath it is nearly\n * the same size and the two genuinely overlay each other. It still passes\n * through exactly 0 at CHAIR_NONE_M, so nothing pops into existence.\n */\nexport function chairScale(depth: number): number {\n return Math.sqrt(chairWeight(depth));\n}\n\nexport function computeSeatLod(distance: number, radius: number): SeatLod {\n const near = radius * 1.4;\n const far = radius * 3.2;\n if (distance <= near) return { scale: 1, fade: 0, minPixels: SEAT_MIN_PIXELS_NEAR };\n const t = Math.min(1, (distance - near) / Math.max(1e-3, far - near));\n return {\n scale: 1 - t * 0.4,\n fade: t * 0.55,\n // Tapered on the same ramp as the fade: as the block starts reading by its\n // tier tint, the dots stop fighting each other for pixels.\n minPixels: SEAT_MIN_PIXELS_NEAR + t * (SEAT_MIN_PIXELS_FAR - SEAT_MIN_PIXELS_NEAR),\n };\n}\n","/**\n * \"Which seats are close enough to be worth drawing as chairs?\" — a uniform XZ\n * grid over the seat cloud, queried nearest-cell-first.\n *\n * This is the piece that makes the near field affordable. The chair mesh could\n * simply carry every seat and let the vertex stage collapse the far ones to\n * zero size, but that is 52,000 × 72 vertices of shading per frame to draw a few\n * hundred chairs — the whole 60 fps claim, spent on geometry that is discarded.\n * Instead the CPU hands the mesh a COMPACT set bounded by CHAIR_MAX_INSTANCES,\n * so near-field cost is a function of the camera's neighbourhood and not of\n * venue size.\n *\n * The grid is built lazily on the first gather: a camera that never comes down\n * to the deck never pays for it, and the intro pose is always far out.\n */\n\n/** Target seats per cell. Small enough to prune hard, large enough that the\n * bucket arrays stay short and cache-friendly. */\nconst SEATS_PER_CELL = 4;\n\nexport class NearFieldIndex {\n private readonly iPosition: Float32Array;\n private readonly count: number;\n private minX = 0;\n private minZ = 0;\n private cell = 1;\n private cols = 1;\n private rows = 1;\n /** CSR-style buckets: `cellStart[c]…cellStart[c+1]` indexes into `cellItems`. */\n private cellStart: Int32Array | null = null;\n private cellItems: Int32Array | null = null;\n\n constructor(iPosition: Float32Array, count: number) {\n this.iPosition = iPosition;\n this.count = count;\n }\n\n /** Built on demand; safe to call repeatedly. */\n private ensureGrid(): void {\n if (this.cellStart || this.count === 0) return;\n const p = this.iPosition;\n let minX = Infinity, minZ = Infinity, maxX = -Infinity, maxZ = -Infinity;\n for (let i = 0; i < this.count; i++) {\n const x = p[i * 3], z = p[i * 3 + 2];\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (z < minZ) minZ = z;\n if (z > maxZ) maxZ = z;\n }\n const w = Math.max(maxX - minX, 1e-3);\n const h = Math.max(maxZ - minZ, 1e-3);\n this.cell = Math.max(Math.sqrt((w * h * SEATS_PER_CELL) / this.count), 0.25);\n this.cols = Math.max(1, Math.ceil(w / this.cell) + 1);\n this.rows = Math.max(1, Math.ceil(h / this.cell) + 1);\n this.minX = minX;\n this.minZ = minZ;\n // Counting sort into CSR — no per-cell arrays, one pass to count and one to\n // place. At 52k seats this is two linear scans and two typed allocations.\n const nCells = this.cols * this.rows;\n const start = new Int32Array(nCells + 1);\n const cellOf = (i: number): number => {\n const cx = Math.min(this.cols - 1, Math.max(0, Math.floor((p[i * 3] - minX) / this.cell)));\n const cz = Math.min(this.rows - 1, Math.max(0, Math.floor((p[i * 3 + 2] - minZ) / this.cell)));\n return cz * this.cols + cx;\n };\n for (let i = 0; i < this.count; i++) start[cellOf(i) + 1]++;\n for (let c = 0; c < nCells; c++) start[c + 1] += start[c];\n const items = new Int32Array(this.count);\n const cursor = start.slice(0, nCells);\n for (let i = 0; i < this.count; i++) items[cursor[cellOf(i)]++] = i;\n this.cellStart = start;\n this.cellItems = items;\n }\n\n /**\n * Fill `out` with the indices of seats within `radius` metres of (camX, camZ),\n * nearest cell-ring first, and return how many were written.\n *\n * Ring order is what makes the CHAIR_MAX_INSTANCES cap harmless: when the cap\n * bites it drops the OUTERMOST seats, which are the ones already past the fade\n * band and drawing nothing. A cap that truncated in index order would instead\n * punch holes in the row you are sitting in.\n *\n * Note this is a horizontal (XZ) query and ignores height. A stacked venue's\n * upper tier is therefore gathered along with the stalls beneath it — which is\n * correct, because the fade weight is re-derived from true view depth in the\n * shader anyway. The grid's only job is to bound the candidate set.\n */\n gather(camX: number, camZ: number, radius: number, out: Int32Array): number {\n this.ensureGrid();\n const start = this.cellStart;\n const items = this.cellItems;\n if (!start || !items) return 0;\n const cap = out.length;\n const r2 = radius * radius;\n const cx = Math.floor((camX - this.minX) / this.cell);\n const cz = Math.floor((camZ - this.minZ) / this.cell);\n const maxRing = Math.ceil(radius / this.cell) + 1;\n const p = this.iPosition;\n let n = 0;\n for (let ring = 0; ring <= maxRing && n < cap; ring++) {\n const z0 = cz - ring, z1 = cz + ring;\n const x0 = cx - ring, x1 = cx + ring;\n for (let gz = z0; gz <= z1 && n < cap; gz++) {\n if (gz < 0 || gz >= this.rows) continue;\n // Only the newly-exposed cells of this ring: the full row on the top and\n // bottom edges, just the two end cells in between.\n const edge = (gz === z0 || gz === z1);\n for (let gx = x0; gx <= x1 && n < cap; gx++) {\n if (!edge && gx !== x0 && gx !== x1) { gx = x1 - 1; continue; }\n if (gx < 0 || gx >= this.cols) continue;\n const c = gz * this.cols + gx;\n for (let k = start[c], e = start[c + 1]; k < e && n < cap; k++) {\n const i = items[k];\n const dx = p[i * 3] - camX;\n const dz = p[i * 3 + 2] - camZ;\n if (dx * dx + dz * dz <= r2) out[n++] = i;\n }\n }\n }\n }\n return n;\n }\n}\n","/**\n * SeatMap chart document — the single source of truth shared by the\n * Designer (authoring) and the Renderer (buyer picker).\n *\n * Coordinates are abstract units (roughly \"pixels at scale 1\"); the renderer\n * fits the chart to its container.\n * Rows are PARAMETRIC (origin + count + spacing + curve + rotation) — never\n * store per-seat coordinates in the document; expansion happens in layout.ts.\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface CubicPath {\n start: Point;\n control1: Point;\n control2: Point;\n end: Point;\n}\n\n/** A real closed section boundary. `outline` remains its sampled collision and\n * persistence fallback; this path is the smooth authoring/buyer paint source. */\nexport type SectionPathSegment =\n | { kind: 'line'; end: Point }\n | { kind: 'arc'; center: Point; radius: number; clockwise: boolean; end: Point }\n | { kind: 'bezier'; control1: Point; control2: Point; end: Point };\n\nexport interface SectionOutlinePath {\n version: 1;\n closed: true;\n start: Point;\n segments: SectionPathSegment[];\n}\n\nexport interface Category {\n key: string;\n label: string;\n color: string;\n /** Base price — used when the category has no explicit tiers. */\n price?: number;\n /** Durable evidence for semantic/display facts proposed while converting a\n * private reference into sellable inventory. */\n referenceCategorySource?: ReferenceCategorySource;\n /**\n * Ticket tiers (Adult / Child / Senior…). When present, a buyer picks a tier\n * per seat in this category and the tier's price applies; the first tier is the\n * default. Per-category (not per-seat) pricing — see Batch 3.5. Empty/absent =\n * a single price (the `price` above).\n */\n tiers?: CategoryTier[];\n}\n\nexport interface ReferenceCategorySource {\n assetId: string;\n /** Original sampled section color. `Category.color` is the approved output\n * color and may differ only with separately recorded evidence. */\n sourceColor: string;\n /** Every exact sampled color normalized into the same original 4-bit/channel\n * segmentation class. Older documents may contain only `sourceColor`. */\n sourceColors?: string[];\n /** Logical sections whose generated inventory uses this category. */\n logicalSectionIds?: string[];\n /** Source-color grouping is deterministic; a semantic regrouping needs its\n * own confirmed assignment evidence. */\n assignmentDerivation?: 'source-color-class' | 'confirmed-logical-sections';\n assignmentEvidence?: 'user-confirmed' | 'authoritative-source';\n assignmentSourceDescription?: string;\n /** Optional legend/commercial swatch stated by the source. This remains\n * immutable provenance when the approved accessible output color differs. */\n sourcePaletteColor?: string;\n sourcePaletteColorEvidence?: 'user-confirmed' | 'authoritative-source';\n sourcePaletteColorSourceDescription?: string;\n labelEvidence: 'user-confirmed' | 'authoritative-source';\n labelSourceDescription: string;\n priceEvidence: 'user-confirmed' | 'authoritative-source';\n priceSourceDescription: string;\n outputColorEvidence?: 'user-confirmed' | 'authoritative-source';\n outputColorSourceDescription?: string;\n}\n\n/** One ticket tier within a category: a named price (Adult, Child, Senior…). */\nexport interface CategoryTier {\n id: string;\n name: string;\n price: number;\n}\n\n/**\n * Accessibility accommodations a seat can carry. Mirrors the taxonomy real\n * venues (and seats.io) expose so buyers can filter for exactly what they need\n * and organizers can mark seats precisely. `wheelchair` is the legacy default.\n */\nexport type AccessibilityType =\n | 'wheelchair'\n | 'companion'\n | 'semi-ambulatory'\n | 'hearing'\n | 'cart'\n | 'sign-language'\n | 'plus-size'\n | 'lift-armrest';\n\nexport interface AccessibilityMeta {\n key: AccessibilityType;\n /** Full descriptive label (designer checkbox, picker legend). */\n label: string;\n /** Compact label for chips/badges. */\n short: string;\n /** Single-glyph badge shown on seat chips + filter chips. */\n icon: string;\n}\n\n/** Ordered taxonomy — drives the designer seat panel and the picker filters. */\nexport const ACCESSIBILITY_TYPES: AccessibilityMeta[] = [\n { key: 'wheelchair', label: 'Wheelchair space', short: 'Wheelchair', icon: '♿' },\n { key: 'companion', label: 'Companion seat', short: 'Companion', icon: '🧑🤝🧑' },\n { key: 'semi-ambulatory', label: 'Semi-ambulatory (limited mobility)', short: 'Limited mobility', icon: '🦯' },\n { key: 'hearing', label: 'Assistive listening', short: 'Hearing', icon: '🦻' },\n { key: 'cart', label: 'CART live-caption view', short: 'CART captions', icon: 'CC' },\n { key: 'sign-language', label: 'Sign-language view', short: 'Sign language', icon: '🤟' },\n { key: 'plus-size', label: 'Plus-size seat', short: 'Plus-size', icon: '💺' },\n { key: 'lift-armrest', label: 'Lift-up armrest', short: 'Lift armrest', icon: '↕️' },\n];\n\nconst ACCESSIBILITY_LABEL = new Map(ACCESSIBILITY_TYPES.map((a) => [a.key, a]));\n\n/** Metadata for one accessibility key (undefined for unknown keys). */\nexport function accessibilityMeta(key: AccessibilityType): AccessibilityMeta | undefined {\n return ACCESSIBILITY_LABEL.get(key);\n}\n\n/**\n * Outer-ring colour per accommodation. Shared by the buyer picker and the\n * designer canvas so an accessible seat reads with the same hue in both — the\n * seat's first-listed type wins. `wheelchair` blue is the default fallback.\n */\nexport const ACCESSIBILITY_RING_COLOR: Record<AccessibilityType, string> = {\n wheelchair: '#3b82f6',\n companion: '#8b5cf6',\n 'semi-ambulatory': '#0ea5e9',\n hearing: '#14b8a6',\n cart: '#7c3aed',\n 'sign-language': '#f59e0b',\n 'plus-size': '#ec4899',\n 'lift-armrest': '#22c55e',\n};\n\n/** Ring colour for a seat's accessibility set (first-listed type wins). */\nexport function accessibilityRingColor(types: AccessibilityType[] | undefined): string {\n const primary = types?.[0];\n return (primary && ACCESSIBILITY_RING_COLOR[primary]) || '#3b82f6';\n}\n\nexport type SeatViewCoverage =\n | 'exact-seat'\n | 'row-representative'\n | 'section-representative'\n | 'venue-representative';\n\n/** Calibration and truth metadata that travels with an organizer panorama.\n * The URL remains separate for backward compatibility with existing charts. */\nexport interface SeatViewMetadata {\n projection?: 'equirectangular';\n coverage?: SeatViewCoverage;\n /** Optional lightweight first paint. `viewFromSeatUrl` remains the sharp source. */\n previewUrl?: string;\n previewWidth?: number;\n previewHeight?: number;\n /** Degrees relative to the authored focal/stage direction. */\n initialBearingDeg?: number;\n /** Positive looks up. The viewer clamps to its safe pitch range. */\n initialPitchDeg?: number;\n capturedAt?: string;\n /** Buyer-safe provenance, e.g. \"Provided by the venue\". */\n sourceLabel?: string;\n sourceWidth?: number;\n sourceHeight?: number;\n captureEyeHeightM?: number;\n eventConfigurationId?: string;\n}\n\n/** How broadly a confidence record applies to the selected inventory unit.\n * This is resolved by the layout engine from the authoring scope; authors do\n * not get to label venue-wide evidence as exact-seat evidence. */\nexport type SeatConfidenceCoverage =\n | 'exact-seat'\n | 'row-representative'\n | 'section-representative'\n | 'floor-representative'\n | 'venue-representative';\n\nexport type SeatConfidenceModelLevel =\n | 'generated'\n | 'geometry-verified'\n | 'configuration-verified';\n\nexport type SeatConfidenceRealityLevel =\n | 'none'\n | 'comparable-build-reference'\n | 'actual-build-checked';\n\n/** A bounded, explainable visibility result. It is deliberately a modeled\n * target score—not a promise that this percentage of a show will be visible. */\nexport interface SeatConfidenceModeledTarget {\n label: string;\n clearRayCount: number;\n totalRayCount: number;\n classification: 'clear' | 'partial' | 'obstructed';\n /** Buyer-safe method name, e.g. \"45-ray screen target model\". */\n methodLabel: string;\n occluderLabels?: string[];\n}\n\n/** Versioned source evidence for a seat-confidence passport.\n *\n * Model verification and reality evidence are independent dimensions. A\n * comparable photograph must never outrank a venue-approved current event\n * model, and a strong model must never silently become real-world evidence.\n * `coverage` is added only after scope resolution on ExpandedSeat. */\nexport interface SeatConfidenceEvidence {\n schema: 'seatlayer.seat-confidence/1';\n evidenceId: string;\n modelVersion: string;\n evidenceKind: 'synthetic-model' | 'organizer-approved' | 'calibrated-provider';\n modelLevel: SeatConfidenceModelLevel;\n realityLevel?: SeatConfidenceRealityLevel;\n /** Required for configuration verification and actual-build checking. */\n eventConfigurationId?: string;\n assessedAt?: string;\n validUntil?: string;\n /** Buyer-safe approving organization or role; never a named individual. */\n approvedByRole?: string;\n /** Buyer-safe evidence provenance, not an asset URL or private filename. */\n sourceLabel?: string;\n limitations?: string[];\n modeledTarget?: SeatConfidenceModeledTarget;\n}\n\nexport interface ResolvedSeatConfidenceEvidence extends SeatConfidenceEvidence {\n coverage: SeatConfidenceCoverage;\n}\n\nexport interface SeatOverride {\n /** 0-based seat index within the row. */\n index: number;\n /** Physical seat absent (pillar, sound desk) — numbering gap preserved. */\n skip?: boolean;\n /** Position nudge in chart units. */\n dx?: number;\n dy?: number;\n /** Replace the computed label entirely. */\n label?: string;\n /** Buyer-facing copy only. Booking/API identity remains row id + slot index\n * internally and the legacy `label` externally for backwards compatibility. */\n displayLabel?: string;\n categoryKey?: string;\n /** @deprecated legacy flag — read as `['wheelchair']`; write `accessibility`. */\n accessible?: boolean;\n /** Accessibility accommodations of this seat (empty/absent = none). */\n accessibility?: AccessibilityType[];\n /**\n * Physical wheelchair provision. Absent keeps legacy seat rendering;\n * `seat-present` is an explicit removable/fixed accessible chair, while\n * `no-seat` is an empty wheelchair bay that remains one sellable inventory\n * unit. This is deliberately distinct from `skip`, which removes inventory.\n */\n wheelchairSpaceType?: 'seat-present' | 'no-seat';\n /** Commercial selling/view attributes are deliberately not accessibility. */\n commercial?: SeatCommercialAttributes;\n /** Seat-specific view photo; falls back to the row photo. */\n viewFromSeatUrl?: string;\n /** Calibration/truth for `viewFromSeatUrl`; ignored when the URL is absent. */\n viewFromSeatMeta?: SeatViewMetadata;\n /** Seat-scoped confidence evidence, independent of photo availability. */\n confidenceEvidence?: SeatConfidenceEvidence;\n /** Per-seat label size/color override; falls back to the row/theme default.\n * Size is clamped to LABEL_STYLE_MIN_SIZE..MAX_SIZE; color is passed through\n * the shared auto-contrast rule at paint time (see {@link LabelStyle}). */\n labelStyle?: LabelStyle;\n}\n\nexport interface SeatCommercialAttributes {\n restrictedView?: boolean;\n obstructedView?: boolean;\n premium?: boolean;\n note?: string;\n}\n\n/** The selling attributes that can be marked on a seat one at a time, the same\n * way an accommodation can. `note` is free text and belongs to one seat, so it\n * is deliberately not here. */\nexport type SeatCommercialMark = 'obstructedView' | 'restrictedView' | 'premium';\n\nexport interface SeatCommercialMeta {\n key: SeatCommercialMark;\n /** Full descriptive label (designer panel, tooltips). */\n label: string;\n /** Compact label for chips. */\n short: string;\n /** Single-glyph badge, matching the accessibility chips. */\n icon: string;\n}\n\n/**\n * Ordered taxonomy of sellable view attributes.\n *\n * Kept beside {@link ACCESSIBILITY_TYPES} and shaped identically because the\n * designer paints them with the same tool: an author marking the back two rows\n * of a stand as obstructed is doing the same gesture as marking a wheelchair\n * space, and at a thousand seats it has to cost the same. They stay separate\n * TYPES because they mean different things — an accommodation is a buyer's\n * access need, a selling attribute is what the seat is worth.\n */\nexport const SEAT_COMMERCIAL_MARKS: SeatCommercialMeta[] = [\n { key: 'obstructedView', label: 'Obstructed view', short: 'Obstructed', icon: '⛔' },\n { key: 'restrictedView', label: 'Restricted view', short: 'Restricted', icon: '👁' },\n { key: 'premium', label: 'Premium seat', short: 'Premium', icon: '★' },\n];\n\nconst SEAT_COMMERCIAL_LABEL = new Map(SEAT_COMMERCIAL_MARKS.map((mark) => [mark.key, mark]));\n\n/** Metadata for one selling attribute (undefined for unknown keys). */\nexport function seatCommercialMeta(key: SeatCommercialMark): SeatCommercialMeta | undefined {\n return SEAT_COMMERCIAL_LABEL.get(key);\n}\n\n/**\n * Per-object label ink + size overrides layered on top of the chart-wide Theme\n * defaults (rowLabelColor / textColor for rows, the section-name ink for\n * sections). Both fields are optional: an absent field means \"inherit the theme\n * default\". `color` is a preferred hex — renderers still pass it through the\n * shared auto-contrast rule (`stateAwareBookableLabelInk`), so a choice that\n * would be illegible over the seat/section background is switched to black or\n * white at paint time. `size` is a font size in chart units, clamped to\n * LABEL_STYLE_MIN_SIZE..LABEL_STYLE_MAX_SIZE by the shared ops.\n */\nexport interface LabelStyle {\n size?: number;\n color?: string;\n}\n\n/** Clamp bounds for a per-object label `size`, shared by ops, MCP, and UI. */\nexport const LABEL_STYLE_MIN_SIZE = 8;\nexport const LABEL_STYLE_MAX_SIZE = 24;\n\nexport interface LabelPresentation {\n visible?: boolean;\n /** Exact Designer-owned label anchor; public semantic MCP schemas omit it. */\n position?: Point;\n rotation?: number;\n style?: 'plain' | 'pill';\n /** Per-object size/color override for this row's or section's label. */\n labelStyle?: LabelStyle;\n /**\n * End-position preset for a ROW label — which end(s) of the row show it:\n * - `start` (default/undefined) — the row's numbering-start end (legacy behaviour).\n * - `end` — the far end of the row.\n * - `both` — a label at BOTH ends.\n * - `none` — hidden (kept coherent with `visible: false`).\n * A free-drag `position` overrides the preset (the designer shows a 'custom'\n * state). Ignored for sections (they use `position`/`visible` only).\n */\n positionPreset?: 'start' | 'end' | 'both' | 'none';\n}\n\n/** Persisted authoring ergonomics that must not change buyer inventory or\n * rendering. Buyer projections strip this object before a chart is served. */\nexport interface ObjectEditorState {\n /** Objects sharing this id select and transform as one authoring unit. */\n groupId?: string;\n /** Protect canvas geometry while keeping ordinary Inspector properties editable. */\n locked?: boolean;\n /** Remove the object from the authoring scene until Reveal hidden is used. */\n hidden?: boolean;\n}\n\nexport interface AuthoringObjectState {\n editor?: ObjectEditorState;\n}\n\n/** Brand/venue theming — applied by the renderer in both designer and picker. */\nexport interface ChartTheme {\n /** Canvas background color (default dark: #0e1117-ish radial). */\n background?: string;\n /** Preferred text color for the numbers inside seat markers. */\n seatLabelColor?: string;\n /** Preferred color for row identifiers such as A, B, C. Falls back to textColor. */\n rowLabelColor?: string;\n /** Selection ring / accent color (default white ring + brand accent). */\n selectionColor?: string;\n /** Décor (stage/shape) default fill. */\n decorFill?: string;\n /** Free-text color default. */\n textColor?: string;\n /** Font family (CSS stack) for all rendered text — row labels, seat numbers, sections, décor text. */\n fontFamily?: string;\n /** Seat size multiplier on the base radius (0.7–1.6, default 1) — bigger seats fit longer labels. */\n seatScale?: number;\n // ---- White-label branding (applied by the buyer picker chrome) ----\n /** Brand accent color — recolors buttons, links, the hold pill, selection UI. */\n accent?: string;\n /** Ink color for text on the accent (e.g. button labels). Default light. */\n accentInk?: string;\n /** Organizer logo shown in the picker header (data/R2 URL). Falls back to the name. */\n logoUrl?: string;\n /** Brand/venue name shown in the picker header when no event name is set. */\n brandName?: string;\n /** Paid-tier flag: hide the \"Powered by SeatMap\" badge. */\n hideBadge?: boolean;\n}\n\nexport interface RowObject extends AuthoringObjectState {\n type: 'row';\n id: string;\n /** Present when this row was materialized by the in-canvas reference scan.\n * A re-scan of the same asset replaces rows carrying this marker and NEVER\n * touches hand-authored rows — the same replace-generated-only invariant as\n * applyReferenceInventory. */\n referenceScan?: { assetId: string };\n /** Row label, e.g. \"A\". Seat labels are `${label}-${n}`. */\n label: string;\n /** Buyer-facing row name. `label` remains the legacy inventory prefix. */\n displayLabel?: string;\n /**\n * Buyer-facing type word override (seats.io \"Displayed type\"). Replaces the\n * hardcoded \"Row\" in the picker tooltip/confirm/cart, e.g. \"Table\", \"Bench\",\n * \"Aisle\". ≤24 chars; absent = the default \"Row\". Pure presentation.\n */\n displayType?: string;\n labelPresentation?: LabelPresentation;\n /** Position of the FIRST seat. */\n origin: Point;\n /** Degrees, clockwise. 0 = seats laid out along +x. */\n rotation: number;\n /**\n * Total arc sweep in degrees across the whole row. 0 = straight.\n * Positive bends away from +y (concave toward the focal point when the\n * row faces it). Typical theatre rows: 10–40.\n */\n curve: number;\n seatCount: number;\n /** Distance between adjacent seat centers, in chart units. */\n seatSpacing: number;\n /** Optional exact cubic centreline. Normal code owns these coordinates and\n * distributes seats by arc length; MCP/model inputs never submit them. */\n path?: CubicPath;\n categoryKey: string;\n /** Deterministic provenance for rows fitted from confirmed reference\n * inventory. It enables revision-safe replacement without touching manually\n * authored rows or accepting client coordinates. */\n referenceInventorySource?: ReferenceInventorySource;\n /** Semantic parameters for a row produced by the shared Arc/Fan operation.\n * Designer can reopen these parameters while every segment in the group\n * still carries the same generation signature. Public MCP tools never accept\n * the stored center/angles as arbitrary model-authored coordinates. */\n arcFanGeneration?: {\n kind: 'arc-fan-v1';\n groupId: string;\n center: Point;\n innerRadius: number;\n rowCount: number;\n rowGap: number;\n startAngle: number;\n endAngle: number;\n seatPitch: number;\n fit: 'seat-pitch' | 'fixed-count';\n seatsPerRow?: number;\n facing: 'inward' | 'outward';\n taperDegrees: number;\n skewDegrees: number;\n aisleGaps: { left: number; center: number; right: number };\n rowLabelStart: number;\n seatLabelStart: number;\n rowIndex: number;\n segmentIndex: number;\n };\n /**\n * Membership in one buyer-facing segmented row. Physical component rows and\n * their `${rowId}:${slotIndex}` inventory ids remain authoritative; this\n * metadata only supplies logical ordering/presentation and explicit aisle\n * continuity. The descriptor is repeated on every component so selecting any\n * one can resolve the complete logical row without a chart-level side table.\n */\n segmentedRow?: {\n kind: 'segmented-row-v1';\n groupId: string;\n componentIndex: number;\n componentCount: number;\n /** The first component must use `start`; later boundaries are explicit. */\n boundaryBefore: 'start' | 'continuous' | 'break';\n /** Buyer-facing row name; technical component `label` values never change. */\n displayLabel: string;\n displayType?: string;\n labelPresentation?: LabelPresentation;\n viewFromSeatUrl?: string;\n viewFromSeatMeta?: SeatViewMetadata;\n confidenceEvidence?: SeatConfidenceEvidence;\n /** Presentation intent for a continuous node-defined centreline. */\n smoothing?: boolean;\n };\n /**\n * Versioned provenance for rows created by the multiple/intertwined block\n * generator. Manual geometry edits remove this marker rather than allowing a\n * later regeneration to overwrite hand-authored work.\n */\n rowBlockGeneration?: {\n kind: 'row-block-v1';\n groupId: string;\n style: 'multiple' | 'intertwined';\n rowIndex: number;\n rowCount: number;\n seatsPerRow: number;\n origin: Point;\n rotation: number;\n rowGap: number;\n seatSpacing: number;\n curve: number;\n /** Stable canonical generator signature shared by every intact member. */\n signature: string;\n };\n /** First seat number (default 1). Roman/letters read it as a 1-based ordinal\n * (start 1 → I / A). */\n seatLabelStart?: number;\n /** Seat numbering within the row (default decimal, ltr, step 1). */\n seatNumbering?: {\n /** ltr / rtl number from an end; `center` numbers outward from the middle\n * (centre seat lowest — the premium-centre theatre convention). */\n direction: 'ltr' | 'rtl' | 'center';\n /** 2 = odd/even numbering (1,3,5… — start at 2 for evens). */\n step?: 1 | 2;\n /**\n * Label scheme for the seat NUMBER part (the row prefix is separate).\n * Default `decimal`. Composition with `direction`/`step`/`seatLabelStart`:\n * - `decimal` 1,2,3 — honours direction + step + start.\n * - `odd` 1,3,5 — odd numbers from the first odd ≥ start.\n * - `even` 2,4,6 — even numbers from the first even ≥ start.\n * - `updown` 1,3,5,…,6,4,2 — odd-up-even-back; REPLACES direction (uses\n * physical left→right order); start shifts.\n * - `updown-descending` …5,3,1,2,4,6 — odd-back-even-up; the distinct\n * reverse up/down sequence. Also replaces\n * direction and uses physical order.\n * - `roman` I,II,III — honours direction + step + start (uppercase).\n * - `letters-upper` A,B,C…Z,AA — honours direction + step + start.\n * - `letters-lower` a,b,c…z,aa — honours direction + step + start.\n * Like `step`/`direction` today, the scheme changes the seat's inventory\n * label (its booking identity), by design.\n */\n scheme?: 'decimal' | 'odd' | 'even' | 'updown' | 'updown-descending' | 'roman' | 'letters-upper' | 'letters-lower';\n /** Optional prefix prepended to every seat number, e.g. 'R' → 'R1', 'R2'. */\n prefix?: string;\n /**\n * End-at preset (\"useEndAt\"): the row's numbering ENDS at this value instead\n * of starting at `seatLabelStart`. The start is derived so the last-numbered\n * seat (highest position rank) lands on `endAt`, respecting the scheme's step\n * (odd/even = 2). When set it WINS over the stored `seatLabelStart` (which is\n * left untouched). For letters it is a 1-based number index (26 → last seat\n * 'Z'); `updown` owns its own sequence and ignores `endAt`.\n */\n endAt?: number;\n };\n /**\n * Per-seat exceptions, keyed by seat index (0-based position in the row).\n * `skip` removes the physical seat but keeps the numbering gap (theatre\n * convention: a pillar eats A-3; A-4 stays A-4).\n */\n overrides?: SeatOverride[];\n /**\n * Organizer-supplied equirectangular 360 (or wide photo) shown as the\n * view-from-seat for every seat in this row. When absent, the picker\n * generates a synthetic panorama from chart geometry.\n */\n viewFromSeatUrl?: string;\n viewFromSeatMeta?: SeatViewMetadata;\n /** Row-scoped confidence evidence inherited by seats without a closer record. */\n confidenceEvidence?: SeatConfidenceEvidence;\n /** Default commercial attributes inherited by seats without an override. */\n commercial?: SeatCommercialAttributes;\n}\n\nexport interface GAAreaObject extends AuthoringObjectState {\n type: 'gaArea';\n id: string;\n /** Stable technical/inventory label. */\n label: string;\n /** Buyer-facing area name; technical `label` and GA unit ids stay stable. */\n displayLabel?: string;\n /** Buyer-facing type word override (seats.io \"Displayed type\"), ≤24 chars.\n * Absent = the default type word. Pure presentation. */\n displayType?: string;\n /** Closed polygon, in chart units. */\n points: Point[];\n /** Explicit aisles/pillars/cutouts excluded from the sellable GA surface. */\n holes?: Point[][];\n capacity: number;\n categoryKey: string;\n /** Corner-rounding radius in chart units (default 0 = sharp corners). Pure\n * presentation — softens the polygon's corners in every renderer without\n * touching capacity, unit identities, or the stored points. Clamped per\n * corner to half the shorter adjacent edge at draw time. */\n cornerRadius?: number;\n /**\n * Durable inventory provenance for a surface produced by Join Areas.\n *\n * A GA unit is identified by the id and zero-based range of the area that\n * originally authored it, not by the current polygon which happens to own\n * it. Keeping those source ranges means a geometric join never renumbers an\n * already published/booked unit. Ordinary (never-joined) areas omit this\n * field and implicitly own `[0, capacity)` under their own id.\n *\n * The ranges must be non-overlapping, contain positive whole counts, and sum\n * exactly to `capacity`; validation rejects malformed metadata. Capacity\n * growth appends a new range under the surviving area id, while shrinking a\n * joined area is deliberately refused because it would silently destroy\n * stable inventory identities.\n */\n inventorySegments?: GAInventorySegment[];\n referenceInventorySource?: ReferenceInventorySource;\n}\n\nexport interface GAInventorySegment {\n sourceAreaId: string;\n startIndex: number;\n count: number;\n}\n\n/**\n * Durable evidence link for sellable objects generated from a private reference.\n * The client supplies facts and stable logical-section ids, never coordinates.\n */\nexport interface ReferenceAccessibilitySource {\n placementDerivation: 'server-synthesized-row-edges';\n groupLogicalSectionIds: string[];\n assignmentEvidence: 'user-confirmed' | 'authoritative-source';\n assignmentSourceDescription: string;\n counts: Array<{\n type: AccessibilityType;\n count: number;\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n }>;\n}\n\nexport interface ReferenceInventorySource {\n assetId: string;\n logicalSectionId: string;\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n /** Distinguishes directly supplied inventory from a user-approved server\n * distribution based only on an aggregate capacity. */\n derivation?: 'explicit-inventory' | 'server-synthesized-from-aggregate';\n /** Evidence for the aggregate figure; synthesized rows remain\n * `user-confirmed` and are never mislabeled as source-extracted. */\n aggregateEvidence?: 'user-confirmed' | 'authoritative-source';\n /** Separate evidence for assigning standing inventory to this logical\n * section. Aggregate evidence alone cannot prove section placement. */\n sectionAssignmentEvidence?: 'user-confirmed' | 'authoritative-source';\n sectionAssignmentSourceDescription?: string;\n /** Aggregate row synthesis may propose numbering, but persistence requires\n * the applying user/agent to confirm that policy explicitly. */\n numberingEvidence?: 'user-confirmed';\n numberingSourceDescription?: string;\n /** Evidence and deterministic placement contract for synthesized accessible\n * units in this logical section. */\n accessibility?: ReferenceAccessibilitySource;\n}\n\n/** Durable evidence that a visible source-backed section shell intentionally\n * carries no generated sellable inventory in this reference configuration. */\nexport interface ReferenceInventoryExclusionSource {\n assetId: string;\n logicalSectionId: string;\n reason: string;\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n}\n\n/** Open-path stroke semantics. Optional ShapeObject fields retain the legacy\n * round/round/no-ending rendering when absent. */\nexport type ShapeLineCap = 'butt' | 'round' | 'square';\nexport type ShapeLineJoin = 'miter' | 'round' | 'bevel';\nexport type ShapeLineEnding = 'none' | 'arrow';\n\n/** Non-bookable décor: stage, walls, exits. */\nexport interface ShapeObject extends AuthoringObjectState {\n type: 'shape';\n id: string;\n /**\n * Closed area shapes (`rect`/`ellipse`/`polygon`) take a `fill`; open path\n * primitives (`line` = two points, `polyline` = n points) are stroke-only and\n * never filled. All kinds honour the optional `stroke`.\n */\n kind: 'rect' | 'ellipse' | 'polygon' | 'line' | 'polyline';\n label?: string;\n /** For rect/ellipse: bounding box. For a stage polygon: the base (pre-shape) box, so its kind can be regenerated. */\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n /** For polygon/line/polyline. */\n points?: Point[];\n fill?: string;\n /** Optional outline. `width` is in chart units; both fields are required together. */\n stroke?: { color: string; width: number };\n /** Open line/polyline only. Absent fields preserve round/round/no-ending legacy rendering. */\n lineCap?: ShapeLineCap;\n /** Controls corners between open-path segments. */\n lineJoin?: ShapeLineJoin;\n /** Independent open-path start/end decorations. Closed outlines never use these fields. */\n startEnding?: ShapeLineEnding;\n endEnding?: ShapeLineEnding;\n /** Rect only — corner rounding radius in chart units, clamped to half the short side at edit time. */\n cornerRadius?: number;\n /** Whole-shape opacity 0.1–1 (default 1). */\n opacity?: number;\n /** Optional physical extrusion height used by the 3D venue renderer. */\n heightM?: number;\n /**\n * Event configurations in which this architecture is present. Absent means\n * the shape belongs to every configuration; a scoped shape is hidden until\n * one of these ids is active on the chart or 3D scene input.\n */\n eventConfigurationIds?: string[];\n /** Degrees clockwise about the shape's center (default 0). Applied at render time. */\n rotation?: number;\n /**\n * Semantic tag driving special rendering. `'stage'` gets the gradient +\n * prominent uppercase label treatment; a décor landmark role (bar, exit…)\n * gets a quieter label. Loose string to avoid a circular import with\n * stage.ts / decor.ts (see StageKind / DecorRole there).\n */\n role?: string;\n /** For a stage: which `StageKind` its polygon was generated from. */\n stageKind?: string;\n}\n\nexport type RectTableSide = 'top' | 'bottom' | 'left' | 'right';\n\n/** Exact rectangular-table chair distribution. The four keys are deliberately\n * required: zero means that edge has no chair, while the sum is the authored\n * `seatCount`. Numeric chair identity remains `${table.id}:${index}` in the\n * canonical top, bottom, left, right expansion order. */\nexport interface RectTableSeatCounts {\n top: number;\n bottom: number;\n left: number;\n right: number;\n}\n\n/** Seats arranged around a table. Grouped selling is activated only by an\n * event's explicit inventory-model-2 snapshot; model-1 events continue to\n * treat every authored chair as an independent unit. */\nexport interface TableObject extends AuthoringObjectState {\n type: 'table';\n id: string;\n /** e.g. \"T1\" — seat labels are `${label}-${n}`. */\n label: string;\n /** Buyer-facing table name; technical chair/group labels stay stable. */\n displayLabel?: string;\n /** Buyer-facing type word override (seats.io \"Displayed type\"), ≤24 chars.\n * Absent = the default \"Row\"/\"Table\" word. Pure presentation. */\n displayType?: string;\n center: Point;\n shape: 'round' | 'rect';\n /** Seats around the perimeter (round) or along the enabled edges (rect). */\n seatCount: number;\n /** Rect tables: which edges get seats (default ['top','bottom']). */\n sides?: RectTableSide[];\n /**\n * Rect tables only: exact chairs on every edge. Absent preserves the legacy\n * `seatCount` + `sides` round-robin distribution byte-for-byte. When present,\n * all four values are whole numbers >= 0 and their sum equals `seatCount`.\n */\n seatCountsBySide?: RectTableSeatCounts;\n /** Individual-chair semantic overrides. Grouped whole/variable tables cannot\n * author these because their only sellable identity is the table itself. */\n overrides?: SeatOverride[];\n rotation: number;\n /** Round tables. */\n radius?: number;\n /**\n * Round tables: the arc (in degrees) the seats occupy, default 360 (full\n * ring). Below 360 leaves an open side — e.g. a service gap for waiters, a\n * head table facing the room, or clearance against a wall. The opening is\n * centred on the `rotation` direction; seats spread across the rest.\n */\n seatArc?: number;\n /** Rect tables. */\n width?: number;\n height?: number;\n categoryKey: string;\n /** One buyer owns the complete table at exactly `seatCount` guests. */\n bookAsWhole?: boolean;\n /** One buyer owns the complete table and chooses a bounded guest quantity. */\n variableOccupancy?: boolean;\n /** Required inclusive guest bounds when `variableOccupancy` is true. */\n minOccupancy?: number;\n maxOccupancy?: number;\n referenceInventorySource?: ReferenceInventorySource;\n}\n\n/** A booth: one bookable unit rendered as a block (trade shows, VIP boxes). */\nexport interface BoothObject extends AuthoringObjectState {\n type: 'booth';\n id: string;\n /** Stable technical/inventory label. */\n label: string;\n /** Buyer-facing booth name; technical `label` stays stable. */\n displayLabel?: string;\n /** Buyer-facing type word override (seats.io \"Displayed type\"), ≤24 chars.\n * Absent = the default type word. Pure presentation. */\n displayType?: string;\n center: Point;\n width: number;\n height: number;\n rotation: number;\n /**\n * Optional custom outline (closed polygon, absolute chart coordinates) for\n * non-rectangular booths — L-shaped, corner, or island units on expo floors.\n * Absent = the default axis-aligned rectangle described by `width`/`height`/\n * `rotation`. A booth stays exactly ONE atomic sellable unit whatever its\n * outline; `points` is purely geometric. `width`/`height` are retained as the\n * last rectangular size so \"Back to rectangle\" can restore it. When `points`\n * is present, renderers draw the polygon and ignore `rotation`.\n */\n points?: Point[];\n categoryKey: string;\n referenceInventorySource?: ReferenceInventorySource;\n}\n\n/**\n * A named region of the venue (Balcony Left, Floor B…). Sections are outlines\n * only — objects belong to a section spatially (center inside the outline),\n * keeping the document flat (no nesting; simpler for tools and for AI edits).\n * Renderer: far zoom shows section shapes/labels instead of seats; clicking\n * a section zooms into it.\n */\nexport interface SectionObject extends AuthoringObjectState {\n type: 'section';\n id: string;\n label: string;\n /** Buyer-facing section name; logical/id fields remain stable. */\n displayLabel?: string;\n /**\n * Buyer-facing entrance/door hint shown in the picker section card\n * (\"Entrance X\"). ≤40 chars; absent = no entrance line. Pure presentation.\n */\n entrance?: string;\n /**\n * Organizer-supplied view image inherited by buyer inventory whose owning\n * row/table/booth sits in this logical section. Seat and row photos take\n * precedence; multipart components are kept in sync by the shared section\n * metadata operation.\n */\n viewFromSeatUrl?: string;\n viewFromSeatMeta?: SeatViewMetadata;\n /** Section-scoped confidence evidence inherited after seat and row evidence. */\n confidenceEvidence?: SeatConfidenceEvidence;\n labelPresentation?: LabelPresentation;\n /**\n * Stable management/inventory identity shared by disconnected visual\n * components of one logical section. When absent, `id` is the logical id.\n * Each component keeps its own `id` and reference provenance so rendering,\n * editing, measured diffs, and source restoration remain exact.\n */\n logicalSectionId?: string;\n /** Shared semantic Arc/Fan group wrapped by this section. The section id is\n * preserved when the fan parameters are reopened and regenerated. */\n arcFanGroupId?: string;\n /** Closed polygon, chart units. */\n outline: Point[];\n /** Optional true line/arc/cubic boundary. `outline` is a deterministic sample\n * of this path and remains authoritative for membership, validation, and\n * clients that predate curved section rendering. */\n outlinePath?: SectionOutlinePath;\n /** Explicit aisle/cutout polygons excluded from rendering, hit-testing, and membership. */\n holes?: Point[][];\n /** Durable opaque link to the private reference component. Unlike generator\n * provenance, this survives manual geometry edits so a measured diff can\n * report drift and server-owned code can restore the source contour. */\n referenceSource?: {\n assetId: string;\n regionId: string;\n };\n /** Evidence-backed reason this source section remains a visible shell without\n * synthesized sellable inventory (press, closed technical zone, etc.). */\n referenceInventoryExclusion?: ReferenceInventoryExclusionSource;\n /** Deterministic generator provenance for editable reference/parametric shells. */\n geometry?: {\n kind: 'rectangle' | 'tapered' | 'bezier' | 'contour';\n sourceRegionId?: string;\n contourMethod?: 'pixel-edge-loops-rdp' | 'shared-edge-vector-fit-v1';\n simplificationTolerancePx?: number;\n vectorFitErrorPx?: number;\n sharedEdgeCount?: number;\n };\n /** Optional tint override (defaults to a neutral fill / dominant category mix). */\n color?: string;\n /** Zone this section belongs to (id into `ChartDoc.zones`). Far-zoom nav + pricing group. */\n zone?: string;\n /**\n * Tier height. 0 = floor (default). Higher values lift the section in the\n * picker's isometric (\"3D\") view, drawn on extruded side faces. Same field a\n * future multi-floor mode reuses — authored in 2D, never drawn by the user.\n *\n * This is the coarse, back-compat source for {@link height}/{@link rake}: when\n * those are absent, {@link sectionGeometry} derives real geometry from this\n * tier so legacy charts render pixel-identical.\n */\n elevation?: number;\n /**\n * 3D foundations (Phase A, additive — no migration; charts are JSON blobs).\n * Metres the section's **front edge** sits above floor 0 (a balcony/tier floor\n * height). Absent ⇒ derived from the coarse {@link elevation} tier via\n * {@link sectionGeometry}. Deliberately two scalars, not a foundation polygon:\n * front-height + {@link rake} fully determine a rectangular tier's back-height.\n *\n * NOTE: no consumer reads this raw field directly — all callers go through\n * {@link sectionGeometry}. Phase B consumers (iso view lift in\n * `SeatmapRenderer`, per-seat eye-height in the `generatePanorama` 360°\n * generator) are intentionally NOT wired in Phase A. Range 0–120 m.\n */\n height?: number;\n /**\n * Degrees of seating incline within the section (0 = flat; typical stalls\n * 5–15°, steep tiers 25–35°). Absent ⇒ 0. Consumed alongside {@link height}\n * by the future Phase B iso-lift shear and 360° sightline math — never in\n * Phase A. Range 0–45°.\n */\n rake?: number;\n /**\n * Override how 3D builds this section's seating surface. Absent = inferred.\n *\n * The renderer infers structure from the authored geometry — which rows form a\n * stand, where its front is, whether the rows are concentric arcs or straight\n * blocks — and that inference is correct across every shipped template. But\n * inference cannot be right on every chart a customer will ever draw, and when\n * it is wrong the author needs a way to say so without us inventing a second\n * source of truth for the geometry itself.\n *\n * - `flat`: a level deck, whatever the rake says. Terraces, boxes, standing.\n * - `rakedRows`: level ribbons stepping back, the default for a raked stand.\n *\n * Deliberately NOT a \"venue type\" picker: the chart is authored once in 2D,\n * and every 3D-only property that is not derived from that authoring is a\n * source of drift. This is a per-section correction, in the same place and of\n * the same kind as {@link height} and {@link rake}.\n */\n surfaceKind?: 'flat' | 'rakedRows';\n /** Uniform scale about the outline centroid (1 = as drawn). Scales members too. */\n scale?: number;\n /** 0–100: reviewed strength last used to bend member rows toward a common fitted arc. */\n smoothing?: number;\n /**\n * Corner smoothing: the raw clicked polygon this section was drawn from, kept\n * verbatim so {@link cornerSmoothing} stays re-derivable and fully reversible.\n * When present, {@link outlinePath} is a curve computed from THIS polygon (not\n * a reference/blueprint contour); {@link outline} is its deterministic sample.\n * Additive and optional — legacy charts and reference-derived curves omit it.\n */\n sourceOutline?: Point[];\n /**\n * 0–100 corner-smoothing strength applied to {@link sourceOutline} to produce\n * the curved {@link outlinePath}. 0/absent = exact clicked corners. Higher\n * rounds the wide (gently-angled) corners more; sharp corners stay crisp.\n * Coordinate-free, so it round-trips over MCP (`update_sections`).\n */\n cornerSmoothing?: number;\n /**\n * Per-edge curvature, one entry per edge of {@link sourceOutline}, -100..100,\n * 0 = straight. Edge `i` runs from clicked vertex `i` to vertex `i+1`.\n *\n * Positive bows to the LEFT of that edge's own direction. The sign is relative\n * to the edge, never to world axes, which is what lets curvature survive\n * rotate / flip / mirror / radial repeat with no per-operation handling.\n *\n * Coordinate-free like {@link cornerSmoothing}, so it round-trips over MCP and\n * the server never accepts client geometry. Absent/all-zero = straight edges;\n * zeroing it restores the exact clicked polygon.\n */\n edgeCurvature?: number[];\n /** Degrees clockwise about the outline centroid (default 0). Rotates members too. */\n rotation?: number;\n}\n\n/**\n * A group of sections (Lower Bowl, Upper Bowl, Floor…). One concept, three jobs:\n * the farthest-zoom navigation unit, a pricing group, and (Batch 3) a timed-\n * release unit. Kept as a flat list on the doc; sections point back by `zone` id.\n */\nexport interface ZoneDef {\n id: string;\n label: string;\n color?: string;\n /**\n * Authored point this zone faces. Optional only for legacy documents: runtime\n * consumers fall back to the active floor/chart focal, while publication of\n * a zone-mode draft requires every used zone to carry an explicit point.\n */\n focalPoint?: Point;\n}\n\n/**\n * Selection layer — a hit-test/dim filter in the designer, NOT z-order management.\n * Fixed set of four; derived from object type via `layerOf()` (no per-object field yet).\n */\nexport type SelectionLayer = 'interactive' | 'background' | 'foreground' | 'surroundings';\n\n/** Shape roles emitted by the curated venue-landmark palette. Keep this list in\n * lockstep with `DECOR_PRESETS`; the selection-layer unit test fails if either\n * vocabulary changes without an explicit routing decision. `reference-focal`\n * is source-backed venue context rather than an authoring-palette preset. */\nexport const SURROUNDINGS_SHAPE_ROLES = [\n 'reference-focal',\n 'bar',\n 'entrance',\n 'exit',\n 'restroom',\n 'screen',\n 'sound',\n 'concession',\n 'coat',\n 'wall',\n 'rail',\n 'suite',\n 'obstruction',\n] as const;\n\nconst SURROUNDINGS_SHAPE_ROLE_SET: ReadonlySet<string> = new Set(SURROUNDINGS_SHAPE_ROLES);\n\n/** Derive an object's selection layer from its type. */\nexport function layerOf(obj: ChartObject): SelectionLayer {\n switch (obj.type) {\n case 'row':\n case 'table':\n case 'gaArea':\n case 'booth':\n case 'section':\n return 'interactive';\n // Raster décor follows its explicit authored z-layer. Absence retains the\n // legacy Background default; bitmap content is never guessed semantically.\n case 'decorImage':\n return obj.layer === 'foreground' ? 'foreground' : 'background';\n // Source-backed focal geometry and curated venue landmarks help an author\n // orient around the sellable plan. A stage and an ordinary authored shape\n // remain Background even when they carry an arbitrary/custom role.\n case 'shape':\n return obj.role && SURROUNDINGS_SHAPE_ROLE_SET.has(obj.role)\n ? 'surroundings'\n : 'background';\n // Free text — including semantic icon text — is background furniture.\n case 'text':\n return 'background';\n default:\n return 'interactive';\n }\n}\n\n/** Free-standing text on the chart (aisle names, door labels…). */\nexport type TextAlignment = 'left' | 'center' | 'right';\n\nexport interface TextBackground {\n color: string;\n /** Space inside the background in chart units; default 6. */\n padding?: number;\n /** Rounded background corners in chart units; default 4. */\n cornerRadius?: number;\n /** Background-only opacity multiplied by the text object's overall opacity. */\n opacity?: number;\n}\n\nexport interface TextObject extends AuthoringObjectState {\n type: 'text';\n id: string;\n /** Persisted provenance for objects created from the venue-icon palette. */\n semanticKind?: 'icon';\n /**\n * Registry key for a vector wayfinding icon (see src/core/icons.ts). Present\n * on modern icon placements; the object then renders as a single-color vector\n * Path instead of `text`. Absent on legacy emoji icons, which keep rendering\n * `text` through the shared glyph path — old charts are never rewritten.\n */\n iconKey?: string;\n text: string;\n position: Point;\n fontSize: number;\n /** Optional CSS family stack for this annotation; absent inherits ChartTheme.fontFamily. */\n fontFamily?: string;\n rotation: number;\n color?: string;\n /** Render weight (default false). Maps to Konva fontStyle bold. */\n bold?: boolean;\n /** Render slant (default false). Maps to Konva fontStyle italic. */\n italic?: boolean;\n /** Fixed text-box width in chart units. Absent keeps legacy auto width. */\n width?: number;\n /** Horizontal alignment inside a fixed-width text box. */\n align?: TextAlignment;\n /** Multiplier over the font's natural line height; default 1.2. */\n lineHeight?: number;\n /** Added chart units between glyphs; default 0. */\n letterSpacing?: number;\n /** Underline decoration. Absent is off. */\n underline?: boolean;\n /** Whole text-object opacity; default 1. */\n opacity?: number;\n /** Optional padded background painted behind the exact text box. */\n background?: TextBackground;\n}\n\n/**\n * A raster/vector decor graphic drawn IN the chart, beneath the seats and\n * sections (ice rink, basketball court, stage art, pitch markings). Purely\n * visual venue context — never bookable, never hit-tested, so it never steals a\n * seat click. `href` is a self-contained data URL (image or SVG) produced by the\n * same client-side downscale used for row photos, so it travels with the doc and\n * caches as a single bitmap blit (zero per-frame cost). Placed by top-left\n * (x,y) + size, rotated about its centre — the same handles a shape rect uses.\n */\nexport interface DecorImageObject extends AuthoringObjectState {\n type: 'decorImage';\n id: string;\n /** Image or SVG data URL. */\n href: string;\n x: number;\n y: number;\n width: number;\n height: number;\n /** Degrees clockwise about the image centre (default 0). */\n rotation?: number;\n /** 0–1 (default 1). Multiplied by any chart-theme dimming at render time. */\n opacity?: number;\n /**\n * Z-layer relative to the interactive seat layer. `background` (default)\n * draws beneath the seats/sections; `foreground` draws above them (a roof\n * canopy, an overlay graphic). Absent = background — no migration needed.\n */\n layer?: 'background' | 'foreground';\n /** Optional caption for the designer inspector / accessibility (not drawn). */\n label?: string;\n}\n\nexport type ChartObject =\n | RowObject\n | GAAreaObject\n | ShapeObject\n | TableObject\n | BoothObject\n | TextObject\n | SectionObject\n | DecorImageObject;\n\n/**\n * One floor / level of a multi-floor venue (Batch 5). Each floor owns its own\n * geometry, stage focal point, and trace image; categories/zones/tiers stay\n * chart-global (one event, one inventory). A single-floor chart has NO `floors`\n * — its `objects[]` is the whole venue — so all existing charts are untouched.\n */\nexport interface Floor {\n id: string;\n name: string;\n /**\n * Absolute physical deck height in metres above the venue/stage datum.\n * Optional for backwards compatibility; an absent value resolves to ground\n * level (0 m). Section tiers and rakes are separate, section-local metadata.\n * Range 0–120 m.\n */\n baseHeightM?: number;\n objects: ChartObject[];\n focalPoint: Point;\n /**\n * Private organizer trace/calibration layer. This is authoring evidence and\n * must never be served to, or rendered by, a buyer surface.\n */\n referenceImage?: ChartReferenceImage;\n /**\n * Buyer-visible aesthetic background. Canonical documents store URL-only\n * images here. Historical `assetId` values are interpreted as a trace layer\n * by the background compatibility helpers.\n */\n backgroundImage?: ChartDoc['backgroundImage'];\n /**\n * Per-floor override of the venue-wide view-from-seat fallback: a single\n * organizer 360° (or wide photo) inherited by every seat on THIS floor that\n * has no closer row/section photo. Falls below section photos and above the\n * chart-level `viewFromSeatUrl`. Absent ⇒ the venue default (then a synthetic\n * panorama) applies.\n */\n viewFromSeatUrl?: string;\n viewFromSeatMeta?: SeatViewMetadata;\n /** Floor-scoped confidence evidence inherited before the venue default. */\n confidenceEvidence?: SeatConfidenceEvidence;\n}\n\nexport interface ReferenceCalibration {\n type: 'two-point';\n /** Points in immutable source-image pixels, selected by a human or trusted detector. */\n sourceA: Point;\n sourceB: Point;\n /** Verified real-world distance between the two source points. */\n distance: number;\n unit: 'm' | 'ft' | 'chart-unit';\n /** Derived and stored for deterministic geometry compilers. */\n pixelsPerUnit: number;\n}\n\n/**\n * A single human-placed seat probe: the author clicks one seat on the reference\n * image and the server reads the surrounding seat lattice from it.\n *\n * COORDINATE-POLICY CARVE-OUT (owner decision 2026-07-21). The reference\n * blueprint pipeline runs `coordinatePolicy: 'opaque-region-ids-only'` — the\n * server is the sole source of chart coordinates and MCP clients select opaque\n * `reg_*` ids, never points. This type is a deliberate, narrow exception on the\n * same grounds as `ReferenceCalibration`: the point is placed by a human in the\n * designer canvas, not proposed by a model.\n *\n * Therefore this is DESIGNER-ONLY and is intentionally NOT exposed over MCP.\n * That is an accepted, documented exception to the MCP-parity rule — the\n * server-is-sole-source guarantee for model-driven edits is worth more than\n * parity here. Do not \"fix\" it by adding a seed point to an MCP tool schema.\n */\nexport interface ReferenceSeatSeed {\n /** Seed centre in immutable source-image pixels (never chart coordinates). */\n source: Point;\n /** Half-width of the author's sizing ring, in source pixels — the \"this is how\n * big one seat is\" hint that replaces seats.io's zoom-until-it-matches step. */\n radius: number;\n /** Whether `radius` was fitted from image pixels or set by hand. Detection\n * weights an author-set radius more heavily than one we guessed. */\n origin: 'auto-fit' | 'manual';\n}\n\n/** One detected row in a scan proposal — a straight seat run in CHART\n * coordinates (the server maps source pixels through referencePixelToChart;\n * clients never see source-pixel geometry back). */\nexport interface ReferenceScanRowProposal {\n start: Point;\n end: Point;\n seatCount: number;\n}\n\n/** Detected rows attributed to one compiled section (or unattributed when the\n * lattice extends outside every compiled polygon). */\nexport interface ReferenceScanSectionProposal {\n /** Id of the compiled SectionObject the rows landed in; null = unattributed. */\n sectionId: string | null;\n name: string;\n rows: ReferenceScanRowProposal[];\n seatCount: number;\n /** 0..1 — how well this section's lattice agreed with the probe's pitch. */\n confidence: number;\n /** Index of the seed (multi-probe) whose pitch produced these rows. */\n seedIndex: number;\n}\n\n/** Server response for an in-canvas reference scan. A PROPOSAL — nothing is\n * committed until the author applies it in the designer (chartOps + undo). */\nexport interface ReferenceScanProposal {\n assetId: string;\n /** Measured seat diameter / centre-to-centre pitch, in chart units. */\n seatDiameter: number;\n seatPitch: number;\n totalSeats: number;\n totalRows: number;\n sections: ReferenceScanSectionProposal[];\n}\n\n/**\n * Human-only Magic Trace request. `source` is one click in immutable\n * source-image pixels; it is accepted only by the browser Designer HTTP\n * surfaces and must never be added to an MCP schema.\n */\nexport interface ReferenceSectionTraceInput {\n assetId: string;\n floorId?: string;\n expectedUpdatedAt: number;\n source: Point;\n}\n\n/**\n * Whole-reference Magic Trace request. Unlike the one-region request this\n * carries no source coordinates: the server returns every persisted closed\n * region as an ID-free proposal for explicit browser review.\n */\nexport interface ReferenceSectionTraceBatchInput {\n assetId: string;\n floorId?: string;\n expectedUpdatedAt: number;\n}\n\n/**\n * Read-only Magic Trace result. This is deliberately not a SectionObject:\n * there is no object id or label until the author accepts the proposal through\n * the normal Designer command/undo boundary.\n *\n * All geometry is in chart coordinates. Source pixels, private contour\n * vertices and reference bounds are never returned.\n */\nexport interface ReferenceSectionTraceProposal {\n assetId: string;\n floorId: string;\n outline: Point[];\n /** Required exact fitted line/arc/cubic boundary. */\n outlinePath: SectionOutlinePath;\n /** Only structural source voids; printed labels/icons are filtered out. */\n holes: Point[][];\n /**\n * Optional, and the tracer deliberately omits it. The colour a section is\n * drawn in belongs to the CHART, not to the screenshot it was traced from —\n * see the note in `referenceSectionTrace.ts`. The sampled value lives on the\n * region row as `sampled_color`. Consumers fall back to zone/theme colour.\n */\n color?: string;\n referenceSource: {\n assetId: string;\n regionId: string;\n };\n geometry: {\n kind: 'contour';\n sourceRegionId: string;\n contourMethod: 'shared-edge-vector-fit-v1';\n simplificationTolerancePx: number;\n vectorFitErrorPx: number;\n sharedEdgeCount: number;\n };\n provenance: {\n regionId: string;\n analysisVersion: number;\n vectorTopologyVersion: number;\n selection: 'human-source-pixel-seed' | 'human-bulk-reference-review';\n registration: 'persisted-reference-registration-v1';\n };\n /** Coordinate-free evidence suitable for an author-facing confirmation. */\n diagnostics: {\n regionMarker: string;\n structuralHoleCount: number;\n fittedComponentCount: number;\n sharedBoundaryCount: number;\n maximumAllowedVectorErrorPx: number;\n measuredVectorErrorPx: number;\n /** Source component footprint, as a percentage of the analyzed image. */\n sourceAreaPercent?: number;\n /**\n * The name already printed on the plan for this region, when one has been\n * read. The review proposes it instead of a generated number — a venue plan\n * names its own sections, and retyping forty of them is work the author\n * should never have to do. Absent means fall back to numbering.\n */\n visibleLabel?: string;\n };\n}\n\n/** Read-only whole-reference proposal. The author may approve each proposal or\n * all proposals; the Designer then materializes the accepted set atomically. */\nexport interface ReferenceSectionTraceBatchProposal {\n assetId: string;\n floorId: string;\n proposals: ReferenceSectionTraceProposal[];\n diagnostics: {\n analyzedRegionCount: number;\n proposalCount: number;\n alreadyTracedCount: number;\n withinToleranceCount: number;\n /** Regions that could not be traced into a usable boundary and were left\n * out. One unusable region must not cost the author the rest of the plan. */\n unusableRegionCount?: number;\n };\n}\n\n/** Coordinate-free physical scale derived by server code from a confirmed\n * semantic feature. Unlike manual two-point calibration, no source points pass\n * through an MCP client or language model. */\nexport interface ReferenceDerivedScale {\n method: 'confirmed-focal-axis-v1';\n feature: 'focal-long-axis' | 'focal-short-axis';\n distance: number;\n unit: 'm' | 'ft';\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n chartUnitsPerUnit: number;\n}\n\nexport interface ChartReferenceImage {\n /** Stable private reference asset. New cloud-authored charts use this. */\n assetId?: string;\n /** Legacy/self-contained source. Optional when assetId is present. */\n url?: string;\n center: Point;\n /** Rendered width in chart units (height follows the cropped image aspect). */\n width: number;\n opacity: number;\n rotation?: number;\n visible?: boolean;\n layer?: 'below' | 'above';\n locked?: boolean;\n /** Normalized source crop; defaults to the full image. */\n crop?: { x: number; y: number; width: number; height: number };\n calibration?: ReferenceCalibration;\n /** Server-derived semantic calibration without source-image coordinates. */\n derivedScale?: ReferenceDerivedScale;\n}\n\nexport interface ChartDoc {\n version: 1;\n name: string;\n venueType: 'SIMPLE' | 'MIXED';\n /** The stage / point every seat looks at. Anchors seat-view + sightlines.\n * Multi-floor: mirrors floor 0; each floor also carries its own focalPoint. */\n focalPoint: Point;\n /** Active event layout used by configuration-scoped 3D architecture. */\n eventConfigurationId?: string;\n categories: Category[];\n /** Section groupings for far-zoom navigation + pricing (optional; sections reference by id). */\n zones?: ZoneDef[];\n /** Multi-floor venues (Batch 5): present ⇒ floors[] is the source of truth;\n * absent ⇒ single-floor and `objects` below is the whole chart. `objects`\n * is kept mirroring floor 0 so single-floor readers never branch. */\n floors?: Floor[];\n objects: ChartObject[];\n /**\n * Private floor-plan source used for tracing, calibration, scanning and\n * reference-backed generation. Buyer projections always remove this field.\n */\n referenceImage?: ChartReferenceImage;\n /**\n * Buyer-visible aesthetic background. Canonical values are URL-only.\n * Compatibility: a historical value containing `assetId` is trace-only and\n * is never rendered or exposed to buyers.\n */\n backgroundImage?: ChartReferenceImage;\n /** Brand/venue theming (colors); categories carry their own colors separately. */\n theme?: ChartTheme;\n /** Parametric-template provenance: present ⇒ the chart came from a capacity-\n * adjustable template family, and the designer offers a capacity control that\n * regenerates it at a new target seat count (Batch 4 \"curated singles + resize\"). */\n template?: { family: string; targetSeats: number };\n /**\n * Venue-wide view-from-seat fallback: a single organizer 360° (or wide photo)\n * inherited by every seat that has no closer row/section/floor photo. Absent ⇒\n * the picker generates a synthetic panorama.\n */\n viewFromSeatUrl?: string;\n viewFromSeatMeta?: SeatViewMetadata;\n /** Venue-scoped confidence evidence. It does not require panorama media. */\n confidenceEvidence?: SeatConfidenceEvidence;\n}\n\n// ---------------------------------------------------------------------------\n// Expanded (render-time) model — produced by layout.ts, consumed by the engine\n// ---------------------------------------------------------------------------\n\nexport interface ExpandedSeat {\n /** Stable id: `${rowId}:${index}` */\n id: string;\n /** Public label: `${rowLabel}-${seatNumber}` */\n label: string;\n /** Buyer-facing copy; absent on legacy charts, where `label` is displayed. */\n displayLabel?: string;\n x: number;\n y: number;\n rowId: string;\n /** Owning logical section and navigation zone, resolved once at expand time. */\n sectionId?: string;\n zoneId?: string;\n /** Zone focal when authored, otherwise the active floor/chart legacy fallback. */\n focalPoint?: Point;\n /** Buyer-facing segmented-row identity. `rowId` stays the physical owner id. */\n logicalRowId?: string;\n /**\n * Seat order inside the logical row. A deliberate missing integer is inserted\n * at every aisle boundary, so numerical adjacency cannot bridge a gap.\n */\n logicalSeatIndex?: number;\n categoryKey: string;\n /** 'booth' units render as blocks (dimensions looked up via rowId = booth id). */\n kind?: 'seat' | 'booth';\n /** True when the seat has any accessibility accommodation — renderer rings/dims these. */\n accessible?: boolean;\n /** Specific accessibility accommodations (absent = none) — picker badges/filters these. */\n accessibility?: AccessibilityType[];\n /** Physical wheelchair provision resolved from the seat override. */\n wheelchairSpaceType?: 'seat-present' | 'no-seat';\n commercial?: SeatCommercialAttributes;\n /** Organizer-supplied view-from-seat image (inherited from the row). */\n viewUrl?: string;\n /** Calibration/truth inherited from the same scope that supplied `viewUrl`. */\n viewMeta?: SeatViewMetadata;\n /** Closest configuration-valid evidence plus its engine-resolved coverage. */\n confidenceEvidence?: ResolvedSeatConfidenceEvidence;\n /** Per-seat label size/color override; absent = inherit the row/theme default. */\n labelStyle?: LabelStyle;\n /**\n * Real-world eye height in metres above the focal/stage datum, resolved at\n * expand time from the owning section's `{height, rake}` + drawn depth (Phase B2).\n * Feeds the auto-360° generator's stage-pitch math. Absent ⇒ flat seated eye\n * height (legacy / seats in no section) — so old charts stay pixel-identical.\n */\n eyeHeightM?: number;\n}\n\nexport type SeatStatus = 'free' | 'held' | 'booked' | 'not_for_sale';\n\n/** Buyer canvas projection. Perspective is a view-only projected-2.5D lane. */\nexport type RendererViewMode = 'flat' | 'isometric' | 'perspective';\n\n// ---------------------------------------------------------------------------\n// Renderer engine public API — implemented in src/engine/SeatmapRenderer.ts\n// ---------------------------------------------------------------------------\n\nexport interface RendererCallbacks {\n onSelect?: (seat: ExpandedSeat) => void;\n onDeselect?: (seat: ExpandedSeat) => void;\n /** Buyer tried to add a seat after the active selection cap was reached. */\n onSelectionLimit?: (maxSelection: number) => void;\n /** seat is null when the pointer leaves any seat. */\n onHover?: (seat: ExpandedSeat | null) => void;\n /** Keyboard focus moved to a seat (arrow-key navigation) — for screen-reader announcements. */\n onFocusSeat?: (seat: ExpandedSeat | null) => void;\n /** Called ~1×/sec with the measured frames-per-second. */\n onFps?: (fps: number) => void;\n /** Fired when a GA area is clicked (quantity picking is UI-side). */\n onGAClick?: (areaId: string) => void;\n /**\n * Fired when a tap lands on a section outline while seats are NOT the active\n * rung (i.e. zoomed out, section/zone LOD). The host glides in + shows a\n * section-summary card instead of trying to select a 4px seat (Slice 5).\n */\n onSectionTap?: (sectionId: string) => void;\n /**\n * Fired when a seat/deck is tapped in the 3D all-floors stacked overview — the\n * host drops back to the flat 2D map on that floor (\"tap a deck to enter\").\n */\n onDeckTap?: (floorId: string) => void;\n /** Fired after any pan/zoom/resize settles — re-anchor screen-space overlays. */\n onViewChange?: () => void;\n /**\n * Organizer manage-mode only (`manageMode` + `marqueeSelect`): fired on\n * pointer-UP after a rubber-band marquee drag, or a ⌘A/Escape bulk shortcut,\n * with the FULL current selection (selectable seats only). The host toolbar\n * reads this to drive bulk block/unblock. Never fires when manageMode is off.\n */\n onMarquee?: (seats: ExpandedSeat[]) => void;\n}\n\n/** Far-zoom level-of-detail rung: whole zones → section blocks → individual seats. */\nexport type LodRung = 'zones' | 'sections' | 'seats';\n\nexport interface RendererOptions extends RendererCallbacks {\n /**\n * Internal fixed-output mode used by the free maker's PNG/PDF pipeline.\n * It disables interaction, ResizeObserver, animation/FPS work and overview\n * bitmap caching while keeping the ordinary buyer renderer unchanged.\n */\n exportMode?: boolean;\n /** Max seats selectable at once (default 10). */\n maxSelection?: number;\n /** Only these statuses are clickable (default ['free']). */\n selectableStatuses?: SeatStatus[];\n /**\n * Opt-in host behaviour flag — the renderer's own click/selection logic is\n * unchanged (it still selects + fires `onSelect` immediately, so the seat\n * highlights right away). When true, the host (e.g. PublicEventPage) treats\n * that `onSelect` as a pending candidate and shows a confirm card instead of\n * pushing straight into the cart; `deselect([seat.id])` on Cancel un-highlights.\n */\n confirmSelection?: boolean;\n /** ISO 4217 currency for on-map prices (\"FROM …\"); defaults to money.DEFAULT_CURRENCY.\n * Locale for grouping/symbol placement comes from the active i18n locale. */\n currency?: string;\n /**\n * Organizer manage surface (SDK SeatManager). Opt-in — enables the manage-mode\n * gestures (marquee, ⌘A/Escape) and the bulk-selection helpers. Buyer pan /\n * pinch / tap and every existing code path are byte-identical when this is\n * false (every manage branch is gated on it). Default false.\n */\n manageMode?: boolean;\n /**\n * When `manageMode` is on, a mouse/pen primary-button drag at the seats rung\n * draws a rubber-band marquee that bulk-selects the seats it covers (emitting\n * `onMarquee` on pointer-up) instead of panning. Touch keeps single-finger\n * pan (pinch to zoom); a middle-button drag pans with a mouse. Disabled below\n * the seats rung (zoom in first). No effect unless `manageMode` is also set.\n */\n marqueeSelect?: boolean;\n}\n\nexport type RenderedLabelHiddenReason =\n | 'below-minimum-size'\n | 'outside-viewport'\n | 'dimmed-or-unavailable'\n | 'clutter-or-fit'\n | 'renderer-hidden';\n\n/** Browser-renderer evidence used by visual QA and catalog release gates. */\nexport interface RenderedBookableLabelEvidence {\n seatId: string;\n label: string;\n kind: 'seat' | 'booth';\n /** Painted inventory silhouette. Empty wheelchair bays are deliberately\n * square, while physical seats retain the ordinary circular marker. */\n markerShape: 'circle' | 'square' | 'booth';\n /** Physical wheelchair provision represented by this inventory unit. */\n wheelchairSpaceType?: 'seat-present' | 'no-seat';\n categoryKey: string;\n sectionId?: string;\n zoneId?: string;\n status: SeatStatus;\n selected: boolean;\n visible: boolean;\n renderedFontPx: number;\n fill: string;\n ink: string;\n opacity: number;\n /** Buyer-visible accessibility glyph evidence. Filter emphasis uses a\n * screen-space minimum so wheelchair provision remains recognizable at fit. */\n accessibilityMarker?: {\n glyphVisible: boolean;\n glyphWidthPx: number;\n emphasizedByFilter: boolean;\n };\n /** Direct Konva shape bounds and the production near-miss rescue combined. */\n pointerTarget: {\n active: boolean;\n directWidthPx: number;\n directHeightPx: number;\n effectiveMinimumPx: number;\n };\n /** Centre of the painted unit, even when its text is intentionally hidden. */\n screenCenter: { x: number; y: number };\n screenBox?: { x: number; y: number; width: number; height: number };\n hiddenReason?: RenderedLabelHiddenReason;\n}\n\nexport interface RenderedHierarchyLabelEvidence {\n id: string;\n kind: 'section' | 'zone';\n role: 'name' | 'availability' | 'price';\n label: string;\n visible: boolean;\n renderedFontPx: number;\n opacity: number;\n fill: string;\n ink: string;\n /** Independent geometric containment check for section-owned text. */\n fitsContainer?: boolean;\n screenBox?: { x: number; y: number; width: number; height: number };\n}\n\nexport interface RenderedFreeTextEvidence {\n objectId: string;\n kind: 'free-text' | 'stage' | 'table' | 'decor' | 'ga-label' | 'ga-capacity';\n text: string;\n visible: boolean;\n renderedFontPx: number;\n ink: string;\n background: string;\n opacity: number;\n screenBox?: { x: number; y: number; width: number; height: number };\n hiddenReason?: 'below-minimum-size' | 'outside-viewport' | 'renderer-hidden';\n}\n\nexport interface RenderedGAAreaEvidence {\n areaId: string;\n label: string;\n capacity: number;\n categoryKey: string;\n /** Owning logical section when the rendered GA surface is section-contained. */\n sectionId?: string;\n visible: boolean;\n interactive: boolean;\n opacity: number;\n fill: string;\n effectiveBackground: string;\n screenBox?: { x: number; y: number; width: number; height: number };\n}\n\nexport interface RendererQualityEvidence {\n viewport: { width: number; height: number };\n /** Runtime projection actually used for the pixels and hit graph below. */\n projection: RendererViewMode;\n /** Phase-C proof metadata. Present only in the projected-2.5D lane. */\n perspective?: {\n model: 'pinhole-exact-seat-anchors';\n sectionSurfaceModel: 'tangent-plane';\n exactSeatAnchorCount: number;\n depthSorted: true;\n };\n canvasBackground: string;\n effectiveScale: number;\n rung: LodRung;\n minimumVisibleLabelPx: number;\n totalLabelledBookableUnits: number;\n visibleLabels: number;\n hiddenLabels: number;\n /** Seats/table-seats/booths plus the full GA capacity. */\n totalBookableUnits: number;\n selectionRingSeatIds: string[];\n selectionRingColor: string;\n focusedSectionId: string | null;\n focusBackdropVisible: boolean;\n categoryFilterKeys: string[] | null;\n /** Exact scene-graph proof for the clean section-first overview contract. */\n overviewStyle: {\n visibleSectionShells: number;\n categoryPaintedSectionShells: number;\n visibleCategoryDetailOutlines: number;\n visibleSectionRowHints: number;\n visibleSectionAvailabilityLabels: number;\n visibleSectionGADetails: number;\n };\n labels: RenderedBookableLabelEvidence[];\n gaAreas: RenderedGAAreaEvidence[];\n hierarchyLabels: RenderedHierarchyLabelEvidence[];\n freeTextLabels: RenderedFreeTextEvidence[];\n}\n\nexport interface ISeatmapRenderer {\n /** Replace the chart. Resets selection and statuses, zooms to fit.\n * `opts.floorId` picks which floor to render on a multi-floor chart (Batch 5). */\n setChart(doc: ChartDoc, opts?: { floorId?: string }): void;\n /** Bulk status update; re-renders affected seats only. */\n setStatus(seatIds: string[], status: SeatStatus): void;\n /**\n * Mark the active buyer's own held seats. They remain server-status `held`,\n * but render with the buyer selection treatment instead of the anonymous\n * unavailable treatment used for another buyer's hold.\n */\n setOwnedHold?(seatIds: string[] | null): void;\n /**\n * Mark one selected seat as the buyer's pending confirmation candidate.\n * The candidate receives a strong focus halo while unrelated seats recede;\n * pass null after Select/Cancel. This is visual only and never mutates the\n * renderer selection.\n */\n setSelectionFocus?(seatId: string | null): void;\n /**\n * SYNCHRONOUS repaint that bypasses requestAnimationFrame. Konva's batchDraw()\n * (used by setStatus and friends) schedules the actual paint on the next rAF\n * tick, which Chrome throttles/pauses on hidden, backgrounded, or occluded\n * tabs — so a seat-status delta updates the scene graph but the pixels never\n * change until the tab is foregrounded again. forceDraw() paints the affected\n * layers immediately (Layer.draw() is synchronous) and flushes any pending\n * cache-debounce, so a caller (visibilitychange catch-up, or an opted-in\n * always-live board) can guarantee the canvas reflects current state\n * regardless of tab visibility. No-op difference in the foreground.\n */\n forceDraw(): void;\n getStatus(seatId: string): SeatStatus;\n getSelection(): ExpandedSeat[];\n clearSelection(): void;\n /** Update the buyer selection cap without rebuilding the chart or camera. */\n setMaxSelection?(maxSelection: number): void;\n /**\n * Programmatically restore free seats (for example an Undo action). Added\n * seats respect the active cap and do not reopen a confirmation popover.\n */\n select?(seatIds: string[]): ExpandedSeat[];\n /**\n * Dynamically update organizer-only interaction without rebuilding the\n * renderer. No buyer surface calls this; every behavior remains gated by\n * `manageMode` exactly as it is at construction time.\n */\n setManageInteraction?(options: {\n manageMode: boolean;\n marqueeSelect: boolean;\n selectableStatuses: SeatStatus[];\n maxSelection?: number;\n }): void;\n /** Organizer-only section heat overlay. Values are normalized 0..1. */\n setSectionHeat?(scores: Record<string, number> | null): void;\n /**\n * Manage-mode bulk selection helpers (no-op / empty unless `manageMode`).\n * They select the matching SELECTABLE seats (respecting `selectableStatuses`\n * + closed sections), union with the current selection, and return the seats\n * they added — the SDK SeatManager expands category/row/section picks to\n * labels and drives one batched block/unblock from them.\n */\n selectAllSelectable?(): ExpandedSeat[];\n selectByLabels?(labels: string[]): ExpandedSeat[];\n /** Exact-render QA only: select one server-chosen unit without label ambiguity. */\n setEvidenceSelection?(seatId: string): boolean;\n /** Selectable seats belonging to a section OR zone id (no selection side-effect). */\n getSelectableInSection?(sectionId: string): ExpandedSeat[];\n /** Programmatic deselect of specific seats (e.g. chip × in the cart). */\n deselect(seatIds: string[]): void;\n /**\n * Brief attention pulse on a seat (a ring that expands + fades once) — used to\n * signal live activity, e.g. a seat \"just taken\" by another buyer via a WS\n * delta. Purely visual; no state change. `color` overrides the default.\n */\n flashSeat(seatId: string, color?: string): void;\n /**\n * Brief organizer attention pulse around a whole section. This is a visual\n * overlay only: it never changes section geometry, hit targets, selection, or\n * the active camera. Useful for grouped realtime operations at venue overview.\n */\n flashSection?(sectionId: string, color?: string): void;\n zoomToFit(): void;\n /** Zoom in one step about the viewport center, clamped to the usual zoom bounds. */\n zoomIn(): void;\n /** Zoom out one step about the viewport center, clamped to the usual zoom bounds. */\n zoomOut(): void;\n /** Individually status-managed seats/table-seats/booths; excludes GA capacity. */\n seatCount(): number;\n /** Seats/table-seats/booths plus the full capacity of rendered GA areas. */\n bookableCount(): number;\n /**\n * Maps a chart-space point (or a seat, by its x/y) to container-relative\n * screen pixels, using the current stage scale/position. Lets host UI anchor\n * DOM overlays (confirm card, tooltip) over a live seat and re-anchor them\n * on `onViewChange`.\n */\n worldToScreen(point: Point): { x: number; y: number };\n /** When on, dim non-accessible free seats so accessible seats stand out. */\n setAccessibleFilter(on: boolean): void;\n /**\n * Dim free seats that lack ANY of these accessibility types. `null` clears the\n * filter; `[]` means \"any accessible seat\" (same as setAccessibleFilter(true)).\n */\n setAccessibilityFilter(types: AccessibilityType[] | null): void;\n /** Legend hover-highlight: dim free seats of other categories (null clears). */\n setCategoryHighlight?(key: string | null): void;\n /** Price-band filter (F4): dim free seats whose category is NOT in `keys`\n * (null clears). The widget resolves which categories fall in the band. */\n setCategoryFilter?(keys: string[] | null): void;\n /** Smoothly frame the currently available seats in these categories. `null`\n * returns to the full chart. Used after an explicit buyer price-filter action. */\n focusCategories?(keys: string[] | null): void;\n /** Dim the seats of these section/zone ids (organizer manager: held-back inventory). */\n setDimmedSections?(ids: string[] | null): void;\n /**\n * Phase 2 event-level section states: mark these section/zone ids `closed` —\n * flat grey block, seats greyed + not pickable, section stays rendered.\n * `null`/empty clears. (Distinct from the buyer's applyHidden seat-strip.)\n */\n setClosedSections?(ids: string[] | null): void;\n /**\n * AXS section-focus: dim + desaturate every other section, draw a calm backdrop\n * behind this section, and glide the camera to frame it. Seat-picking is gated\n * until seats are large enough on screen (≥ LABEL_SCALE). Slice 5 / Phase 2 §4.\n */\n focusSection?(id: string): void;\n /** Clear an AXS section focus (restore full-bowl brightness + drop backdrop). */\n clearSectionFocus?(): void;\n /** The currently AXS-focused section id, or null. */\n getFocusedSection?(): string | null;\n /** World-space rect currently visible in the viewport (minimap viewport frame). */\n getVisibleWorldRect?(): { x: number; y: number; width: number; height: number };\n /** Axis-aligned world bounds of all seats + section outlines (minimap frame). */\n getWorldBounds?(): { x: number; y: number; width: number; height: number };\n /**\n * Colorblind-safe mode: category hues switch to an Okabe-Ito palette and\n * booked seats render hollow (a non-color cue), so seat state never relies\n * on hue alone. Off (the default) renders exactly as before.\n */\n setColorblindSafe?(on: boolean): void;\n /**\n * Switch the projection. `'flat'` = normal top-down; `'isometric'` = the\n * legacy affine preview; `'perspective'` = projected 2.5D with exact pinhole\n * seat anchors/native hit shapes and bounded per-section tangent surfaces.\n * Purely visual — the chart is authored flat.\n */\n setViewMode?(mode: RendererViewMode): void;\n /** Current projection (defaults to 'flat' when unimplemented). */\n getViewMode?(): RendererViewMode;\n /** Multi-floor (Batch 5): switch the shown floor; list floors; read the active id. */\n setActiveFloor?(floorId: string): void;\n getFloors?(): { id: string; name: string }[];\n getActiveFloorId?(): string;\n /** Render all floors stacked (3D overview) vs the active floor. No-op single-floor. */\n setStacked?(on: boolean): void;\n isStacked?(): boolean;\n /**\n * Section id whose outline contains a container-relative screen point (or null).\n * Feeds the far-zoom \"tap a section to zoom in\" flow (Slice 5).\n */\n sectionAt?(clientPoint: Point): string | null;\n /** Seat ids belonging to a section — for the section-summary card (Slice 5). */\n sectionMembers?(id: string): string[];\n /**\n * Smoothly glide (pan+zoom) the camera to frame a section (by id) or a world-\n * space bounds rect over a calm easeInOutCubic glide. `prefers-reduced-motion` snaps.\n * A pointer-down (grab/pan) cancels an in-flight glide. Slice 5 \"glide in\".\n */\n focusRegion?(\n target: string | { x: number; y: number; width: number; height: number },\n opts?: { animate?: boolean; minScale?: number; durationMs?: number },\n ): void;\n /** Current LOD rung derived from zoom (for the ZONES/SECTIONS/SEATS pill). */\n getRung?(): LodRung;\n /** Jump the camera to a rung's zoom band, centred on the chart (glided). */\n setRung?(rung: LodRung): void;\n /** Read actual browser-rendered label visibility, size, fill, ink and state.\n * Pure diagnostic: it never changes chart or renderer state. */\n getRenderedQualityEvidence(): RendererQualityEvidence;\n destroy(): void;\n}\n\n/** localStorage key the Designer writes and the Picker reads. */\nexport const CHART_STORAGE_KEY = 'seatmap.chart';\n","/**\n * Real-world scale primitives — the ONE place the app fixes chart-unit ↔ metre ↔\n * renderer-world scale, and the section 3D-geometry resolver that rides on it.\n *\n * Everything that converts between chart units, metres, and renderer \"world\"\n * units derives from {@link METRES_PER_CHART_UNIT}. Renderer world units and\n * chart units are 1:1, so metres → world is exactly {@link CHART_UNITS_PER_METRE}\n * (the single m→world conversion constant Phase B consumers use).\n *\n * This is a leaf module (types only) so both `layout.ts` and `sections.ts` can\n * import it without the two forming an import cycle.\n */\nimport type { SectionObject } from './types';\n\n/**\n * Chart units → metres. seatSpacing 24 ≈ 0.55 m (a real seat pitch). This single\n * constant anchors every real-world scale in the app; `generatePanorama` and the\n * iso lift both derive from it instead of re-declaring their own 0.55/24.\n */\nexport const METRES_PER_CHART_UNIT = 0.55 / 24;\n\n/**\n * Metres → chart units, i.e. metres → renderer world units (world == chart units\n * in the engine). THE single m→world conversion constant: the iso view lifts an\n * elevated section by `sectionGeometry(section).height × CHART_UNITS_PER_METRE`.\n */\nexport const CHART_UNITS_PER_METRE = 1 / METRES_PER_CHART_UNIT;\n\n/**\n * Renderer world units a section lifts per {@link SectionObject.elevation} tier in\n * the legacy iso view (mirror of `SeatmapRenderer`'s old `LIFT_PER_STEP`). Kept so\n * the tier→metres fallback below reproduces today's iso look byte-for-byte.\n */\nexport const LIFT_PER_STEP_WORLD = 58;\n\n/**\n * Metres of front-edge height one elevation tier represents. Chosen (not guessed)\n * so `elevation × TIER_HEIGHT_M` metres, scaled back through\n * {@link CHART_UNITS_PER_METRE}, equals the legacy `elevation × LIFT_PER_STEP`\n * world lift exactly — an un-authored chart stays pixel-identical. ≈ 1.329 m/tier.\n */\nexport const TIER_HEIGHT_M = LIFT_PER_STEP_WORLD * METRES_PER_CHART_UNIT;\n\n/** Canonical authored section-geometry bounds. Keep every UI/API/renderer on\n * these constants so a producer cannot silently invent a second unit system. */\nexport const SECTION_ELEVATION_TIER_MIN = 0;\nexport const SECTION_ELEVATION_TIER_MAX = 3;\nexport const SECTION_HEIGHT_MIN_M = 0;\nexport const SECTION_HEIGHT_MAX_M = 120;\nexport const SECTION_RAKE_MIN_DEG = 0;\nexport const SECTION_RAKE_MAX_DEG = 45;\n\n/** Curated charts released before the height field used coarse levels 4–7.\n * Preserve their established lift while drafts/templates migrate to an explicit\n * height. Values above 7 came from broken compiler multipliers and must never be\n * interpreted as physical tiers. */\nexport const LEGACY_SECTION_ELEVATION_TIER_MAX = 7;\n\n/**\n * Seated spectator eye height above the tier floor (arena ≈ 1.20 m; theatre refs\n * use 1.15 m). Also the flat-ground baseline eye height, so a flat, ground-level\n * seat produces zero elevation offset in the 360° stage-pitch math (back-compat).\n */\nexport const SEATED_EYE_HEIGHT_M = 1.2;\n\n/**\n * Resolve a section's real 3D geometry, applying the legacy-elevation fallback so\n * old charts and new charts share ONE code path. Every consumer (iso lift, 360°\n * eye-height, author lint, any 2D depth cue) must call this and never read the raw\n * {@link SectionObject.height}/{@link SectionObject.rake} fields — that is what\n * keeps un-authored charts rendering identically to today.\n *\n * - `height`: authored absolute metres if present, else owning-floor base height\n * plus `elevation × TIER_HEIGHT_M`.\n * - `rake`: authored degrees if present, else 0 (flat).\n *\n * Malformed values are bounded here as a last defensive barrier. Structural\n * validation still reports them so drafts can be repaired instead of silently\n * persisting a renderer-only interpretation.\n *\n * Pure — no document mutation, no side effects.\n */\nexport interface SectionGeometryContext {\n /** Absolute physical height of the owning floor above the venue datum. */\n floorBaseHeightM?: number;\n}\n\nfunction finiteClamped(value: number | undefined, min: number, max: number, fallback: number): number {\n return Number.isFinite(value) ? Math.max(min, Math.min(max, value as number)) : fallback;\n}\n\n/** Canonical 0–3 tier exposed by Designer/shared operations. */\nexport function sectionElevationTier(value: number | undefined): number {\n if (!Number.isFinite(value)) return SECTION_ELEVATION_TIER_MIN;\n return Math.max(\n SECTION_ELEVATION_TIER_MIN,\n Math.min(SECTION_ELEVATION_TIER_MAX, Math.round(value as number)),\n );\n}\n\n/** Runtime-only compatibility tier. Released curated charts used integers 4–7;\n * preserve those while bounding compiler mistakes such as 12/55/220 to the\n * canonical maximum. New documents must store only {@link sectionElevationTier}. */\nfunction compatibleAutomaticTier(value: number | undefined): number {\n if (!Number.isFinite(value) || !Number.isInteger(value) || (value as number) < 0) return 0;\n if ((value as number) <= LEGACY_SECTION_ELEVATION_TIER_MAX) return value as number;\n return SECTION_ELEVATION_TIER_MAX;\n}\n\nexport function sectionGeometry(\n section: Pick<SectionObject, 'elevation' | 'height' | 'rake'>,\n context: SectionGeometryContext = {},\n): {\n height: number;\n rake: number;\n} {\n const floorBaseHeightM = finiteClamped(\n context.floorBaseHeightM,\n SECTION_HEIGHT_MIN_M,\n SECTION_HEIGHT_MAX_M,\n 0,\n );\n const automaticHeight = Math.min(\n SECTION_HEIGHT_MAX_M,\n floorBaseHeightM + compatibleAutomaticTier(section.elevation) * TIER_HEIGHT_M,\n );\n const height = section.height === undefined\n ? automaticHeight\n : finiteClamped(section.height, SECTION_HEIGHT_MIN_M, SECTION_HEIGHT_MAX_M, automaticHeight);\n const rake = finiteClamped(section.rake, SECTION_RAKE_MIN_DEG, SECTION_RAKE_MAX_DEG, 0);\n return { height, rake };\n}\n","/**\n * Pure geometry primitives for the view3d scene — no OGL, no DOM, so the whole\n * scene-model builder is unit-testable in a plain runtime.\n *\n * Coordinate convention: chart units (x, y) map to world metres as\n * worldX = x * METRES_PER_CHART_UNIT\n * worldZ = y * METRES_PER_CHART_UNIT\n * worldY = up (height in metres)\n * i.e. the chart's audience-depth (+y) becomes world +Z, and Y is the vertical.\n */\n\nimport earcut from 'earcut';\nimport polygonClipping from 'polygon-clipping';\nimport type { Point } from '../../core/types';\nimport { CHART_UNITS_PER_METRE, METRES_PER_CHART_UNIT } from '../../core/units';\nimport type { RGB } from '../palette';\n\nexport const M = METRES_PER_CHART_UNIT;\n\nexport interface MeshData {\n /** Non-indexed triangle soup: 3 floats per vertex. */\n position: Float32Array;\n normal: Float32Array;\n /** Baked vertex colour incl. AO, 3 floats per vertex. */\n color: Float32Array;\n /**\n * Owning floor index per vertex.\n *\n * Lets one merged mesh be dimmed per floor without splitting it into a draw\n * call per floor. A multi-floor chart (the opera house has three) draws every\n * floor at once, so the balcony sits over the parterre and hides it; isolating\n * one is the only way to look at the level you are actually booking.\n */\n floor: Float32Array;\n /**\n * Small renderer material class per vertex:\n * 0 = ordinary structure, 1 = authored powered display,\n * 2 = row riser, 3 = row tread.\n */\n material: Float32Array;\n /** Vertex count (position.length / 3). */\n count: number;\n}\n\n/**\n * Thinnest triangle the mesh will accept, world metres.\n *\n * Measured as the shortest altitude. Every path that triangulates a polygon can\n * emit degenerate slivers from inputs that are geometrically valid: earcut fans\n * near-collinear runs, and a boolean union of overlapping shapes leaves\n * zero-width SLITS where two boundaries nearly coincide — measured at 9.74 m long\n * and 0.5 mm wide when the block footprints were introduced, with aspect ratios\n * to 4.6 million. Such a triangle has a numerically meaningless face normal and\n * flat-shades as a dark hairline across the surface.\n *\n * Cleaning each producer separately does not work: the slit's vertices are far\n * apart, so point-merging and Douglas-Peucker both legitimately keep them. A\n * guard at the point of emission catches every producer at once, and dropping a\n * 2 mm-wide triangle cannot open a visible hole — it is sub-pixel at any view\n * that could ever show it.\n */\nconst MIN_TRI_ALTITUDE_M = 0.002;\n\n/** True when a triangle is too thin to contribute anything but shading noise. */\nfunction isDegenerate(\n p0: readonly [number, number, number],\n p1: readonly [number, number, number],\n p2: readonly [number, number, number],\n): boolean {\n const e0 = Math.hypot(p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]);\n const e1 = Math.hypot(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]);\n const e2 = Math.hypot(p0[0] - p2[0], p0[1] - p2[1], p0[2] - p2[2]);\n const longest = Math.max(e0, e1, e2);\n if (longest < 1e-9) return true;\n const s = (e0 + e1 + e2) / 2;\n const area = Math.sqrt(Math.max(0, s * (s - e0) * (s - e1) * (s - e2)));\n return (2 * area) / longest < MIN_TRI_ALTITUDE_M;\n}\n\n/** Accumulates flat-shaded, per-vertex-coloured triangles. */\n/**\n * Growable Float32 buffer.\n *\n * `MeshBuilder` accumulated into plain `number[]` and converted once at the end.\n * On a 99k-seat venue that meant four arrays of ~1.2M boxed numbers each and a\n * final copy of all of them — profiled at 11 % of scene build in `build()`\n * alone, plus a share of the 8 % spent in GC. Writing straight into typed\n * storage that doubles when full removes both the boxing and the final copy.\n */\nclass F32Buffer {\n private buf: Float32Array;\n private len = 0;\n\n constructor(initial = 1 << 14) {\n this.buf = new Float32Array(initial);\n }\n\n push3(a: number, b: number, c: number): void {\n if (this.len + 3 > this.buf.length) this.grow(this.len + 3);\n this.buf[this.len++] = a;\n this.buf[this.len++] = b;\n this.buf[this.len++] = c;\n }\n\n push1(a: number): void {\n if (this.len + 1 > this.buf.length) this.grow(this.len + 1);\n this.buf[this.len++] = a;\n }\n\n private grow(need: number): void {\n let cap = this.buf.length * 2;\n while (cap < need) cap *= 2;\n const next = new Float32Array(cap);\n next.set(this.buf.subarray(0, this.len));\n this.buf = next;\n }\n\n get length(): number { return this.len; }\n\n /** A copy trimmed to the used length. */\n toArray(): Float32Array {\n return this.buf.slice(0, this.len);\n }\n}\n\nexport class MeshBuilder {\n private pos = new F32Buffer();\n private nor = new F32Buffer();\n private col = new F32Buffer();\n private flr = new F32Buffer();\n private mat = new F32Buffer();\n /** Floor index stamped onto every triangle emitted from now on. */\n private currentFloor = 0;\n private currentMaterial = 0;\n\n /** Stamp subsequent triangles as belonging to `index`. */\n setFloor(index: number): void {\n this.currentFloor = index;\n }\n\n /** Stamp subsequent triangles with a small renderer material class. */\n setMaterial(index: number): void {\n this.currentMaterial = index;\n }\n\n /** One triangle with a shared (flat) normal and per-vertex colours. */\n tri(\n p0: readonly [number, number, number],\n p1: readonly [number, number, number],\n p2: readonly [number, number, number],\n n: readonly [number, number, number],\n c0: RGB,\n c1: RGB = c0,\n c2: RGB = c0,\n ): void {\n if (isDegenerate(p0, p1, p2)) return;\n this.pos.push3(p0[0], p0[1], p0[2]); this.pos.push3(p1[0], p1[1], p1[2]); this.pos.push3(p2[0], p2[1], p2[2]);\n this.nor.push3(n[0], n[1], n[2]); this.nor.push3(n[0], n[1], n[2]); this.nor.push3(n[0], n[1], n[2]);\n this.col.push3(c0[0], c0[1], c0[2]); this.col.push3(c1[0], c1[1], c1[2]); this.col.push3(c2[0], c2[1], c2[2]);\n this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor);\n this.mat.push1(this.currentMaterial); this.mat.push1(this.currentMaterial); this.mat.push1(this.currentMaterial);\n }\n\n /** One triangle with independent per-vertex normals (smooth shading). */\n triN(\n p0: readonly [number, number, number],\n p1: readonly [number, number, number],\n p2: readonly [number, number, number],\n n0: readonly [number, number, number],\n n1: readonly [number, number, number],\n n2: readonly [number, number, number],\n c0: RGB,\n c1: RGB = c0,\n c2: RGB = c0,\n ): void {\n // `triN` is used for caps with authoritative analytic normals. A very thin\n // earcut triangle therefore shades exactly like its neighbours; deleting it\n // would open a real hole in the otherwise watertight surface. Keep the\n // sliver guard on `tri`, where a face-derived normal is unreliable, but\n // preserve all finite analytically-normaled cap coverage here.\n if (![...p0, ...p1, ...p2].every(Number.isFinite)) return;\n this.pos.push3(p0[0], p0[1], p0[2]); this.pos.push3(p1[0], p1[1], p1[2]); this.pos.push3(p2[0], p2[1], p2[2]);\n this.nor.push3(n0[0], n0[1], n0[2]); this.nor.push3(n1[0], n1[1], n1[2]); this.nor.push3(n2[0], n2[1], n2[2]);\n this.col.push3(c0[0], c0[1], c0[2]); this.col.push3(c1[0], c1[1], c1[2]); this.col.push3(c2[0], c2[1], c2[2]);\n this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor);\n this.mat.push1(this.currentMaterial); this.mat.push1(this.currentMaterial); this.mat.push1(this.currentMaterial);\n }\n\n get vertexCount(): number {\n return this.pos.length / 3;\n }\n\n build(): MeshData {\n return {\n position: this.pos.toArray(),\n normal: this.nor.toArray(),\n color: this.col.toArray(),\n floor: this.flr.toArray(),\n material: this.mat.toArray(),\n count: this.pos.length / 3,\n };\n }\n}\n\n/** Face normal of a triangle (right-handed). */\nexport function faceNormal(\n a: readonly [number, number, number],\n b: readonly [number, number, number],\n c: readonly [number, number, number],\n): [number, number, number] {\n const ux = b[0] - a[0], uy = b[1] - a[1], uz = b[2] - a[2];\n const vx = c[0] - a[0], vy = c[1] - a[1], vz = c[2] - a[2];\n let nx = uy * vz - uz * vy;\n let ny = uz * vx - ux * vz;\n let nz = ux * vy - uy * vx;\n const len = Math.hypot(nx, ny, nz) || 1;\n nx /= len; ny /= len; nz /= len;\n return [nx, ny, nz];\n}\n\nexport interface Triangulation {\n /** Outline points followed by every hole's points, in order. */\n pts: Point[];\n /** Triangle vertex indices into `pts` (length is a multiple of 3). */\n tris: number[];\n}\n\n/** Triangulate a closed polygon with optional holes via earcut. */\nexport function triangulate(outline: Point[], holes?: Point[][]): Triangulation {\n const pts: Point[] = [...outline];\n const flat: number[] = [];\n for (const p of outline) flat.push(p.x, p.y);\n const holeIndices: number[] = [];\n if (holes) {\n for (const hole of holes) {\n if (hole.length < 3) continue;\n holeIndices.push(pts.length);\n for (const p of hole) {\n pts.push(p);\n flat.push(p.x, p.y);\n }\n }\n }\n const tris = earcut(flat, holeIndices.length ? holeIndices : undefined, 2);\n return { pts, tris };\n}\n\n/** Centroid of a point ring (average — good enough for wall orientation). */\nexport function centroid(pts: Point[]): Point {\n let x = 0, y = 0;\n for (const p of pts) { x += p.x; y += p.y; }\n const n = pts.length || 1;\n return { x: x / n, y: y / n };\n}\n\n/** Signed area of a ring (shoelace). Positive = CCW, negative = CW, ~0 = degenerate. */\nexport function signedArea(pts: Point[]): number {\n let a = 0;\n for (let i = 0, n = pts.length; i < n; i++) {\n const p = pts[i], q = pts[(i + 1) % n];\n a += p.x * q.y - q.x * p.y;\n }\n return a / 2;\n}\n\n/** Return the ring wound counter-clockwise (reversed copy if it was CW), so\n * CW and CCW inputs of the same polygon extrude to identical geometry. */\nexport function toCCW(pts: Point[]): Point[] {\n return signedArea(pts) < 0 ? [...pts].reverse() : pts;\n}\n\n/** Recursion ceiling for uniform cap subdivision (4^d triangles per seed). */\nconst MAX_CAP_SPLIT_DEPTH = 3;\n\n/** Max deviation of the plane from the surface, sampled over a triangle. */\nfunction capDeviation(a: Point, b: Point, c: Point, topY: (p: Point) => number): number {\n const ya = topY(a), yb = topY(b), yc = topY(c);\n const edge = (p: Point, q: Point, yp: number, yq: number): number =>\n Math.abs(topY({ x: (p.x + q.x) / 2, y: (p.y + q.y) / 2 }) - (yp + yq) / 2);\n const cx3 = (a.x + b.x + c.x) / 3, cy3 = (a.y + b.y + c.y) / 3;\n const yCen = (ya + yb + yc) / 3;\n let worst = Math.max(\n edge(a, b, ya, yb), edge(b, c, yb, yc), edge(c, a, yc, ya),\n Math.abs(topY({ x: cx3, y: cy3 }) - yCen),\n );\n // Mid-edge-to-centroid probes: the deviation is concave and zero at the\n // vertices, so its maximum lies in the interior and edge samples alone\n // under-report it — worst at the fold where a flat front plateau meets a rake.\n const probe = (px: number, py: number, yp: number): void => {\n const d = Math.abs(topY({ x: (px + cx3) / 2, y: (py + cy3) / 2 }) - (yp + yCen) / 2);\n if (d > worst) worst = d;\n };\n probe((a.x + b.x) / 2, (a.y + b.y) / 2, (ya + yb) / 2);\n probe((b.x + c.x) / 2, (b.y + c.y) / 2, (yb + yc) / 2);\n probe((c.x + a.x) / 2, (c.y + a.y) / 2, (yc + ya) / 2);\n return worst;\n}\n\n/** Worst deviation over a triangle uniformly subdivided `depth` times. */\nfunction maxDevAtDepth(a: Point, b: Point, c: Point, topY: (p: Point) => number, depth: number): number {\n if (depth <= 0) return capDeviation(a, b, c, topY);\n const ab = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n const bc = { x: (b.x + c.x) / 2, y: (b.y + c.y) / 2 };\n const ca = { x: (c.x + a.x) / 2, y: (c.y + a.y) / 2 };\n return Math.max(\n maxDevAtDepth(a, ab, ca, topY, depth - 1),\n maxDevAtDepth(ab, b, bc, topY, depth - 1),\n maxDevAtDepth(ca, bc, c, topY, depth - 1),\n maxDevAtDepth(ab, bc, ca, topY, depth - 1),\n );\n}\n\n/**\n * Emit one cap triangle uniformly subdivided `depth` times (1→4 each level).\n *\n * Uniform, at a depth shared by every triangle in the section — NOT adaptive.\n * Adaptive refinement was tried first and produced visible dark hairlines across\n * the decks: two triangles sharing an edge each pick which edge to split\n * independently, so one can split a shared edge while its neighbour does not.\n * That leaves a T-junction, and the crack between them shows the background\n * through the deck. A uniform depth guarantees both sides of every shared edge\n * are subdivided identically, so the mesh stays watertight by construction.\n */\nfunction emitCapUniform(\n builder: MeshBuilder,\n a: Point, b: Point, c: Point,\n topY: (p: Point) => number,\n colTop: RGB,\n depth: number,\n topN?: (p: Point) => [number, number, number],\n): void {\n if (depth > 0) {\n const ab = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n const bc = { x: (b.x + c.x) / 2, y: (b.y + c.y) / 2 };\n const ca = { x: (c.x + a.x) / 2, y: (c.y + a.y) / 2 };\n emitCapUniform(builder, a, ab, ca, topY, colTop, depth - 1, topN);\n emitCapUniform(builder, ab, b, bc, topY, colTop, depth - 1, topN);\n emitCapUniform(builder, ca, bc, c, topY, colTop, depth - 1, topN);\n emitCapUniform(builder, ab, bc, ca, topY, colTop, depth - 1, topN);\n return;\n }\n const at: [number, number, number] = [a.x * M, topY(a), a.y * M];\n const bt: [number, number, number] = [b.x * M, topY(b), b.y * M];\n const ct: [number, number, number] = [c.x * M, topY(c), c.y * M];\n if (topN) {\n // Analytic per-vertex normals: continuous across every shared edge, so the\n // deck shades as the smooth surface it approximates rather than as facets,\n // and a degenerate triangle (whose face normal is numerically meaningless)\n // still shades identically to its neighbours.\n builder.triN(at, bt, ct, topN(a), topN(b), topN(c), colTop);\n return;\n }\n let n = faceNormal(at, bt, ct);\n if (n[1] < 0) n = [-n[0], -n[1], -n[2]]; // caps face up\n builder.tri(at, bt, ct, n, colTop);\n}\n/** Ceiling on inserted points per ring edge, so a pathological outline cannot\n * explode the vertex count. */\nconst MAX_RING_SPLIT_DEPTH = 8;\n\n/**\n * Insert points along a ring's edges until each sub-edge's midpoint height is\n * within `maxError` of the straight interpolation between its ends.\n *\n * This is what lets the wall tops follow the same curved surface as the cap. A\n * wall's top edge is a straight line between two outline vertices; on a raked\n * tier the true surface bows away from that line, so an undensified ring leaves\n * the wall top hanging above or below the cap it is supposed to meet. Densifying\n * once, before triangulation, fixes cap boundary and wall top together — they\n * are built from the same point list.\n */\nfunction densifyRing(ring: Point[], topY: (p: Point) => number, maxError: number): Point[] {\n if (!Number.isFinite(maxError)) return ring;\n const out: Point[] = [];\n const emit = (p: Point, q: Point, yp: number, yq: number, depth: number): void => {\n if (depth > 0) {\n const mid: Point = { x: (p.x + q.x) / 2, y: (p.y + q.y) / 2 };\n const ym = topY(mid);\n if (Math.abs(ym - (yp + yq) / 2) > maxError) {\n emit(p, mid, yp, ym, depth - 1);\n emit(mid, q, ym, yq, depth - 1);\n return;\n }\n }\n out.push(p); // q is emitted by the next edge (ring is closed)\n };\n for (let i = 0; i < ring.length; i++) {\n const p = ring[i], q = ring[(i + 1) % ring.length];\n emit(p, q, topY(p), topY(q), MAX_RING_SPLIT_DEPTH);\n }\n return out;\n}\n\n/**\n * Drop ring points that sit closer than `eps` to the previous kept point, and\n * points that are collinear with their neighbours to within `eps`.\n *\n * This is the SOURCE of the remaining slivers, and it is upstream of shading.\n * `outsetRing`'s closing union re-emits intersection points that can land within\n * nanometres of an input vertex, and `densifyRing` then splits around them; a\n * point pair 1e-7 chart units apart is a zero-area triangle to earcut, which\n * fans it against a distant vertex. Measured aspect ratios reached 8.7e6.\n *\n * Removing them cannot move the outline perceptibly — `eps` is a fraction of a\n * millimetre in world metres — but it removes the degeneracy earcut amplifies.\n * Collinear removal is the same argument: a point exactly on the segment between\n * its neighbours carries no shape, and it is what turns one clean triangle into\n * a needle plus a remainder.\n */\nfunction dedupeRing(ring: Point[], eps: number): Point[] {\n if (ring.length < 3) return ring;\n const out: Point[] = [];\n for (const p of ring) {\n const last = out[out.length - 1];\n if (last && Math.hypot(p.x - last.x, p.y - last.y) < eps) continue;\n out.push(p);\n }\n // Close-the-loop duplicate.\n while (out.length > 2 && Math.hypot(out[0].x - out[out.length - 1].x, out[0].y - out[out.length - 1].y) < eps) {\n out.pop();\n }\n if (out.length < 3) return ring;\n // Collinear pass: drop p when its perpendicular distance to (prev,next) < eps.\n const keep: Point[] = [];\n for (let i = 0; i < out.length; i++) {\n const prev = keep.length ? keep[keep.length - 1] : out[(i - 1 + out.length) % out.length];\n const p = out[i];\n const next = out[(i + 1) % out.length];\n const ax = next.x - prev.x, ay = next.y - prev.y;\n const len = Math.hypot(ax, ay);\n if (len > eps) {\n const cross = Math.abs((p.x - prev.x) * ay - (p.y - prev.y) * ax) / len;\n if (cross < eps) continue;\n }\n keep.push(p);\n }\n return keep.length >= 3 ? keep : out;\n}\n\n/** Ring cleanup tolerance in chart units (~0.1 mm in world metres). */\nconst RING_EPS_U = 1e-3 * CHART_UNITS_PER_METRE * 0.1;\n\n/**\n * Extrude a closed polygon into a prism: a (possibly sloped) top cap, a bottom\n * cap, and side walls with a baked top→bottom AO gradient.\n *\n * `topY(p)` returns the world-metre height of the top surface at chart-point `p`\n * (constant for a slab, rake-sloped for a raked tier). `bottomY` is the floor.\n *\n * `maxCapError` (metres) bounds how far the drawn cap may deviate from `topY`;\n * omit it (or pass Infinity) for a flat top, where the cap is exact by\n * construction and no subdivision is wanted.\n */\nexport function extrudePrism(\n builder: MeshBuilder,\n outlineIn: Point[],\n holesIn: Point[][] | undefined,\n topY: (p: Point) => number,\n bottomY: number,\n colTop: RGB,\n colWall: RGB,\n ao: { top: number; wallBottom: number; bottomCap: number },\n maxCapError: number | undefined = Infinity,\n topNormal?: (p: Point) => [number, number, number],\n): void {\n // Guard degenerate/near-collinear polygons (zero visible area) — a free-hand\n // or generated outline can collapse to a sliver and would emit garbage tris.\n if (!outlineIn || outlineIn.length < 3) return;\n if (maxCapError === undefined) maxCapError = Infinity;\n if (Math.abs(signedArea(outlineIn)) < 1e-4) return;\n // Normalise winding so CW and CCW inputs produce identical geometry (the solid\n // program also disables culling, but this keeps the emitted mesh deterministic).\n // Densify the rings against the surface FIRST, so the cap boundary and the\n // wall tops are generated from one point list and cannot part company.\n const outline = dedupeRing(densifyRing(dedupeRing(toCCW(outlineIn), RING_EPS_U), topY, maxCapError), RING_EPS_U);\n const holes = holesIn?.map((h) => dedupeRing(densifyRing(dedupeRing(toCCW(h), RING_EPS_U), topY, maxCapError), RING_EPS_U))\n .filter((h) => h.length >= 3 && Math.abs(signedArea(h)) >= 1e-4);\n const { pts, tris } = triangulate(outline, holes);\n const cTop: RGB = [colTop[0] * ao.top, colTop[1] * ao.top, colTop[2] * ao.top];\n const cBot: RGB = [colTop[0] * ao.bottomCap, colTop[1] * ao.bottomCap, colTop[2] * ao.bottomCap];\n const cWallTop: RGB = [colWall[0] * ao.top, colWall[1] * ao.top, colWall[2] * ao.top];\n const cWallBot: RGB = [colWall[0] * ao.wallBottom, colWall[1] * ao.wallBottom, colWall[2] * ao.wallBottom];\n\n // Resolve ONE subdivision depth for the whole section: the smallest depth at\n // which every cap triangle tracks the surface within `maxCapError`. Shared by\n // all triangles so the cap stays watertight (see emitCapUniform).\n let capDepth = 0;\n if (Number.isFinite(maxCapError)) {\n for (; capDepth < MAX_CAP_SPLIT_DEPTH; capDepth++) {\n let worst = 0;\n for (let i = 0; i < tris.length; i += 3) {\n const d = maxDevAtDepth(pts[tris[i]], pts[tris[i + 1]], pts[tris[i + 2]], topY, capDepth);\n if (d > worst) worst = d;\n }\n if (worst <= maxCapError) break;\n }\n }\n\n // Top + bottom caps.\n for (let i = 0; i < tris.length; i += 3) {\n const a = pts[tris[i]], b = pts[tris[i + 1]], c = pts[tris[i + 2]];\n emitCapUniform(builder, a, b, c, topY, cTop, capDepth, topNormal);\n // Bottom cap (reversed winding, faces down).\n const ab: [number, number, number] = [a.x * M, bottomY, a.y * M];\n const bb: [number, number, number] = [b.x * M, bottomY, b.y * M];\n const cb: [number, number, number] = [c.x * M, bottomY, c.y * M];\n builder.tri(ab, cb, bb, [0, -1, 0], cBot);\n }\n\n // Side walls. Orient outline walls away from the outline centroid; hole walls\n // face into the hole (flip). Vertical walls → horizontal normals.\n // Wall tops must land on exactly the vertices the cap's boundary produced, or\n // a crack opens along the top edge. Uniform cap subdivision splits every\n // boundary edge into 2^capDepth equal parts, so split the wall rings the same\n // way and both meshes share the identical point list.\n const splitRing = (ring: Point[]): Point[] => {\n if (capDepth <= 0) return ring;\n const n = 1 << capDepth;\n const out: Point[] = [];\n for (let i = 0; i < ring.length; i++) {\n const a = ring[i], b = ring[(i + 1) % ring.length];\n for (let k = 0; k < n; k++) {\n const t = k / n;\n out.push({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });\n }\n }\n return out;\n };\n const oc = centroid(outline);\n const rings: Array<{ ring: Point[]; flip: boolean }> = [{ ring: splitRing(outline), flip: false }];\n if (holes) for (const h of holes) if (h.length >= 3) rings.push({ ring: splitRing(h), flip: true });\n\n for (const { ring, flip } of rings) {\n for (let i = 0; i < ring.length; i++) {\n const a = ring[i];\n const b = ring[(i + 1) % ring.length];\n const dx = (b.x - a.x) * M;\n const dz = (b.y - a.y) * M;\n let nx = dz, nz = -dx;\n const nl = Math.hypot(nx, nz) || 1;\n nx /= nl; nz /= nl;\n // Orient outward from the outline centroid.\n const mx = (a.x + b.x) / 2 - oc.x;\n const mz = (a.y + b.y) / 2 - oc.y;\n let dot = nx * mx + nz * mz;\n if (flip) dot = -dot;\n if (dot < 0) { nx = -nx; nz = -nz; }\n const n: [number, number, number] = [nx, 0, nz];\n\n const aTop: [number, number, number] = [a.x * M, topY(a), a.y * M];\n const bTop: [number, number, number] = [b.x * M, topY(b), b.y * M];\n const aBot: [number, number, number] = [a.x * M, bottomY, a.y * M];\n const bBot: [number, number, number] = [b.x * M, bottomY, b.y * M];\n builder.tri(aTop, bTop, bBot, n, cWallTop, cWallTop, cWallBot);\n builder.tri(aTop, bBot, aBot, n, cWallTop, cWallBot, cWallBot);\n }\n }\n}\n\n/** Merge several MeshData buffers into one (single draw call). */\nexport function mergeMeshData(parts: MeshData[]): MeshData {\n let total = 0;\n for (const p of parts) total += p.count;\n const position = new Float32Array(total * 3);\n const normal = new Float32Array(total * 3);\n const color = new Float32Array(total * 3);\n const floor = new Float32Array(total);\n const material = new Float32Array(total);\n let off = 0;\n for (const p of parts) {\n position.set(p.position, off * 3);\n normal.set(p.normal, off * 3);\n color.set(p.color, off * 3);\n floor.set(p.floor, off);\n material.set(p.material, off);\n off += p.count;\n }\n return { position, normal, color, floor, material, count: total };\n}\n\n/**\n * Grow a ring outward by `d` chart units — a MITER offset, not a dilation.\n *\n * Seat dots have a real world radius, so a seat authored hard against its\n * section boundary overhangs the deck and appears to float in the aisle. Seats\n * are chart data and must not move, so the DECK grows to meet them.\n *\n * The first implementation was a true Minkowski dilation (union of the ring, a\n * quad per edge, a disc per vertex). It was geometrically correct and visually\n * wrong: it tripled a 30-point outline to ~90 points with clusters of\n * near-duplicates where discs meet quads, and earcut fans such clusters into\n * needle triangles — 10,787 of 23,621 slivers at up to 20,650:1, which\n * flat-shade into dark hairlines streaking across the deck. Decimating the\n * result then cut the very corners the dilation had just rounded, putting seats\n * back over the edge. Dilate-then-decimate was fighting itself.\n *\n * A miter offset emits exactly ONE point per input vertex instead: each vertex\n * moves along its angle bisector by `d / cos(half-angle)`, which lands precisely\n * on the intersection of the two offset edges. Corners stay sharp and exact, the\n * point count does not grow, so there is nothing to decimate and no sliver\n * source. Sharp spikes are bevelled at `miterLimit`, and the single union at the\n * end resolves the self-intersections a miter offset produces where a concave\n * outline folds over itself.\n *\n * Holes are deliberately left alone: eroding them needs the complement and they\n * are rare in practice. A seat authored on a hole's rim can still overhang it.\n */\nexport function outsetRing(ring: Point[], d: number, miterLimit = 2.5): Point[] {\n if (d <= 0 || ring.length < 3) return ring;\n const r = toCCW(ring);\n const n = r.length;\n // Outward unit normal of each edge (valid because the ring is CCW).\n const nrm: Array<{ x: number; y: number }> = [];\n for (let i = 0; i < n; i++) {\n const a = r[i], b = r[(i + 1) % n];\n const dx = b.x - a.x, dy = b.y - a.y;\n const len = Math.hypot(dx, dy) || 1;\n nrm.push({ x: dy / len, y: -dx / len });\n }\n const out: [number, number][] = [];\n for (let i = 0; i < n; i++) {\n const p = r[i];\n const nPrev = nrm[(i - 1 + n) % n]; // edge arriving at p\n const nCur = nrm[i]; // edge leaving p\n let mx = nPrev.x + nCur.x, my = nPrev.y + nCur.y;\n const ml = Math.hypot(mx, my);\n // Bevel a spike (or a full reversal) into the two edge-offset points rather\n // than letting the miter shoot off to infinity.\n const bevel = (): void => {\n out.push([p.x + nPrev.x * d, p.y + nPrev.y * d]);\n out.push([p.x + nCur.x * d, p.y + nCur.y * d]);\n };\n if (ml < 1e-9) { bevel(); continue; }\n mx /= ml; my /= ml;\n const cosHalf = mx * nPrev.x + my * nPrev.y;\n const scale = 1 / Math.max(cosHalf, 1e-6);\n if (!Number.isFinite(scale) || scale > miterLimit) { bevel(); continue; }\n out.push([p.x + mx * d * scale, p.y + my * d * scale]);\n }\n if (out.length < 3) return ring;\n try {\n // Self-union: resolves the loops a miter offset folds into concave corners.\n const merged = polygonClipping.union([[...out, out[0]]]);\n let best: [number, number][] | null = null, bestArea = -Infinity;\n for (const poly of merged) {\n const area = Math.abs(signedArea(poly[0].map(([x, y]) => ({ x, y }))));\n if (area > bestArea) { bestArea = area; best = poly[0]; }\n }\n if (!best) return ring;\n const pts = best.map(([x, y]) => ({ x, y }));\n // union() repeats the first point to close the ring; drop the duplicate.\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n return pts.length >= 3 ? pts : ring;\n } catch {\n return ring; // never let a degenerate outline break the whole scene\n }\n}\n\n/** Sample an ellipse (chart units) into a closed polygon of `seg` points. */\nexport function ellipsePolygon(cx: number, cy: number, rx: number, ry: number, seg = 28): Point[] {\n const out: Point[] = [];\n for (let i = 0; i < seg; i++) {\n const a = (i / seg) * Math.PI * 2;\n out.push({ x: cx + rx * Math.cos(a), y: cy + ry * Math.sin(a) });\n }\n return out;\n}\n\n/** Axis-aligned rectangle (chart units) as a closed polygon. */\nexport function rectPolygon(x: number, y: number, w: number, h: number): Point[] {\n return [\n { x, y },\n { x: x + w, y },\n { x: x + w, y: y + h },\n { x, y: y + h },\n ];\n}\n","/**\n * The near-field seat: a three-box chair plus a compact seated-person\n * silhouette, and the per-seat facing that aims it at the stage.\n *\n * Why this exists at all: every seat in the venue was a camera-facing disc, and\n * a disc has no front. That is fine across a bowl and wrong at the one moment\n * the 3D view is selling — the buyer lands ON their seat and the row they are\n * sitting in is a line of coloured blobs rather than chairs. The renderer's own\n * programme notes call it the biggest visual gap at close range.\n *\n * What it deliberately is NOT: furniture. There are no armrests, no cushion\n * bevel, no legs. At 3–10 m what the eye needs is a pad at seat height and a\n * back rising behind it, facing the right way — the silhouette, not the detail.\n * Everything here is one small instanced base mesh, because the third draw call\n * is the budget. Improving the shared silhouette does not add a draw per person.\n */\n\n/** Local-space part id, so the fragment stage can treat furniture and people apart. */\nexport const CHAIR_PART = { pedestal: 0, pad: 1, back: 2, body: 3, head: 4 } as const;\n\n/**\n * Parts at or above this index belong to the OCCUPANT, not the chair.\n *\n * They are collapsed to nothing on an empty seat (see the chair vertex shader),\n * which is how one instanced mesh draws both a bare seat and a taken one without\n * a second draw call or a second geometry.\n */\nexport const CHAIR_PART_OCCUPANT_MIN = CHAIR_PART.body;\n\nexport interface ChairMesh {\n /**\n * Local vertex positions. x and z are in units of the seat's own chair\n * half-width (`iChairWidth`); y is in METRES and is not scaled.\n *\n * The split matters: a chart authored at a tight seat pitch needs narrower\n * chairs or they interpenetrate, but it does not need SHORTER ones — a tight\n * row is still full of adult-sized people. A uniform scale would have produced\n * a doll's-house block on exactly the densest charts.\n */\n position: Float32Array;\n normal: Float32Array;\n /** Per-vertex CHAIR_PART. */\n part: Float32Array;\n index: Uint16Array;\n vertexCount: number;\n indexCount: number;\n}\n\n/**\n * A chair's half-width as a fraction of its seat's own spacing.\n *\n * NOT the dot's `SEAT_PITCH_FRACTION` (0.42), and the difference matters. That\n * constant exists to keep round dots visually separate, so it is deliberately\n * conservative and it is capped at SEAT_DOT_RADIUS_M on top. Sizing a chair from\n * it produced tall narrow slabs — a 0.34 m chair with a 0.95 m back, an aspect\n * ratio of nearly 3:1, which reads as a gravestone rather than as seating.\n *\n * Real auditorium seats very nearly touch: at a 0.53 m pitch (the measured mean)\n * a seat is about 0.48 m across. 0.44 reproduces that — the pair occupies 0.88\n * of the pitch and the remaining 12 % is the gap you actually see in a venue.\n */\nexport const CHAIR_PITCH_FRACTION = 0.44;\n\n/**\n * Bounds on the resolved half-width, world metres.\n *\n * The floor is the honest compromise in this file. The amphitheatre measures a\n * 0.21 m minimum seat spacing, and at that spacing NO chair both fits between\n * its neighbours and reads as a chair — 21 cm is not a real seat pitch, it is a\n * chart authored tighter than the units claim. Given the choice between chairs\n * that overlap slightly and chairs that are legible, the overlap wins: it is\n * invisible from any angle a buyer actually looks from, whereas a 9 cm-wide\n * 0.95 m-tall slab is wrong from every angle. The ceiling stops a sparse box\n * seat from inflating into an armchair.\n */\nexport const CHAIR_HALF_WIDTH_MIN_M = 0.15;\nexport const CHAIR_HALF_WIDTH_MAX_M = 0.30;\n/** Used when a seat has no resolvable neighbour at all. */\nexport const CHAIR_HALF_WIDTH_DEFAULT_M = 0.24;\n\n/**\n * The world half-width of one seat's chair, from its own spacing in metres.\n *\n * Everything horizontal about the chair scales from this single number, so a\n * chair's depth tracks its width and the proportions hold at any pitch. Height\n * does not — see `ChairMesh.position`.\n */\nexport function chairHalfWidth(pitchM: number | undefined): number {\n if (pitchM === undefined || !Number.isFinite(pitchM) || pitchM <= 0) {\n return CHAIR_HALF_WIDTH_DEFAULT_M;\n }\n return Math.min(CHAIR_HALF_WIDTH_MAX_M, Math.max(CHAIR_HALF_WIDTH_MIN_M, pitchM * CHAIR_PITCH_FRACTION));\n}\n\n/**\n * Backward lean of the seat back, as a slope (metres of z per metre of rise).\n *\n * The first build had a vertical back and the result read as a crate: a flat top\n * face, a flat wall behind it, and nothing to say which way was up or which way\n * was forward. A rake is the cheapest possible chair cue — it costs a shear —\n * and it puts a lit face at an angle to every other surface in the venue, which\n * is what breaks the block up.\n *\n * Applied in the SHADER, in world metres, and deliberately not baked into the\n * base mesh. The mesh's x/z are in half-width units, so a lean baked there would\n * scale with the seat's width: the same chair would lean 20° on a wide-pitch\n * stadium row and 6° on a tight theatre row. 0.21 is about 12° from vertical,\n * which is roughly a real fixed auditorium seat.\n */\nexport const BACK_RAKE_SLOPE = 0.21;\n\n/**\n * Air between the top of the pad and the bottom of the back, world metres.\n *\n * The other half of \"crate vs chair\". Two boxes sharing a face silhouette as one\n * solid; separate them and the eye reads a seat with a back behind it, even at\n * 10 m where the gap itself is barely a pixel — because it breaks the outline.\n */\nconst PAD_BACK_GAP_M = 0.05;\n\nconst PAD_TOP_M = 0.45;\n/** World height at which the back starts, and therefore where the rake pivots. */\nexport const BACK_BASE_M = PAD_TOP_M + PAD_BACK_GAP_M;\n\n/**\n * Chair dimensions. +Z is the direction the seat FACES, so the back panel lives\n * at negative z. x/z are half-width units; y is metres above the seat's own deck\n * point, chosen against the height contract the rest of the renderer uses — a\n * seated eye sits SEAT_EYE_ABOVE_DECK (1.02 m) up, so a 0.45 m pad and a 0.92 m\n * back top put the occupant's shoulders just over the seat back.\n */\nconst CHAIR_BOXES: Array<{\n part: number;\n min: [number, number, number];\n max: [number, number, number];\n}> = [\n // Pedestal — a plain column under the pad. Without it the pad floats 0.36 m\n // over the deck and the row reads as hovering trays.\n { part: CHAIR_PART.pedestal, min: [-0.30, 0.00, -0.30], max: [0.30, 0.36, 0.30] },\n // Seat pad — a full seat width across and about as deep, which is what a real\n // one is. Its depth is bounded by the same pitch as its width, because the\n // pitch measure is the tighter of the in-row and row-to-row spacings, so a\n // tightly-raked tier cannot drive a pad into the back of the row in front.\n { part: CHAIR_PART.pad, min: [-1.00, 0.36, -0.95], max: [1.00, PAD_TOP_M, 1.00] },\n // Back panel — thin, raked, and the tallest thing in the row, so it is what\n // carries the state colour when you look along a row from behind.\n {\n part: CHAIR_PART.back,\n min: [-1.00, BACK_BASE_M, -1.00],\n max: [1.00, 0.92, -0.72],\n },\n];\n\n/** The two legs are split so the gap between them survives in silhouette. */\nconst OCCUPANT_BOXES: Array<{ part: number; min: [number, number, number]; max: [number, number, number] }> = [\n // Thighs project forward from the pad. A single lap slab brought back the\n // same toy-block problem as the old torso, so each leg keeps its own outline.\n { part: CHAIR_PART.body, min: [-0.52, 0.39, -0.08], max: [-0.08, 0.55, 0.82] },\n { part: CHAIR_PART.body, min: [0.08, 0.39, -0.08], max: [0.52, 0.55, 0.82] },\n // Lower legs drop from the knees, making the pose unmistakably seated from\n // the side and from the elevated arena camera.\n { part: CHAIR_PART.body, min: [-0.47, 0.05, 0.58], max: [-0.13, 0.43, 0.82] },\n { part: CHAIR_PART.body, min: [0.13, 0.05, 0.58], max: [0.47, 0.43, 0.82] },\n // A short neck keeps the head connected to the shoulders from behind. Most\n // of it sits inside the torso/head overlap: a long exposed cuboid made the\n // close-range figure read as a mannequin on a post.\n { part: CHAIR_PART.head, min: [-0.16, 1.04, -0.33], max: [0.16, 1.10, -0.13] },\n];\n\n/** The six axis-aligned faces of a box, as (normal, corner order). */\nconst FACES: Array<{ n: [number, number, number]; c: Array<[number, number, number]> }> = [\n // +X\n { n: [1, 0, 0], c: [[1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 0, 1]] },\n // -X\n { n: [-1, 0, 0], c: [[0, 0, 1], [0, 1, 1], [0, 1, 0], [0, 0, 0]] },\n // +Y\n { n: [0, 1, 0], c: [[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]] },\n // -Y\n { n: [0, -1, 0], c: [[0, 0, 1], [0, 0, 0], [1, 0, 0], [1, 0, 1]] },\n // +Z\n { n: [0, 0, 1], c: [[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]] },\n // -Z\n { n: [0, 0, -1], c: [[1, 0, 0], [0, 0, 0], [0, 1, 0], [1, 1, 0]] },\n];\n\ntype V3 = [number, number, number];\n\n/**\n * Build the shared chair/person base mesh once per GPU scene.\n *\n * The old occupant was two cuboids. It was cheap, but its square head and\n * rectangular body looked like a marker post rather than a buyer in a seat.\n * This keeps the same one-mesh/one-draw architecture while spending a few\n * hundred shared vertices on the information-bearing silhouette: tapered\n * shoulders, a round head and separated bent legs.\n */\nexport function buildChairMesh(): ChairMesh {\n const positions: number[] = [];\n const normals: number[] = [];\n const parts: number[] = [];\n const indices: number[] = [];\n\n const addQuad = (corners: [V3, V3, V3, V3], outward: V3, partId: number): void => {\n const base = parts.length;\n const [a, b, c] = corners;\n const ab: V3 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];\n const ac: V3 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];\n let nx = ab[1] * ac[2] - ab[2] * ac[1];\n let ny = ab[2] * ac[0] - ab[0] * ac[2];\n let nz = ab[0] * ac[1] - ab[1] * ac[0];\n const length = Math.hypot(nx, ny, nz);\n if (length > 1e-9) { nx /= length; ny /= length; nz /= length; }\n else { [nx, ny, nz] = outward; }\n if (nx * outward[0] + ny * outward[1] + nz * outward[2] < 0) {\n nx = -nx; ny = -ny; nz = -nz;\n }\n for (const corner of corners) {\n positions.push(...corner);\n normals.push(nx, ny, nz);\n parts.push(partId);\n }\n indices.push(base, base + 1, base + 2, base, base + 2, base + 3);\n };\n\n const addBox = (box: { part: number; min: V3; max: V3 }): void => {\n for (const face of FACES) {\n const corners = face.c.map((corner) => corner.map(\n (side, axis) => side ? box.max[axis] : box.min[axis],\n ) as V3) as [V3, V3, V3, V3];\n addQuad(corners, face.n, box.part);\n }\n };\n\n for (const box of CHAIR_BOXES) addBox(box);\n for (const box of OCCUPANT_BOXES) addBox(box);\n\n // Tapered seated torso. Shoulders are wider than the waist and the front\n // narrows toward the chest, so even an untextured back view reads as a human\n // upper body instead of the old rectangular parcel.\n const waistY = PAD_TOP_M;\n const shoulderY = 1.08;\n const wb = 0.47, wt = 0.69;\n const backBottom = -0.55, frontBottom = 0.08;\n const backTop = -0.48, frontTop = 0.01;\n const blb: V3 = [-wb, waistY, backBottom], brb: V3 = [wb, waistY, backBottom];\n const blf: V3 = [-wb, waistY, frontBottom], brf: V3 = [wb, waistY, frontBottom];\n const tlb: V3 = [-wt, shoulderY, backTop], trb: V3 = [wt, shoulderY, backTop];\n const tlf: V3 = [-wt, shoulderY, frontTop], trf: V3 = [wt, shoulderY, frontTop];\n addQuad([brb, trb, trf, brf], [1, 0, 0], CHAIR_PART.body);\n addQuad([blf, tlf, tlb, blb], [-1, 0, 0], CHAIR_PART.body);\n addQuad([tlf, trf, trb, tlb], [0, 1, 0], CHAIR_PART.body);\n addQuad([blb, brb, brf, blf], [0, -1, 0], CHAIR_PART.body);\n addQuad([brf, trf, tlf, blf], [0, 0, 1], CHAIR_PART.body);\n addQuad([blb, tlb, trb, brb], [0, 0, -1], CHAIR_PART.body);\n\n // Smooth low-poly ellipsoid head: 8 sides × 5 vertical bands. This is only\n // 54 shared vertices, but removing the square head is the largest perceptual\n // gain in the whole occupant. Radii are authored in the mesh's mixed units:\n // x/z scale with chair width, y remains honest world metres in the shader.\n const lonSegments = 10;\n const latSegments = 8;\n // x/z are later multiplied by the chair half-width while y stays in metres.\n // These mixed units therefore look odd on paper: the wider x/z radii produce\n // a roughly round 20–22 cm head in world space instead of the former tall,\n // narrow egg. Its lower pole overlaps the shoulders/short neck slightly so\n // rasterisation cannot open a floating gap.\n const centre: V3 = [0, 1.205, -0.23];\n const radii: V3 = [0.42, 0.115, 0.38];\n const headBase = parts.length;\n for (let lat = 0; lat <= latSegments; lat++) {\n const theta = Math.PI * lat / latSegments;\n const sy = Math.cos(theta);\n const ring = Math.sin(theta);\n for (let lon = 0; lon <= lonSegments; lon++) {\n const phi = Math.PI * 2 * lon / lonSegments;\n const dx = ring * Math.cos(phi);\n const dz = ring * Math.sin(phi);\n positions.push(\n centre[0] + radii[0] * dx,\n centre[1] + radii[1] * sy,\n centre[2] + radii[2] * dz,\n );\n let nx = dx / radii[0];\n let ny = sy / radii[1];\n let nz = dz / radii[2];\n const length = Math.hypot(nx, ny, nz) || 1;\n nx /= length; ny /= length; nz /= length;\n normals.push(nx, ny, nz);\n parts.push(CHAIR_PART.head);\n }\n }\n for (let lat = 0; lat < latSegments; lat++) {\n for (let lon = 0; lon < lonSegments; lon++) {\n const a = headBase + lat * (lonSegments + 1) + lon;\n const b = a + lonSegments + 1;\n indices.push(a, b, a + 1, a + 1, b, b + 1);\n }\n }\n\n const position = new Float32Array(positions);\n const normal = new Float32Array(normals);\n const part = new Float32Array(parts);\n const index = new Uint16Array(indices);\n return {\n position,\n normal,\n part,\n index,\n vertexCount: parts.length,\n indexCount: indices.length,\n };\n}\n\n/**\n * Per-seat facing yaw, radians, such that the chair's local +Z points at what\n * the seat looks at.\n *\n * Derived from the ROW, not from the seat alone. Aiming every seat individually\n * at the focal point is the obvious rule and it is wrong for the commonest case:\n * a straight row in a theatre fans out into a shallow arc of chairs pointing\n * slightly inward, which no real auditorium does. Taking the row's local tangent\n * and turning perpendicular to it gives BOTH cases for free — a straight row\n * comes out uniformly forward, and a curved arena row keeps the genuine radial\n * fan it is built with.\n *\n * The focal only picks the SIGN (which of the two perpendiculars faces the\n * show), so a mis-authored focal can never rotate a row sideways, only flip it.\n * A row with a single seat has no tangent and falls back to facing the focal\n * directly.\n *\n * Row membership is read as RUNS of equal `rowId` rather than through a map:\n * expanded seats come out row-contiguous, the scan is O(n) with no allocation,\n * and a chart that somehow interleaved rows degrades to shorter runs rather than\n * to wrong answers.\n */\nexport function computeSeatYaw(\n iPosition: Float32Array,\n count: number,\n rowIdAt: (index: number) => string | undefined,\n focal: (index: number) => readonly [number, number],\n): Float32Array {\n const yaw = new Float32Array(count);\n const px = (i: number): number => iPosition[i * 3];\n const pz = (i: number): number => iPosition[i * 3 + 2];\n\n let runStart = 0;\n const flushRun = (start: number, end: number): void => {\n // [start, end) share a rowId.\n const n = end - start;\n for (let i = start; i < end; i++) {\n const [fx, fz] = focal(i);\n let dx = fx - px(i);\n let dz = fz - pz(i);\n if (n >= 2) {\n // Central difference where possible; one-sided at the row ends.\n const a = Math.max(start, i - 1);\n const b = Math.min(end - 1, i + 1);\n const tx = px(b) - px(a);\n const tz = pz(b) - pz(a);\n const tl = Math.hypot(tx, tz);\n if (tl > 1e-6) {\n // Perpendicular to the row, signed toward the focal.\n let nx = -tz / tl;\n let nz = tx / tl;\n if (nx * dx + nz * dz < 0) { nx = -nx; nz = -nz; }\n dx = nx;\n dz = nz;\n }\n }\n // atan2(x, z): the yaw that rotates local +Z onto (dx, dz).\n yaw[i] = (dx === 0 && dz === 0) ? 0 : Math.atan2(dx, dz);\n }\n };\n\n for (let i = 1; i <= count; i++) {\n if (i === count || rowIdAt(i) !== rowIdAt(runStart)) {\n flushRun(runStart, i);\n runStart = i;\n }\n }\n return yaw;\n}\n","/**\n * Pure builder for the instanced seat cloud. Produces the per-instance arrays a\n * single OGL InstancedMesh consumes (one draw call for every seat), plus the\n * seatId → instanceIndex map that `setAvailability` uses to patch only the seats\n * that actually changed via a sub-range `bufferSubData` upload.\n */\n\nimport { accessibilityRingColor, type ExpandedSeat } from '../../core/types';\nimport { hexToRgb } from '../palette';\nimport { SEATED_EYE_HEIGHT_M } from '../../core/units';\nimport { M } from './geometry';\nimport { chairHalfWidth } from './seatChair';\nimport { seatStateIndex, SEAT_STATES, type SeatState3D } from '../palette';\nimport type { VenueSurfaces } from './surface';\n\n/**\n * World radius of a seat dot in metres — the single source for the shader\n * uniform AND for how far a section deck is padded so its seats sit on it.\n * (The shader may grow this with distance to hold `uMinPixels`; this is the\n * near-field base.)\n */\nexport const SEAT_DOT_RADIUS_M = 0.22;\n\n/**\n * Whether a seat has somebody in it, and who.\n *\n * Occupancy is not invented — it is the inventory. A seat that is SOLD has been\n * bought by a person, and a HELD one is being bought right now, so those are the\n * seats that get an occupant. Available and dimmed seats stay empty, and a\n * SELECTED seat stays empty too: that is the buyer's own seat, and drawing a\n * stranger in it while they choose it would be worse than an empty hall.\n *\n * This means the crowd tells the truth about the event. A nearly-sold show looks\n * full from the seat you are considering, and a quiet one looks quiet — which is\n * information a buyer actually wants, arrived at without inventing anything.\n *\n * Returns a negative value for an empty seat; otherwise a stable per-seat hash\n * in [0,1) that varies the occupant's build and tone. Stable so a person does\n * not change shape as the near-field set is re-gathered around a moving camera.\n */\nexport function seatOccupantSeed(stateIndex: number, seatIndex: number): number {\n const state = SEAT_STATES[stateIndex];\n if (state !== 'sold' && state !== 'held') return -1;\n // Cheap integer hash — deterministic, no Math.random, and well distributed\n // across neighbouring indices so adjacent seats do not come out matching.\n let h = (seatIndex + 1) * 2654435761;\n h ^= h >>> 15;\n h = Math.imul(h, 2246822519);\n h ^= h >>> 13;\n return ((h >>> 0) % 100000) / 100000;\n}\n\n/**\n * Fraction of a seat's nearest-neighbour spacing that its dot may occupy.\n *\n * Below 0.5 two adjacent dots cannot touch even when both sit at their ceiling,\n * so a gap always survives between rows. 0.42 leaves that gap visible rather\n * than hairline.\n */\nexport const SEAT_PITCH_FRACTION = 0.42;\n\nexport interface SeatInstanceData {\n count: number;\n /** vec3 per instance: world (x, y, z) in metres. */\n iPosition: Float32Array;\n /** float per instance: index into the seat-state colour LUT. */\n iState: Float32Array;\n /**\n * vec3 per instance: the seat's CATEGORY colour, used while it is available.\n *\n * 2D fills a free seat with its category colour and only overrides it once the\n * seat is held/sold/selected. 3D used one flat green for every available seat,\n * which threw away the price-tier read on switching views and made a full\n * house look like a status map instead of a venue.\n */\n iCategory: Float32Array;\n /**\n * float per instance: the largest world radius this dot may take, metres.\n *\n * The shader enforces `uMinPixels` by GROWING a dot's world radius with depth,\n * which is what merges rows into a solid mass at range — measured mean seat\n * spacing is 0.53–0.58 m against a 0.44 m dot diameter, so there is very little\n * slack to spend before neighbours touch. A global cap cannot fix it: spacing\n * is a property of the chart, and varies between sections of the same venue\n * (measured min 0.21 m on the amphitheatre against a 0.58 m mean).\n *\n * So the ceiling travels per seat, derived from that seat's own nearest\n * neighbour. A dot grows to hold its minimum pixel size and then STOPS,\n * whatever the distance. Past that point holding legibility is the LOD ladder's\n * job (fade toward the tier tint), not the dot's.\n */\n iMaxRadius: Float32Array;\n /**\n * vec3 per instance: accommodation ring colour, or (0,0,0) for none.\n *\n * 2D draws a coloured ring around every seat with an accessibility type, and\n * 3D drew nothing at all — so a wheelchair space, a companion seat or a\n * lift-armrest seat was indistinguishable from any other the moment a buyer\n * switched to the 3D view. A ring mirrors the 2D treatment exactly, needs no\n * texture, and costs one instanced attribute rather than a draw call.\n */\n iRing: Float32Array;\n /** float per instance: owning floor index, for per-floor isolation. */\n iFloor: Float32Array;\n /**\n * float per instance: facing yaw in radians, for the near-field chair mesh.\n *\n * A billboard dot has no front, so nothing here ever needed a direction. A\n * chair does, and getting it wrong is worse than drawing nothing — a row of\n * seats with their backs to the stage reads as a mistake immediately.\n *\n * Left at zero by this builder and filled by `sceneModel`, which is the layer\n * that knows what each seat FACES (its floor's focal point). Direct callers\n * and tests that build instances without a scene get zeros, which is a\n * well-defined \"facing +Z\" rather than an undefined attribute.\n */\n iYaw: Float32Array;\n /**\n * float per instance: half-width of this seat's near-field CHAIR, world metres.\n *\n * Deliberately not `iMaxRadius`. That value is the dot's ceiling — conservative\n * by design so round dots stay visually apart, and clamped to\n * SEAT_DOT_RADIUS_M on top of that. Sizing a chair from it gave 0.34 m chairs\n * under 0.95 m backs: an aspect ratio near 3:1, which reads as a headstone.\n * Real seats very nearly touch. See `chairHalfWidth`.\n */\n iChairWidth: Float32Array;\n /** 1 = physical chair; 0 = sellable empty wheelchair bay. */\n iPhysicalSeat: Float32Array;\n /** seatId → instance index (drives targeted availability updates). */\n idToIndex: Map<string, number>;\n}\n\n/**\n * Nearest-neighbour distance in chart units for every seat, via a uniform grid.\n *\n * Linear in seat count for realistic charts (the grid cell is sized to the mean\n * spacing, so each lookup touches a bounded neighbourhood) — the naive pairwise\n * version is 14k² on the Uber Arena and would show up in build time.\n */\nfunction nearestNeighbourSpacing(seats: ExpandedSeat[]): Float64Array {\n const n = seats.length;\n const out = new Float64Array(n);\n if (n < 2) { out.fill(Infinity); return out; }\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const s of seats) {\n if (s.x < minX) minX = s.x;\n if (s.y < minY) minY = s.y;\n if (s.x > maxX) maxX = s.x;\n if (s.y > maxY) maxY = s.y;\n }\n const w = Math.max(maxX - minX, 1e-6), h = Math.max(maxY - minY, 1e-6);\n // Aim at ~1 seat per cell: cell = sqrt(area / count).\n const cell = Math.max(Math.sqrt((w * h) / n), 1e-6);\n const cols = Math.max(1, Math.ceil(w / cell) + 1);\n const rows = Math.max(1, Math.ceil(h / cell) + 1);\n const buckets = new Map<number, number[]>();\n const cellOf = (x: number, y: number): number => {\n const cx = Math.min(cols - 1, Math.max(0, Math.floor((x - minX) / cell)));\n const cy = Math.min(rows - 1, Math.max(0, Math.floor((y - minY) / cell)));\n return cy * cols + cx;\n };\n for (let i = 0; i < n; i++) {\n const k = cellOf(seats[i].x, seats[i].y);\n const b = buckets.get(k);\n if (b) b.push(i); else buckets.set(k, [i]);\n }\n for (let i = 0; i < n; i++) {\n const s = seats[i];\n const cx = Math.min(cols - 1, Math.max(0, Math.floor((s.x - minX) / cell)));\n const cy = Math.min(rows - 1, Math.max(0, Math.floor((s.y - minY) / cell)));\n let best = Infinity;\n // Widen the ring until a hit is found — a sparse region (a lone box seat)\n // would otherwise report Infinity and lose its ceiling entirely.\n for (let r = 1; r <= 4; r++) {\n for (let gy = cy - r; gy <= cy + r; gy++) {\n if (gy < 0 || gy >= rows) continue;\n for (let gx = cx - r; gx <= cx + r; gx++) {\n if (gx < 0 || gx >= cols) continue;\n // Only the newly-added ring after the first pass.\n if (r > 1 && Math.abs(gy - cy) < r && Math.abs(gx - cx) < r) continue;\n const b = buckets.get(gy * cols + gx);\n if (!b) continue;\n for (const j of b) {\n if (j === i) continue;\n const d = Math.hypot(seats[j].x - s.x, seats[j].y - s.y);\n if (d < best) best = d;\n }\n }\n }\n if (Number.isFinite(best) && best <= r * cell) break;\n }\n out[i] = best;\n }\n return out;\n}\n\n/**\n * Resolve a seat's surface height in world metres — the fallback path used only\n * when no venue surface was supplied (direct callers, tests). Prefer the\n * section-resolved eye height minus the seated-eye offset.\n *\n * There is deliberately NO lift constant here any more. A dot is placed exactly\n * ON the deck, and the seat shader offsets the billboard by its own radius along\n * screen-up so the dot RESTS on that point at every distance. A fixed lift could\n * not do that: the shader enforces `uMinPixels`, so a dot's world radius grows\n * with depth and any constant clearance is eventually smaller than the dot.\n */\nfunction seatSurfaceY(seat: ExpandedSeat): number {\n const eye = seat.eyeHeightM;\n if (Number.isFinite(eye)) return Math.max(0, (eye as number) - SEATED_EYE_HEIGHT_M);\n return 0;\n}\n\nexport function buildSeatInstances(\n seats: ExpandedSeat[],\n initial?: (seat: ExpandedSeat) => SeatState3D,\n surfaces?: VenueSurfaces,\n seatFloor?: Float32Array,\n /** categoryKey -> display colour, for the AVAILABLE tint. Omitted by direct\n * callers and tests, which then get the flat state green as before. */\n categoryColor?: Map<string, string>,\n): SeatInstanceData {\n const count = seats.length;\n const iPosition = new Float32Array(count * 3);\n const iState = new Float32Array(count);\n const iCategory = new Float32Array(count * 3);\n const iMaxRadius = new Float32Array(count);\n const iChairWidth = new Float32Array(count);\n const iPhysicalSeat = new Float32Array(count);\n const iRing = new Float32Array(count * 3);\n const idToIndex = new Map<string, number>();\n const spacing = nearestNeighbourSpacing(seats);\n for (let i = 0; i < count; i++) {\n const seat = seats[i];\n // The seat's OWN row pitch when its section resolved into rows; the raw\n // nearest-neighbour distance only as a fallback for unstructured seats.\n // See `VenueSurfaces.seatPitchU` for why the raw measure is wrong.\n const resolved = surfaces?.seatPitchU(i);\n const pitchM = (resolved ?? spacing[i]) * M;\n // The ceiling is allowed BELOW the base radius. A chart authored tighter\n // than the 0.22 m default dot (measured 0.21 m minimum spacing on the\n // amphitheatre) overlaps at every distance today, including close up, and\n // shrinking those dots is the only thing that separates them. A small floor\n // keeps a pathologically dense chart's seats visible rather than vanishing.\n // A seat with no resolvable neighbour keeps the base radius, not an\n // unbounded one.\n iMaxRadius[i] = Number.isFinite(pitchM)\n ? Math.max(0.06, Math.min(SEAT_DOT_RADIUS_M, pitchM * SEAT_PITCH_FRACTION))\n : SEAT_DOT_RADIUS_M;\n // The chair reads the SAME pitch but through its own rule — see iChairWidth.\n iChairWidth[i] = chairHalfWidth(Number.isFinite(pitchM) ? pitchM : undefined);\n iPhysicalSeat[i] = seat.wheelchairSpaceType === 'no-seat' ? 0 : 1;\n iPosition[i * 3] = seat.x * M;\n // The venue surface wins when present: it is the same function the tier cap\n // is built from, which is what guarantees a seat can never sit inside it.\n iPosition[i * 3 + 1] = surfaces ? surfaces.seatDeckY(i) : seatSurfaceY(seat);\n iPosition[i * 3 + 2] = seat.y * M;\n iState[i] = seatStateIndex(initial ? initial(seat) : 'available');\n // The seat's CATEGORY colour, which is what 2D paints a free seat with\n // (`SeatmapRenderer.paintSeat` -> `seatBaseColor(categoryKey)`). 3D painted\n // every available seat one flat green instead, so a venue lost its price\n // tiers the moment a buyer switched view — and at seat level a hall of\n // identical green read as a diagram rather than a room. Falls back to the\n // same #6e7bff 2D uses when a category has no colour.\n const cat = hexToRgb(categoryColor?.get(seat.categoryKey) ?? '#6e7bff') ?? [0.43, 0.48, 1.0];\n iCategory[i * 3] = cat[0];\n iCategory[i * 3 + 1] = cat[1];\n iCategory[i * 3 + 2] = cat[2];\n // Same source of truth as 2D: the ring colour is derived from the seat's\n // accessibility types, so the two views cannot disagree about which seat is\n // an accessible one.\n if (seat.accessibility?.length) {\n const rgb = hexToRgb(accessibilityRingColor(seat.accessibility));\n if (rgb) { iRing[i * 3] = rgb[0]; iRing[i * 3 + 1] = rgb[1]; iRing[i * 3 + 2] = rgb[2]; }\n }\n idToIndex.set(seat.id, i);\n }\n return {\n count, iPosition, iState, iCategory, iMaxRadius, iRing, idToIndex, iChairWidth, iPhysicalSeat,\n iFloor: seatFloor ?? new Float32Array(count),\n iYaw: new Float32Array(count),\n };\n}\n\n/** Contiguous run of instance indices to upload in a single bufferSubData call. */\nexport interface DirtyRun {\n start: number;\n /** number of instances (floats, since iState is 1 float/instance). */\n length: number;\n}\n\n/**\n * Instances worth re-uploading just to avoid another GL call.\n *\n * Runs are merged across gaps up to this size. A `bufferSubData` has fixed\n * driver overhead regardless of how few bytes it carries, so uploading a handful\n * of unchanged instances inside one call is far cheaper than issuing a second.\n *\n * The case this exists for is a live socket delta on a big venue, where the\n * changed seats are SCATTERED rather than contiguous. Measured on a 50,400-seat\n * chart before merging: 1,000 scattered seats produced 983 separate uploads and\n * 5,000 produced 4,438 — thousands of GL calls in a single frame to move a few\n * kilobytes. A block going on sale already coalesced well; random churn did not.\n */\nconst RUN_MERGE_GAP = 64;\n\n/**\n * Apply state updates to the CPU `iState` array and return the coalesced\n * contiguous runs that changed — the caller uploads exactly those ranges and\n * never the whole buffer.\n */\nexport function applySeatStates(\n data: SeatInstanceData,\n updates: Array<{ seatId: string; state: SeatState3D }>,\n): DirtyRun[] {\n const changed: number[] = [];\n for (const u of updates) {\n const idx = data.idToIndex.get(u.seatId);\n if (idx === undefined) continue;\n const v = seatStateIndex(u.state);\n if (data.iState[idx] !== v) {\n data.iState[idx] = v;\n changed.push(idx);\n }\n }\n if (!changed.length) return [];\n changed.sort((a, b) => a - b);\n const runs: DirtyRun[] = [];\n let start = changed[0];\n let prev = changed[0];\n for (let i = 1; i < changed.length; i++) {\n const idx = changed[i];\n if (idx === prev) continue;\n // Bridge small gaps: the unchanged instances in between are rewritten from\n // `iState`, which already holds their current value, so this is a no-op for\n // them and saves a GL call. See RUN_MERGE_GAP.\n if (idx <= prev + RUN_MERGE_GAP) { prev = idx; continue; }\n runs.push({ start, length: prev - start + 1 });\n start = idx;\n prev = idx;\n }\n runs.push({ start, length: prev - start + 1 });\n return runs;\n}\n","/**\n * Resolve a chart's authored theme into the 3D scene's colours.\n *\n * Every chart already carries a `ChartTheme` — background, brand accent, seat\n * scale — and the 2D renderer and the picker chrome honour it. The 3D view did\n * not: `palette.ts` was a fixed set of constants, so a white-labelled event\n * rendered in SeatLayer's own dark grey whatever the organizer had branded. That\n * is a visible gap in a paid feature, and it is the kind of thing a customer\n * notices immediately when they switch from the 2D map to the 3D view.\n *\n * The palette in `palette.ts` stays the DEFAULT and the reference. This module\n * only rebases it, so an unthemed chart is byte-for-byte what it was.\n *\n * ## How structure is rebased\n *\n * Structure colours are not replaced by the brand colour — a venue rendered in\n * flat brand paint reads as a diagram, not a building, and the look brief is\n * deliberately \"desaturated greys for structure, saturated colour only on\n * seats\". Instead each structure colour is blended a little way toward the\n * authored background, so the whole venue picks up the brand's cast and sits in\n * its own light, while keeping the tonal relationships (tier above wall, stage\n * warmer than tier) that make the geometry readable.\n */\n\nimport type { ChartTheme } from '../core/types';\nimport { BACKGROUND, SEAT_STATE_COLORS, STRUCTURE, hexToRgb, mix, scaleRgb, type RGB, type SeatState3D } from './palette';\n\n/** How far a structure colour is pulled toward the authored background. */\nconst BACKGROUND_INFLUENCE = 0.15;\n\n/**\n * The background gradient's two stops as multiples of the authored colour.\n *\n * Fitted to the existing hand-tuned gradient on the default `#0e1117`, whose\n * per-channel ratios are 0.91/0.90/0.89 for the top stop and 1.82/1.80/1.66 for\n * the bottom. A single scalar cannot reproduce a hand-picked triple exactly, so\n * these are the best fit: an authored background lands within ~0.01 of the old\n * look, and an UNTHEMED chart bypasses this path entirely and stays identical.\n * What matters is that a themed chart gets the same vertical falloff around its\n * own colour rather than a flat wash.\n */\nconst BG_TOP_SCALE = 0.9;\nconst BG_BOTTOM_SCALE = 1.76;\n\n/** Bounds on the authored seat-size multiplier, matching `ChartTheme.seatScale`. */\nconst SEAT_SCALE_MIN = 0.7;\nconst SEAT_SCALE_MAX = 1.6;\n\nexport interface Theme3D {\n background: { top: RGB; bottom: RGB };\n structure: typeof STRUCTURE;\n seatStates: Record<SeatState3D, RGB>;\n /** Multiplier on the seat dot's world radius. */\n seatScale: number;\n}\n\n/** The unthemed default — the palette exactly as authored in `palette.ts`. */\nexport function defaultTheme3D(): Theme3D {\n return {\n background: { top: [...BACKGROUND.top] as RGB, bottom: [...BACKGROUND.bottom] as RGB },\n structure: STRUCTURE,\n seatStates: { ...SEAT_STATE_COLORS },\n seatScale: 1,\n };\n}\n\nexport function resolveTheme3D(theme: ChartTheme | undefined): Theme3D {\n const base = defaultTheme3D();\n if (!theme) return base;\n\n const bg = hexToRgb(theme.background);\n if (bg) {\n base.background = {\n top: scaleRgb(bg, BG_TOP_SCALE),\n bottom: scaleRgb(bg, BG_BOTTOM_SCALE),\n };\n // Rebase every structure colour onto the authored background. Done as one\n // pass over the palette rather than field by field, so a colour added to\n // STRUCTURE later is themed automatically instead of silently staying fixed.\n const rebased: Record<string, RGB> = {};\n for (const [key, value] of Object.entries(STRUCTURE)) {\n rebased[key] = mix(value as RGB, bg, BACKGROUND_INFLUENCE);\n }\n base.structure = rebased as unknown as typeof STRUCTURE;\n }\n\n // Selection is the one seat colour a brand owns: it is the buyer's own\n // choice reflected back, and the picker chrome already paints it in `accent`.\n // Availability, held and sold stay fixed — they carry MEANING, and letting a\n // brand recolour \"sold\" would let a chart mislead about what is for sale.\n const selection = hexToRgb(theme.selectionColor) ?? hexToRgb(theme.accent);\n if (selection) base.seatStates = { ...base.seatStates, selected: selection };\n\n const scale = theme.seatScale;\n if (typeof scale === 'number' && Number.isFinite(scale)) {\n base.seatScale = Math.min(SEAT_SCALE_MAX, Math.max(SEAT_SCALE_MIN, scale));\n }\n return base;\n}\n\n/** Flat LUT (5 × vec3) for the seat fragment shader, in `SEAT_STATES` order. */\nexport function themeSeatColorLUT(theme: Theme3D, order: readonly SeatState3D[]): number[] {\n const out: number[] = [];\n for (const s of order) out.push(...theme.seatStates[s]);\n return out;\n}\n","/**\n * Venue labels — anchors, level-of-detail, and world→screen projection.\n *\n * ## Why labels are DOM, not geometry\n *\n * The renderer is deliberately texture-free at three draw calls. Drawing text on\n * the GPU means a signed-distance font atlas: a texture, another shader, a build\n * asset, and a resolution ceiling — a lot of machinery for the few dozen labels a\n * venue actually needs. Projecting anchors and positioning DOM elements costs\n * nothing when a chart has no labels, and buys properties the GPU path cannot:\n *\n * - **Real text.** A screen reader can read the venue's structure. That is the\n * accessibility gap in 3D, not just a rendering convenience.\n * - Crisp at any device pixel ratio and any zoom, with no atlas to outgrow.\n * - `ChartTheme.fontFamily`, i18n and RTL come from the browser.\n *\n * This module is the pure half — what to label, where its anchor sits, and when\n * it should show. The overlay that positions elements lives in `index.ts`.\n *\n * ## Why the rungs mirror 2D\n *\n * 2D melts through zones → sections → seats. Labels follow the same idea for the\n * same reason: at a distance a buyer needs to know which part of the venue they\n * are looking at, and up close they need to know which block and which door. A\n * label set that does not thin out with distance turns a 51-section arena into\n * unreadable confetti.\n */\n\nimport type { Point } from '../core/types';\n\nexport type LabelKind = 'zone' | 'section' | 'ga' | 'annotation' | 'booth' | 'row' | 'seat';\n\nexport interface SceneLabel {\n id: string;\n kind: LabelKind;\n text: string;\n /** World-metre anchor the label is pinned to. */\n anchor: [number, number, number];\n /** Authored colour (`#rrggbb`), when the object carries one. */\n color?: string;\n /** Authored rotation in degrees, for annotations that specify one. */\n rotation?: number;\n}\n\n/**\n * Distance thresholds as multiples of the venue radius, matching the seat LOD's\n * scale so labels and seats thin out together rather than fighting.\n *\n * Zone labels are the far rung and switch OFF close in, where they would sit on\n * top of the section labels that have become more useful. Section labels are the\n * middle rung. Annotations and booth labels are wayfinding — only legible, and\n * only wanted, once the buyer is actually in that part of the venue.\n */\nconst ZONE_MIN_DISTANCE = 1.15;\nconst SECTION_MAX_DISTANCE = 2.2;\nconst NEAR_MAX_DISTANCE = 0.85;\n/**\n * Row and seat identity are the last two rungs, mirroring 2D's melt: a buyer\n * this close has stopped choosing a part of the venue and started choosing a\n * place to sit. Seats come in last of all — a seat number is unreadable and\n * useless until you are close enough that one row fills much of the screen.\n */\nconst ROW_MAX_DISTANCE = 0.5;\nconst SEAT_MAX_DISTANCE = 0.26;\n\n/** Which label kinds should show at this camera distance. */\nexport function visibleLabelKinds(distance: number, venueRadius: number): Set<LabelKind> {\n const r = Math.max(1e-6, venueRadius);\n const d = distance / r;\n const out = new Set<LabelKind>();\n if (d >= ZONE_MIN_DISTANCE) out.add('zone');\n // A GA area is named at the same rung as a section because that is what it is\n // — a sellable part of the venue the buyer picks before picking a place in it.\n if (d <= SECTION_MAX_DISTANCE) { out.add('section'); out.add('ga'); }\n if (d <= NEAR_MAX_DISTANCE) { out.add('annotation'); out.add('booth'); }\n if (d <= ROW_MAX_DISTANCE) out.add('row');\n if (d <= SEAT_MAX_DISTANCE) out.add('seat');\n return out;\n}\n\nexport interface Projected {\n /** CSS pixels from the container's left/top. */\n x: number;\n y: number;\n /** Normalised depth; smaller is nearer. */\n depth: number;\n /** False when the anchor is behind the camera or outside the frustum. */\n visible: boolean;\n}\n\n/**\n * Project a world point through a column-major 4x4 view-projection matrix.\n *\n * Behind-camera points are reported invisible rather than mirrored to the far\n * side of the screen, which is what a naive divide by a negative w produces —\n * a label for the section behind you appearing over the stage in front of you.\n */\nexport function projectToScreen(\n viewProjection: ArrayLike<number>,\n p: readonly [number, number, number],\n width: number,\n height: number,\n): Projected {\n const m = viewProjection;\n const x = p[0], y = p[1], z = p[2];\n const cx = m[0] * x + m[4] * y + m[8] * z + m[12];\n const cy = m[1] * x + m[5] * y + m[9] * z + m[13];\n const cz = m[2] * x + m[6] * y + m[10] * z + m[14];\n const cw = m[3] * x + m[7] * y + m[11] * z + m[15];\n if (!(cw > 1e-6)) return { x: 0, y: 0, depth: Infinity, visible: false };\n const ndcX = cx / cw, ndcY = cy / cw, ndcZ = cz / cw;\n const inside = ndcX >= -1.05 && ndcX <= 1.05 && ndcY >= -1.05 && ndcY <= 1.05 && ndcZ <= 1;\n return {\n x: (ndcX * 0.5 + 0.5) * width,\n y: (1 - (ndcY * 0.5 + 0.5)) * height,\n depth: ndcZ,\n visible: inside,\n };\n}\n\n/**\n * Drop labels that would overlap, nearest kept.\n *\n * Without this a dense venue paints its section names on top of each other and\n * every one of them becomes unreadable — worse than showing fewer. Nearest-wins\n * because the label a buyer is closest to is the one they are asking about.\n *\n * The test is RECTANGULAR, not radial. A label is a line of text: wide and\n * short. A single radius big enough to stop two names colliding side by side is\n * far bigger than the vertical gap they actually need, so a radial test threw\n * away labels that were stacked but perfectly readable — on the amphitheatre it\n * dropped \"Terrace\" purely for sitting between the other two concentric zones.\n */\nexport function cullOverlapping<T extends { screen: Projected }>(\n items: T[],\n separationX: number,\n separationY: number = separationX,\n): T[] {\n const kept: T[] = [];\n const ordered = [...items].sort((a, b) => a.screen.depth - b.screen.depth);\n for (const item of ordered) {\n let clash = false;\n for (const k of kept) {\n const dx = Math.abs(item.screen.x - k.screen.x);\n const dy = Math.abs(item.screen.y - k.screen.y);\n // Only a genuine box overlap counts: near on BOTH axes.\n if (dx < separationX && dy < separationY) { clash = true; break; }\n }\n if (!clash) kept.push(item);\n }\n return kept;\n}\n\n/**\n * How many of the dense per-place labels may be painted at once.\n *\n * The overlap cull above is necessary and NOT sufficient, and the gap between\n * those two is a real defect: it only removes labels that collide, so the closer\n * the camera gets the FEWER collisions there are and the MORE labels survive.\n * Flown to a seat in a 3,605-seat amphitheatre, every row and seat name in the\n * frustum passed the test and the frame became a wall of tiled text with the\n * seating unreadable behind it — the labels stopped describing the venue and\n * became the thing obscuring it.\n *\n * A budget is the missing rung. A buyer arriving at their seat wants the handful\n * of names around them, not an index of the building; past a dozen or so the set\n * has no informational value left, only cost.\n */\nexport const DENSE_LABEL_BUDGET: Record<'row' | 'seat', number> = { row: 8, seat: 14 };\n\n/**\n * Order dense labels by how much the buyer is likely to care: near the camera\n * and near the middle of the frame first.\n *\n * Both terms are needed. Distance alone keeps labels for seats behind the\n * camera's shoulder that happen to be metres away; screen-centre alone keeps a\n * label for a seat on the far side of the venue purely for being dead ahead. The\n * product ranks \"what I am looking at, close to me\" top, which is the seat a\n * buyer just flew to.\n */\nexport function focusScore(\n screen: Projected,\n worldDistance: number,\n width: number,\n height: number,\n): number {\n const half = Math.max(1, Math.min(width, height) * 0.5);\n const off = Math.min(2, Math.hypot(screen.x - width / 2, screen.y - height / 2) / half);\n return worldDistance * (1 + 1.5 * off);\n}\n\n/**\n * Pick the dense labels to paint: rank by focus, drop overlaps in that order,\n * then take at most `budget`.\n *\n * Order matters. Culling overlaps FIRST and then truncating would let an\n * arbitrary far-corner label win a slot over the seat the buyer is sitting in,\n * because the overlap pass keeps whatever it happens to reach first.\n */\nexport function pickDenseLabels<T extends { screen: Projected; focus: number; priority?: number }>(\n items: T[],\n separationX: number,\n separationY: number,\n budget: number,\n): T[] {\n // A selected seat is an identity, not decorative venue copy. Give explicit\n // priorities first refusal before proximity ranking so a neighbouring row's\n // label can never make the highlighted chair appear to have another ID.\n const ordered = [...items].sort((a, b) => (\n (b.priority ?? 0) - (a.priority ?? 0) || a.focus - b.focus\n ));\n const kept: T[] = [];\n for (const item of ordered) {\n if (kept.length >= budget) break;\n let clash = false;\n for (const k of kept) {\n if (Math.abs(item.screen.x - k.screen.x) < separationX\n && Math.abs(item.screen.y - k.screen.y) < separationY) { clash = true; break; }\n }\n if (!clash) kept.push(item);\n }\n return kept;\n}\n\n/** Mean of a point set, or null when empty. */\nexport function centroidOf(points: readonly Point[]): Point | null {\n if (!points.length) return null;\n let x = 0, y = 0;\n for (const p of points) { x += p.x; y += p.y; }\n return { x: x / points.length, y: y / points.length };\n}\n","/**\n * The JS-side source of truth for the 3D scene — a pure, GPU-free description\n * built once from the chart's existing height contract. Everything the renderer\n * uploads (merged solid geometry, the instanced seat cloud, camera-framing\n * bounds, the seat-state colour LUT) is derived here, so it survives a WebGL\n * context loss: on `webglcontextrestored` the renderer simply re-uploads from\n * this model without recomputing anything.\n *\n * Feeds 100% from `sectionGeometry` / `Floor.baseHeightM` / `ExpandedSeat`\n * (docs/3d-program-workorder §Architecture) — no new chart data is invented.\n */\n\nimport type { ChartDoc, ChartObject, ExpandedSeat, Point, SectionObject, ShapeObject } from '../../core/types';\nimport { CHART_UNITS_PER_METRE } from '../../core/units';\nimport { SEAT_STATES, STRUCTURE, hexToRgb, mix, desaturate, scaleRgb, type RGB } from '../palette';\nimport { resolveTheme3D, themeSeatColorLUT, type Theme3D } from '../theme';\nimport { centroidOf, type SceneLabel } from '../labels';\nimport {\n MeshBuilder, extrudePrism, mergeMeshData, ellipsePolygon, rectPolygon, outsetRing, M, type MeshData,\n} from './geometry';\nimport polygonClipping from 'polygon-clipping';\nimport { buildSeatInstances, SEAT_DOT_RADIUS_M, type SeatInstanceData } from './seatInstances';\nimport { computeSeatYaw } from './seatChair';\nimport { buildVenueSurfaces, CAP_MAX_ERROR_M, type SectionSurface } from './surface';\n\n/** Axis-aligned stage footprint in world metres. */\nexport interface StageBounds {\n cx: number;\n cz: number;\n y: number;\n halfX: number;\n halfZ: number;\n}\nimport { emitDeckBands, deckFootprints, rowNeighbourhoods } from './deckBands';\n\n/** One resolved plane of geometry — a single-floor chart is one of these. */\ninterface FloorUnit {\n objects: ChartObject[];\n focal: Point;\n baseHeightM: number;\n}\n\n/**\n * A navigable zone — the venue's own top-level grouping (Orchestra, Lower Bowl,\n * Hall A), resolved for camera framing and for the section/zone LOD rung.\n *\n * Every shipped chart authors zones and gives every section one, and the 2D\n * renderer uses them as its farthest LOD rung. 3D ignored them entirely, so the\n * one structure a buyer navigates by (\"take me to the Grand Circle\") had no\n * representation at all in the 3D view.\n */\nexport interface SceneZone {\n id: string;\n label: string;\n /** Authored zone colour, or null when the chart leaves it to the category mix. */\n color: RGB | null;\n /** Section object ids belonging to this zone. */\n sectionIds: string[];\n /** Seats resolved into this zone. */\n seatCount: number;\n /** World-metre centre of the zone's seats (camera target). */\n center: [number, number, number];\n /** Half-diagonal of its footprint, world metres (camera fit). */\n radius: number;\n /** What this zone faces — its authored focal, else the venue's. */\n focalWorld: [number, number, number];\n}\n\n/**\n * One seated section, resolved for navigation — the middle rung of the venue\n * ladder, between a zone and a seat. A chart with no zones authored still has\n * sections, so this is the level at which every venue can be navigated.\n */\nexport interface SceneSection {\n id: string;\n /** Buyer-facing name, matching the section label. */\n label: string;\n seatCount: number;\n /** World-metre centre of the section's seats (camera target). */\n center: [number, number, number];\n /** Half-diagonal of its seat footprint, world metres (camera fit). */\n radius: number;\n /** What this section faces — the camera approaches from this side. */\n focalWorld: [number, number, number];\n}\n\n/** One authored row resolved for guided large-venue navigation. */\nexport interface SceneRow {\n id: string;\n label: string;\n sectionId?: string;\n seatCount: number;\n center: [number, number, number];\n radius: number;\n}\n\n/** A navigable floor — a logical level of the venue. */\nexport interface SceneFloor {\n index: number;\n id: string;\n label: string;\n seatCount: number;\n center: [number, number, number];\n radius: number;\n}\n\nexport interface SceneModel {\n /** Every non-seat surface merged into one triangle soup (1 draw call). */\n solids: MeshData;\n seats: SeatInstanceData;\n bounds: {\n /** World-metre venue centre (camera target). */\n center: [number, number, number];\n /** Half-diagonal of the horizontal footprint, metres (camera fit). */\n radius: number;\n groundY: number;\n };\n /** Exact enclosing sphere for rendered solids plus near-field seat detail. */\n renderBounds: {\n center: [number, number, number];\n radius: number;\n };\n /** 5 × vec3 flat LUT for the seat fragment shader. */\n stateColorLUT: number[];\n /** Resolved colours for this chart's authored theme (background, seat scale). */\n theme: Theme3D;\n seatCount: number;\n /** Venue focal point in world metres (cinematic look-at target). */\n focalWorld: [number, number, number];\n /** The venue's zones, in authored order. Empty when the chart has none. */\n zones: SceneZone[];\n /**\n * Stage footprints in world metres.\n *\n * Collected for stage lighting, which is not shipped: additive cones were\n * built, placed correctly and still could not be read from a seat (see\n * `git log` for `stageLight.ts`). The bounds stay because they are cheap,\n * correct, and exactly what a second attempt needs — the geometry was never\n * the hard part, the technique was.\n */\n stages: StageBounds[];\n /** Seated sections, in authored order — the navigable middle rung. */\n sections: SceneSection[];\n /** Rows used by the Section → Row → Seat locator. */\n rows: SceneRow[];\n /**\n * The venue's floors, in authored order. A single-floor chart reports one.\n *\n * Every shipped multi-floor template puts all its floors at baseHeightM 0 and\n * takes height from the sections instead, so floors are a logical grouping,\n * not a physical stack. What they need is ISOLATION: draw all three of an\n * opera house at once and the balcony sits over the parterre.\n */\n floors: SceneFloor[];\n /**\n * Everything worth naming, with a world anchor: zones, sections, authored\n * wayfinding text, and booths. Rendered as a DOM overlay — see `labels.ts` for\n * why text is not geometry here.\n */\n labels: SceneLabel[];\n}\n\nexport function shapeVisibleInConfiguration(\n shape: Pick<ShapeObject, 'eventConfigurationIds'>,\n activeConfigurationId?: string,\n): boolean {\n const ids = shape.eventConfigurationIds;\n return !ids?.length || (!!activeConfigurationId && ids.includes(activeConfigurationId));\n}\n\nfunction visibleObjects(objects: ChartObject[], activeConfigurationId?: string): ChartObject[] {\n return objects.filter((object) => (\n object.type !== 'shape' || shapeVisibleInConfiguration(object, activeConfigurationId)\n ));\n}\n\nfunction floorUnits(doc: ChartDoc, activeConfigurationId?: string): FloorUnit[] {\n if (doc.floors?.length) {\n return doc.floors.map((f) => ({\n objects: visibleObjects(f.objects, activeConfigurationId),\n focal: f.focalPoint ?? doc.focalPoint,\n baseHeightM: f.baseHeightM ?? 0,\n }));\n }\n return [{ objects: visibleObjects(doc.objects, activeConfigurationId), focal: doc.focalPoint, baseHeightM: 0 }];\n}\n\nconst AO = { top: 1.0, wallBottom: 0.5, bottomCap: 0.4 };\n\n// The cap's tessellation tolerance lives beside the surface it approximates\n// (surface.ts), together with the seat clearance derived from it.\n\n/**\n * Fold a surface's 2D fill colour into the dark structure palette: desaturate\n * ~40 %, darken, then ground it in the neutral structure grey so a tier top\n * reads architectural — a recognisable hue (purple/green/orange) but muted, not\n * candy-coloured paint. Risers/walls stay neutral concrete; baked AO still\n * multiplies these per vertex downstream. `null` fill ⇒ the neutral grey.\n */\nfunction tintTop(fill: RGB | null, neutral: RGB): RGB {\n if (!fill) return neutral;\n const muted = scaleRgb(desaturate(fill, 0.4), 0.62);\n return mix(neutral, muted, 0.72);\n}\n\n/**\n * Resolve each section's 2D paint colour, keyed by logical section id\n * (`logicalSectionId ?? id`, matching how expanded seats attribute `sectionId`):\n * the count-weighted mix of its member seats' category colours — the same source\n * the 2D renderer blends into a section's block fill. An explicit `section.color`\n * override is applied later (it wins in `sectionFill`).\n */\nfunction resolveSectionFills(doc: ChartDoc, seats: ExpandedSeat[]): Map<string, RGB> {\n const catColor = new Map<string, string>();\n for (const c of doc.categories ?? []) catColor.set(c.key, c.color);\n const counts = new Map<string, Map<string, number>>();\n for (const s of seats) {\n if (!s.sectionId) continue;\n let m = counts.get(s.sectionId);\n if (!m) { m = new Map(); counts.set(s.sectionId, m); }\n m.set(s.categoryKey, (m.get(s.categoryKey) ?? 0) + 1);\n }\n const out = new Map<string, RGB>();\n for (const [sid, byCat] of counts) {\n let r = 0, g = 0, b = 0, w = 0;\n for (const [key, n] of byCat) {\n const rgb = hexToRgb(catColor.get(key));\n if (!rgb) continue;\n r += rgb[0] * n; g += rgb[1] * n; b += rgb[2] * n; w += n;\n }\n if (w > 0) out.set(sid, [r / w, g / w, b / w]);\n }\n return out;\n}\n\n/** A section's fill: explicit `color` override wins, else the member-category mix. */\nfunction sectionFill(section: SectionObject, byLogical: Map<string, RGB>): RGB | null {\n return hexToRgb(section.color) ?? byLogical.get(section.logicalSectionId ?? section.id) ?? null;\n}\n\n/**\n * Extrude one section into the shared builder, capped by ITS OWN seating surface.\n *\n * The height function is not recomputed here — it comes from `surface.ts`, the\n * same object the seat dots stand on. That is the whole point: there is one\n * definition of the seating surface, and the cap, the walls and the seats all\n * read it, so no cross-file offset constant has to be kept in agreement.\n */\ninterface RingBox { minX: number; minY: number; maxX: number; maxY: number }\n\nfunction bboxOfRing(ring: readonly Point[]): RingBox {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const p of ring) {\n if (p.x < minX) minX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.x > maxX) maxX = p.x;\n if (p.y > maxY) maxY = p.y;\n }\n return { minX, minY, maxX, maxY };\n}\n\nfunction boxesOverlap(a: RingBox, b: RingBox): boolean {\n return a.minX <= b.maxX && b.minX <= a.maxX && a.minY <= b.maxY && b.minY <= a.maxY;\n}\n\n/**\n * A section's padded outline with every OTHER section's authored outline removed.\n *\n * Deck padding grows a section by 0.33 m so a seat authored hard against its\n * boundary still rests on deck. Where two sections abut, that growth crosses into\n * the neighbour — and if the neighbour is lower, the overhang covers its seats.\n * Measured on the concert hall: 27 stalls-terrace seats buried by 0.20 m beneath\n * the terrace beside them, which sits 0.60 m higher.\n *\n * Clipping to the section's own padded ring is not enough, because the padding IS\n * the intrusion. The rule that works is: grow to hold your own seats, but never\n * over another section's floor.\n */\nfunction paddedFootprint(section: SectionObject, siblings: readonly SectionObject[]): Point[][] {\n const padded = outsetRing(section.outline, SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE);\n // Only siblings whose bounding box actually overlaps can remove any area, and\n // a polygon boolean is expensive. Differencing against EVERY sibling made\n // scene build quadratic in section count: measured 2.7 s at 50k seats / 40\n // sections and 11.7 s at 99k / 60, where doubling the venue cost 4.3x the\n // time. A venue's sections are laid out around it, so almost no pair overlaps\n // and this prunes nearly all of the work.\n const box = bboxOfRing(padded);\n const others = siblings.filter((o) => o !== section\n && o.outline && o.outline.length >= 3\n && boxesOverlap(box, bboxOfRing(o.outline)));\n if (!others.length) return [padded];\n const toRing = (pts: readonly Point[]): [number, number][] => {\n const r = pts.map((p) => [p.x, p.y] as [number, number]);\n r.push(r[0]);\n return r;\n };\n try {\n const diff = polygonClipping.difference([toRing(padded)], ...others.map((o) => [toRing(o.outline)]));\n const out: Point[][] = [];\n for (const poly of diff) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push(pts);\n }\n return out.length ? out : [padded];\n } catch {\n return [padded];\n }\n}\n\n/** Intersect each block footprint with the section's own allowed area. */\nfunction clipFootprints(\n blocks: ReturnType<typeof deckFootprints>,\n allowed: Point[][],\n): ReturnType<typeof deckFootprints> {\n if (!allowed.length) return blocks;\n const toRing = (pts: readonly Point[]): [number, number][] => {\n const r = pts.map((p) => [p.x, p.y] as [number, number]);\n r.push(r[0]);\n return r;\n };\n const allowedPolys = allowed.map((a) => [toRing(a)]);\n const out: ReturnType<typeof deckFootprints> = [];\n for (const b of blocks) {\n // NOTE: a \"skip the clip when the block looks contained\" fast path was tried\n // here and reverted. Testing that the block's VERTICES lie inside the allowed\n // ring is not sound for a CONCAVE allowed region — and a section footprint is\n // routinely concave, since an arena wedge curves inward and siblings are\n // subtracted from it. An edge can bulge outside while every vertex is inside,\n // which buried seats on the arena. A sound test needs edge-intersection\n // checks, which is most of the cost of the boolean it would be avoiding.\n let pieces;\n try {\n pieces = polygonClipping.intersection([toRing(b.outline)], ...[allowedPolys.flat()]);\n } catch {\n out.push(b);\n continue;\n }\n for (const poly of pieces) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push({ outline: pts, holes: [], topY: b.topY });\n }\n }\n return out.length ? out : blocks;\n}\n\nfunction buildTier(\n builder: MeshBuilder,\n section: SectionObject,\n unit: FloorUnit,\n fill: RGB | null,\n surface: SectionSurface | undefined,\n claimed: ClaimedArea,\n siblings: readonly SectionObject[],\n S: typeof STRUCTURE,\n): void {\n if (!section.outline || section.outline.length < 3) return;\n const bottomY = unit.baseHeightM;\n const colTop = tintTop(fill, S.tierTop);\n if (!surface) return;\n const topY = (p: Point): number => surface.deckAt(p.x, p.y);\n // Pad the deck so a seat authored hard against the section boundary rests ON\n // its own section instead of overhanging into the aisle. Seats are chart data\n // and never move; the deck grows to meet them. 1.5x the dot footprint: enough\n // that a dot sitting exactly on the boundary still lands clear of the edge,\n // and small against real aisle widths.\n const footprint = paddedFootprint(section, siblings);\n const outline = footprint[0] ?? section.outline;\n\n // A section whose rows resolved into levels is drawn as GEOMETRY, not as a\n // tessellated field: a flat landing prism carrying one level ribbon per row,\n // with a riser between consecutive ribbons. See `deckBands.ts` for why a\n // refined cap cannot work once every row has to be level.\n if (surface.rowLevels.length >= 2) {\n // One base prism per BLOCK, hugging the ribbons above it, instead of one flat\n // plate over the section's whole authored outline. The outline reaches well\n // past the seating, so a single plate stuck out around the stands as a slab\n // and cut every block's end square through it. Each block also stands on its\n // OWN lowest row rather than the section's.\n // Clipped to the section's own footprint like the treads are: a block base is\n // built from a ribbon union carrying its own outward margin, so it overhangs\n // for the same reason and buries a lower neighbour's seats the same way.\n // One neighbourhood solve, shared by the footprint and the emission below.\n const nbrs = rowNeighbourhoods(surface.rowLevels);\n const blocks = clipFootprints(deckFootprints(surface.rowLevels, unit.focal, nbrs), footprint);\n // Explicit UP normals rather than face normals. A block footprint is a\n // boolean union of ~100 overlapping ribbons, and such a union always leaves\n // some very thin triangles along its seams; deriving a normal from one by\n // cross product is numerically meaningless and shades as a dark hairline.\n // The top is FLAT, so the correct normal is known outright and the thin\n // triangles become harmless.\n const capUp = (): [number, number, number] => [0, 1, 0];\n for (const b of blocks) {\n // The base cap occupies the same footprint as the first row's tread. If\n // both sit at `b.topY`, thousands of coplanar triangles compete in the\n // depth buffer and turn into a black radial comb as the camera zooms out.\n // Recess only the structural cap by 2 cm; seats and row treads remain at\n // their exact authored levels, and the small exposed nosing reads as a\n // normal construction joint rather than a floating deck.\n const baseCapY = Math.max(bottomY, b.topY - 0.02);\n // Neighbouring section padding can still make two block bases overlap in\n // a narrow strip. At the same level that is genuine z-fighting, so give\n // each coplanar block exclusive ownership just like the flat-cap path.\n for (const ring of claimed.subtract(b.outline, baseCapY)) {\n extrudePrism(builder, ring, b.holes, () => baseCapY, bottomY,\n colTop, S.tierWall, AO, undefined, capUp);\n }\n }\n // Nothing resolved (a degenerate row set) — fall back to the outline so the\n // section is never simply missing.\n if (!blocks.length) {\n extrudePrism(builder, outline, section.holes, () => surface.landingY, bottomY,\n colTop, S.tierWall, AO);\n }\n const bandColors = {\n tread: [colTop[0] * AO.top, colTop[1] * AO.top, colTop[2] * AO.top] as RGB,\n // Risers read as the structure they are, a shade below their tread, which\n // is what makes the stepping legible from a low angle.\n riser: [colTop[0] * 0.72, colTop[1] * 0.72, colTop[2] * 0.72] as RGB,\n };\n // `paddedFootprint` may return several disjoint polygons after neighbouring\n // sections are subtracted. Flattening those rings into one point list draws\n // synthetic connector edges between them; polygon clipping then cuts long\n // triangular wedges through an arena deck. Clip the same ribbons once per\n // real component instead. The components are disjoint, so their emitted\n // treads cannot overlap or z-fight.\n for (const allowed of footprint.length ? footprint : [outline]) {\n emitDeckBands(builder, surface.rowLevels, unit.focal, surface.landingY,\n bandColors, allowed, nbrs);\n }\n return;\n }\n\n // A flat section's cap is exact at any tessellation, so it is left\n // unsubdivided (Infinity) and stays identical to the pre-surface mesh —\n // legacy height-less charts must not shift by a single vertex.\n const maxErr = surface.flat ? Infinity : CAP_MAX_ERROR_M;\n const topN = (p: Point): [number, number, number] => surface.normalAt(p.x, p.y);\n\n // Take only the ground this section has not already lost to an earlier one.\n //\n // Every flat section's top sits at exactly the same height, and the deck\n // padding above grows each one by 0.33 m, so neighbouring sections OVERLAP.\n // Two coplanar surfaces at identical depth is a z-fight, and it renders as a\n // serrated comb of interpenetrating teeth along every shared boundary — the\n // artifact visible on the flat chart. Depth-biasing them apart would hide it\n // rather than fix it, and would break down as soon as a chart has many\n // sections. Removing the overlap means there is nothing to fight over.\n // A flat section's deck is one constant height, so it can be tested for\n // coplanarity; a raked fallback surface has no single height to offer.\n const level = surface.flat ? surface.landingY : undefined;\n for (const ring of claimed.subtract(outline, level)) {\n extrudePrism(builder, ring, section.holes, topY, bottomY, colTop, S.tierWall, AO, maxErr, topN);\n }\n}\n\n/**\n * The ground already taken by sections drawn so far, so a later section can be\n * clipped to what is left. Only meaningful between COPLANAR surfaces, which is\n * why raked sections (each at its own height) never reach this.\n */\n/** Two claims fight for the same ground only if both are level and level TOGETHER.\n * A claim with no single height (an unresolved raked surface) is treated as\n * comparable with everything, which is what it did before heights existed. */\nfunction coplanarLevels(a: number | undefined, b: number | undefined): boolean {\n if (a === undefined || b === undefined) return true;\n return Math.abs(a - b) < 1e-3;\n}\n\nclass ClaimedArea {\n private rings: Array<[number, number][][]> = [];\n private boxes: RingBox[] = [];\n /** Constant deck height of each claim, or undefined when it is not level. */\n private levels: Array<number | undefined> = [];\n\n /**\n * `ring` minus everything claimed so far AT THE SAME HEIGHT; then claim it.\n *\n * `level` is the claim's constant deck height when it has one. Two decks only\n * z-fight when they are coplanar, so only a coplanar claim may take ground\n * away — an elevated box hanging over a ground-level section overlaps it in\n * plan and must still draw its whole floor, or the box loses the part of its\n * deck that shares a footprint with whatever is underneath it. Passing\n * undefined (a surface with no single height) keeps the original\n * clip-against-everything behaviour.\n */\n subtract(ring: Point[], level?: number): Point[][] {\n const closed: [number, number][] = ring.map((p) => [p.x, p.y]);\n if (closed.length < 3) return [];\n closed.push(closed[0]);\n const box = bboxOfRing(ring);\n // Same prune as `paddedFootprint`: a claim that cannot overlap cannot remove\n // anything, and without this the boolean work is quadratic in section count.\n const overlapping = this.rings.filter((_, i) => boxesOverlap(box, this.boxes[i])\n && coplanarLevels(level, this.levels[i]));\n let pieces: Array<[number, number][][]> = [[closed]];\n if (overlapping.length) {\n try {\n const diff = polygonClipping.difference([closed], ...overlapping);\n pieces = diff.map((poly) => poly.map((r) => r.map(([x, y]) => [x, y] as [number, number])));\n } catch {\n pieces = [[closed]]; // a degenerate outline must not drop the section\n }\n }\n this.rings.push([closed]);\n this.boxes.push(box);\n this.levels.push(level);\n const out: Point[][] = [];\n for (const poly of pieces) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push(pts);\n }\n return out;\n }\n}\n\n/**\n * Height of a booth stand, world metres — a partition wall, not a table.\n *\n * Trade-show and exhibition charts are entirely booths (267 across the two\n * shipped templates), and before this they drew nothing at all: the whole venue\n * rendered as empty floor. A booth is a sellable unit the buyer picks, so it has\n * to be a solid the camera can approach and the pointer can hit.\n */\nconst BOOTH_HEIGHT_M = 2.4;\n\n/** How far a label floats above the thing it names, world metres. */\nconst ZONE_LABEL_LIFT_M = 6;\nconst SECTION_LABEL_LIFT_M = 2.2;\nconst ANNOTATION_LIFT_M = 0.1;\nconst BOOTH_LABEL_LIFT_M = 0.3;\n/** A GA area is flat, so its name sits at head height over the standing floor. */\nconst GA_LABEL_LIFT_M = 1.8;\n/** Row and seat labels ride just above their own deck, like painted markings. */\nconst ROW_LABEL_LIFT_M = 0.9;\nconst SEAT_LABEL_LIFT_M = 0.55;\n/**\n * Seat-label ceiling. Above this the whole set is not emitted — see the note at\n * the emit site. 6,000 covers theatres, halls, cinemas and mid-size arenas; the\n * charts above it are exactly the ones where a per-seat label is least readable.\n */\nconst SEAT_LABEL_MAX = 6_000;\n\n/** Height of a banquet table top, world metres (standard dining height). */\nconst TABLE_HEIGHT_M = 0.75;\n\n/**\n * Height of a décor-image floor plate — a marking on the ground, not an object.\n *\n * Matched to the GA slab (0.15 m) rather than made as thin as it \"should\" be: a\n * 4 cm plate under a 76 m rink is a depth ratio of 5e-4 and Uber Arena's ice\n * z-fought the ground slab into horizontal stripes across the whole surface.\n */\nconst DECOR_PLATE_M = 0.15;\n\n/** Resolve a booth to its closed chart-unit polygon, honouring a custom outline. */\nfunction boothPolygon(booth: Extract<ChartObject, { type: 'booth' }>): Point[] | null {\n // A custom outline wins and ignores rotation, exactly as the 2D renderer does\n // (see BoothObject.points) — L-shaped and island units on an expo floor.\n if (booth.points && booth.points.length >= 3) return booth.points;\n const { center, width, height, rotation } = booth;\n if (!width || !height) return null;\n const a = ((rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: center.x + lx * cos - ly * sin,\n y: center.y + lx * sin + ly * cos,\n }));\n}\n\nfunction buildBooth(\n builder: MeshBuilder,\n booth: Extract<ChartObject, { type: 'booth' }>,\n base: number,\n fill: RGB | null,\n S: typeof STRUCTURE,\n): void {\n const poly = boothPolygon(booth);\n if (!poly) return;\n extrudePrism(builder, poly, undefined, () => base + BOOTH_HEIGHT_M, base,\n tintTop(fill, S.boothTop), S.boothWall, AO);\n}\n\n/** Resolve a table to its closed chart-unit polygon. */\nfunction tablePolygon(table: Extract<ChartObject, { type: 'table' }>): Point[] | null {\n if (table.shape === 'round') {\n const r = table.radius;\n if (!r) return null;\n return ellipsePolygon(table.center.x, table.center.y, r, r, 24);\n }\n const { width, height, rotation, center } = table;\n if (!width || !height) return null;\n const a = ((rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: center.x + lx * cos - ly * sin,\n y: center.y + lx * sin + ly * cos,\n }));\n}\n\nfunction buildTable(\n builder: MeshBuilder,\n table: Extract<ChartObject, { type: 'table' }>,\n base: number,\n fill: RGB | null,\n S: typeof STRUCTURE,\n): void {\n const poly = tablePolygon(table);\n if (!poly) return;\n // Banquet and club charts (77 tables across the shipped templates) drew their\n // chairs floating around nothing. The table is what makes the arrangement read\n // as a table rather than a ring of stray seats.\n extrudePrism(builder, poly, undefined, () => base + TABLE_HEIGHT_M, base,\n tintTop(fill, S.tableTop), S.tableWall, AO);\n}\n\nfunction rotateShapePolygon(poly: Point[], degrees: number | undefined): Point[] {\n if (!degrees) return poly;\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (const point of poly) {\n minX = Math.min(minX, point.x); maxX = Math.max(maxX, point.x);\n minY = Math.min(minY, point.y); maxY = Math.max(maxY, point.y);\n }\n const cx = (minX + maxX) / 2, cy = (minY + maxY) / 2;\n const radians = (degrees * Math.PI) / 180;\n const cos = Math.cos(radians), sin = Math.sin(radians);\n return poly.map((point) => {\n const dx = point.x - cx, dy = point.y - cy;\n return { x: cx + dx * cos - dy * sin, y: cy + dx * sin + dy * cos };\n });\n}\n\n/** Resolve a shape object to its rotated closed chart-unit polygon. */\nfunction shapePolygon(shape: Extract<ChartObject, { type: 'shape' }>): Point[] | null {\n let poly: Point[] | null = null;\n if (shape.kind === 'polygon' && shape.points && shape.points.length >= 3) poly = shape.points;\n if (shape.kind === 'rect' && shape.width && shape.height) {\n poly = rectPolygon(shape.x ?? 0, shape.y ?? 0, shape.width, shape.height);\n }\n if (shape.kind === 'ellipse' && shape.width && shape.height) {\n const cx = (shape.x ?? 0) + shape.width / 2;\n const cy = (shape.y ?? 0) + shape.height / 2;\n poly = ellipsePolygon(cx, cy, shape.width / 2, shape.height / 2);\n }\n return poly ? rotateShapePolygon(poly, shape.rotation) : null; // line / polyline are stroke-only\n}\n\nexport type ShapeArchitectureKind = 'plate' | 'stage' | 'solid' | 'portal' | 'suite';\nexport interface ShapeArchitecture {\n kind: ShapeArchitectureKind;\n heightM: number;\n}\n\n/** Physical interpretation of the chart's existing décor vocabulary. */\nexport function architectureForShape(shape: Pick<ShapeObject, 'role' | 'heightM'>): ShapeArchitecture {\n const role = shape.role ?? '';\n const defaults: Record<string, ShapeArchitecture> = {\n stage: { kind: 'stage', heightM: 1 },\n entrance: { kind: 'portal', heightM: 2.7 },\n exit: { kind: 'portal', heightM: 2.7 },\n screen: { kind: 'solid', heightM: 6 },\n wall: { kind: 'solid', heightM: 2.8 },\n rail: { kind: 'solid', heightM: 1.1 },\n suite: { kind: 'suite', heightM: 3 },\n obstruction: { kind: 'solid', heightM: 4.5 },\n sound: { kind: 'solid', heightM: 1.45 },\n restroom: { kind: 'solid', heightM: 2.4 },\n bar: { kind: 'solid', heightM: 1.1 },\n concession: { kind: 'solid', heightM: 1.1 },\n coat: { kind: 'solid', heightM: 1.1 },\n };\n const resolved = defaults[role] ?? { kind: 'plate' as const, heightM: 0.25 };\n return { ...resolved, heightM: shape.heightM ?? resolved.heightM };\n}\n\ninterface ShapeLayerFootprint {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n topOffsetM: number;\n}\n\n/**\n * Flat authored shapes share one solid draw call, so polygonOffset cannot\n * separate a marking from the surface below it. Preserve the 2D object order by\n * lifting only shapes whose footprints overlap something already emitted.\n * Eight centimetres remains visually flush at venue scale while clearing the\n * depth precision that made Cricket Oval's wicket strip flicker into slivers.\n */\nconst SHAPE_LAYER_LIFT_M = 0.08;\n\nfunction claimShapeTopOffset(\n shape: Extract<ChartObject, { type: 'shape' }>,\n claimed: ShapeLayerFootprint[],\n): number {\n const poly = shapePolygon(shape);\n const architecture = architectureForShape(shape);\n const defaultTop = architecture.heightM;\n if (!poly) return defaultTop;\n // Vertical architecture occupies real space; it does not participate in the\n // tiny z-fight lifts used to order overlapping painted floor plates.\n if (architecture.kind !== 'plate' && architecture.kind !== 'stage') return defaultTop;\n\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const p of poly) {\n minX = Math.min(minX, p.x); minY = Math.min(minY, p.y);\n maxX = Math.max(maxX, p.x); maxY = Math.max(maxY, p.y);\n }\n let topOffsetM = defaultTop;\n for (const prior of claimed) {\n const overlaps = maxX > prior.minX && minX < prior.maxX\n && maxY > prior.minY && minY < prior.maxY;\n if (overlaps && topOffsetM <= prior.topOffsetM + 1e-6) {\n topOffsetM = prior.topOffsetM + SHAPE_LAYER_LIFT_M;\n }\n }\n claimed.push({ minX, minY, maxX, maxY, topOffsetM });\n return topOffsetM;\n}\n\nfunction lerpPoint(a: Point, b: Point, t: number): Point {\n return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };\n}\n\nfunction orientQuadLong(poly: Point[]): Point[] {\n if (poly.length !== 4) return poly;\n const edge0 = Math.hypot(poly[1].x - poly[0].x, poly[1].y - poly[0].y);\n const edge1 = Math.hypot(poly[2].x - poly[1].x, poly[2].y - poly[1].y);\n return edge0 >= edge1 ? poly : [poly[1], poly[2], poly[3], poly[0]];\n}\n\nfunction quadWidthSlice(poly: Point[], t0: number, t1: number): Point[] {\n return [\n lerpPoint(poly[0], poly[1], t0), lerpPoint(poly[0], poly[1], t1),\n lerpPoint(poly[3], poly[2], t1), lerpPoint(poly[3], poly[2], t0),\n ];\n}\n\nfunction quadDepthSlice(poly: Point[], t0: number, t1: number): Point[] {\n return [\n lerpPoint(poly[0], poly[3], t0), lerpPoint(poly[1], poly[2], t0),\n lerpPoint(poly[1], poly[2], t1), lerpPoint(poly[0], poly[3], t1),\n ];\n}\n\nfunction emitArchitecturalPrism(\n builder: MeshBuilder,\n poly: Point[],\n top: number,\n bottom: number,\n colTop: RGB,\n colWall: RGB,\n): void {\n extrudePrism(builder, poly, undefined, () => top, bottom, colTop, colWall, AO);\n}\n\nfunction buildShape(\n builder: MeshBuilder,\n shape: Extract<ChartObject, { type: 'shape' }>,\n base: number,\n topOffsetM: number,\n S: typeof STRUCTURE,\n focal: Point,\n /** Collects the footprint of each STAGE, for the lighting rig above it. */\n stages?: StageBounds[],\n): void {\n const poly = shapePolygon(shape);\n if (!poly) return;\n const isStage = shape.role === 'stage';\n const architecture = architectureForShape(shape);\n const height = base + topOffsetM;\n if (isStage && stages) {\n let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity;\n for (const pt of poly) {\n if (pt.x < minX) minX = pt.x;\n if (pt.x > maxX) maxX = pt.x;\n if (pt.y < minZ) minZ = pt.y;\n if (pt.y > maxZ) maxZ = pt.y;\n }\n // Chart units -> world metres, matching every other position in the scene.\n stages.push({\n cx: ((minX + maxX) / 2) * M,\n cz: ((minZ + maxZ) / 2) * M,\n y: height,\n halfX: Math.max(((maxX - minX) / 2) * M, 0.5),\n halfZ: Math.max(((maxZ - minZ) / 2) * M, 0.5),\n });\n }\n // An authored `fill` is what the 2D renderer paints this shape (see\n // `renderShape`), and 3D used to discard it for a fixed grey — so an organizer\n // who coloured their stage, dance floor or pitch saw it in the map and lost it\n // the moment they switched to 3D. Muted through `tintTop` like every other\n // authored colour, so it keeps its hue without out-shouting the seating.\n const authoredFill = hexToRgb(shape.fill);\n // An explicitly authored stage should keep the material it was given. A\n // mostly warm fixed palette made dark concert decks read like brown timber.\n // Unauthored stages retain the brighter default performance treatment.\n const colTop = isStage && authoredFill\n ? mix(S.stageTop, scaleRgb(desaturate(authoredFill, 0.18), 0.90), 0.90)\n : tintTop(authoredFill, isStage ? S.stageTop : S.decorTop);\n // A screen's useful surface is vertical, so it needs to read from its WALL\n // faces rather than its almost invisible top cap. Treat it like a powered LED\n // panel; every other structure remains inside the muted architectural palette.\n const screenFill = shape.role === 'screen' ? authoredFill : null;\n const colWall: RGB = isStage\n ? authoredFill\n ? mix(S.stageWall, scaleRgb(authoredFill, 0.58), 0.82)\n : S.stageWall\n : shape.role === 'screen'\n ? screenFill\n ? mix(scaleRgb(screenFill, 1.42), [0.24, 0.46, 0.82], 0.08)\n : [0.24, 0.46, 0.82]\n : tintTop(hexToRgb(shape.fill), S.decorWall);\n if (architecture.kind === 'portal' && poly.length === 4) {\n const quad = orientQuadLong(poly);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0, 0.16), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0.84, 1), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quad, height, Math.max(base, height - 0.35), colTop, colWall);\n return;\n }\n if (architecture.kind === 'suite' && poly.length === 4) {\n const edgeDistances = poly.map((point, index) => {\n const next = poly[(index + 1) % 4];\n const mx = (point.x + next.x) / 2, my = (point.y + next.y) / 2;\n return Math.hypot(mx - focal.x, my - focal.y);\n });\n const front = edgeDistances.indexOf(Math.min(...edgeDistances));\n const quad = [0, 1, 2, 3].map((offset) => poly[(front + offset) % 4]);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0, 0.08), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0.92, 1), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quadDepthSlice(quad, 0.88, 1), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quad, height, Math.max(base, height - 0.18), colTop, colWall);\n return;\n }\n emitArchitecturalPrism(builder, poly, height, base, colTop, colWall);\n}\n\n/**\n * Resolve a décor image to its closed chart-unit polygon.\n *\n * `DecorImageObject` is authored as a top-left anchor plus size, rotated about\n * its own CENTRE — the same convention the 2D renderer uses when it offsets the\n * Konva node by half its extent.\n */\nfunction decorPolygon(decor: Extract<ChartObject, { type: 'decorImage' }>): Point[] | null {\n const { x, y, width, height } = decor;\n if (!width || !height) return null;\n const cx = x + width / 2, cy = y + height / 2;\n const a = ((decor.rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: cx + lx * cos - ly * sin,\n y: cy + lx * sin + ly * cos,\n }));\n}\n\n/**\n * The colour a décor plate is painted, sniffed from an SVG data URL.\n *\n * The renderer is deliberately texture-free (three draw calls, no atlas), so a\n * décor image cannot be drawn as an image. What it CAN do is paint the plate in\n * the graphic's own dominant colour, which is the difference between an ice rink\n * reading as ice and reading as a grey slab.\n *\n * The heuristic is narrow on purpose: take the paint of the FIRST covering\n * element (`<rect>` or `<path>`), because an SVG floor graphic is built up\n * background-first — the rink surface, then the lines on top of it. A gradient\n * reference resolves to the mean of that gradient's stops. Anything else (a\n * raster data URL, an external href, an SVG that does not match) returns null\n * and the caller falls back to the neutral décor tone, which is what the object\n * rendered as before this existed.\n */\nfunction decorPlatePaint(href: string | undefined): RGB | null {\n if (!href || !href.startsWith('data:image/svg+xml')) return null;\n let svg = href.slice(href.indexOf(',') + 1);\n if (/;base64/i.test(href.slice(0, href.indexOf(',')))) {\n try { svg = atob(svg); } catch { return null; }\n } else {\n try { svg = decodeURIComponent(svg); } catch { /* already literal */ }\n }\n\n const covering = svg.match(/<(?:rect|path)\\b[^>]*\\bfill=\"([^\"]+)\"/i);\n if (!covering) return null;\n const paint = covering[1].trim();\n if (paint === 'none' || paint === 'transparent') return null;\n\n const gradient = paint.match(/^url\\(#([^)]+)\\)$/);\n if (!gradient) return hexToRgb(paint);\n\n // Average the referenced gradient's stops — a two-stop ice gradient is one\n // colour as far as a flat-shaded plate is concerned.\n const def = svg.match(new RegExp(`<(?:linear|radial)Gradient\\\\b[^>]*\\\\bid=\"${gradient[1]}\"[^>]*>([\\\\s\\\\S]*?)</(?:linear|radial)Gradient>`, 'i'));\n if (!def) return null;\n const stops: RGB[] = [];\n for (const m of def[1].matchAll(/stop-color=\"([^\"]+)\"/gi)) {\n const c = hexToRgb(m[1].trim());\n if (c) stops.push(c);\n }\n if (!stops.length) return null;\n return stops.reduce<RGB>((acc, c, i) => mix(acc, c, 1 / (i + 1)), stops[0]);\n}\n\nfunction buildDecorImage(\n builder: MeshBuilder,\n decor: Extract<ChartObject, { type: 'decorImage' }>,\n base: number,\n S: typeof STRUCTURE,\n): void {\n // A `foreground` décor image draws ABOVE the seats in 2D — a canopy or an\n // overlay graphic. In 3D an opaque plate over the seating would hide the very\n // thing the buyer came to pick from, so it is left undrawn rather than drawn\n // wrongly. No shipped template authors one; the designer can.\n if (decor.layer === 'foreground') return;\n const poly = decorPolygon(decor);\n if (!poly) return;\n // The plate is a floor marking, not an object: it sits a few centimetres proud\n // of the ground slab so it cannot z-fight, and well under the 0.25 m décor\n // shapes that may be standing on it.\n const top = base + DECOR_PLATE_M;\n // Muted into the structure palette exactly like a category fill on a tier cap.\n // Taken literally, the rink's #f4f9ff ice is the BRIGHTEST thing in the venue\n // and the floor out-shouts the seating — against the look brief, which puts\n // saturated colour only on seats. Through `tintTop` it keeps its blue cast and\n // sits in the same tonal range as every other surface.\n let paint = tintTop(decorPlatePaint(decor.href), S.decorTop);\n // No alpha in the solid pass, so authored opacity is honoured by mixing the\n // plate toward the ground it would otherwise be showing through to.\n const opacity = Math.max(0, Math.min(1, decor.opacity ?? 1));\n if (opacity < 1) paint = mix(S.ground, paint, opacity);\n extrudePrism(builder, poly, undefined, () => top, base, paint, mix(paint, S.decorWall, 0.5), AO);\n}\n\nfunction buildGa(builder: MeshBuilder, ga: Extract<ChartObject, { type: 'gaArea' }>, base: number, fill: RGB | null, S: typeof STRUCTURE): void {\n if (!ga.points || ga.points.length < 3) return;\n const colTop = tintTop(fill, S.gaTop);\n extrudePrism(builder, ga.points, ga.holes, () => base + 0.15, base, colTop, S.gaWall, AO);\n}\n\n/** Compute the horizontal chart-unit footprint over everything drawable. */\nfunction chartFootprint(units: FloorUnit[], seats: ExpandedSeat[]): { minX: number; minY: number; maxX: number; maxY: number } {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n const acc = (x: number, y: number): void => {\n if (x < minX) minX = x; if (y < minY) minY = y;\n if (x > maxX) maxX = x; if (y > maxY) maxY = y;\n };\n for (const s of seats) acc(s.x, s.y);\n for (const u of units) {\n for (const o of u.objects) {\n if (o.type === 'section') for (const p of o.outline) acc(p.x, p.y);\n else if (o.type === 'shape' && o.points) for (const p of o.points) acc(p.x, p.y);\n else if (o.type === 'gaArea') for (const p of o.points) acc(p.x, p.y);\n else if (o.type === 'booth') { const b = boothPolygon(o); if (b) for (const p of b) acc(p.x, p.y); }\n else if (o.type === 'table') { const t = tablePolygon(o); if (t) for (const p of t) acc(p.x, p.y); }\n else if (o.type === 'decorImage') { const d = decorPolygon(o); if (d) for (const p of d) acc(p.x, p.y); }\n }\n }\n if (!Number.isFinite(minX)) { minX = -100; minY = -100; maxX = 100; maxY = 100; }\n return { minX, minY, maxX, maxY };\n}\n\nexport interface SceneModelInput {\n doc: ChartDoc;\n seats: ExpandedSeat[];\n /** Overrides the chart's active layout without mutating its authored data. */\n eventConfigurationId?: string;\n /** Optional initial per-seat state (default all available). */\n initialState?: (seat: ExpandedSeat) => import('../palette').SeatState3D;\n}\n\nexport function buildSceneModel(input: SceneModelInput): SceneModel {\n const { doc, seats } = input;\n // Resolved once and threaded down, so no builder reaches for the module-level\n // palette and quietly ignores an organizer's branding.\n const theme = resolveTheme3D(doc.theme);\n const S = theme.structure;\n const units = floorUnits(doc, input.eventConfigurationId ?? doc.eventConfigurationId);\n const builder = new MeshBuilder();\n\n // Ground slab sized to the footprint (+ margin), sitting at datum 0.\n const fp = chartFootprint(units, seats);\n const padU = Math.max(60, (fp.maxX - fp.minX + fp.maxY - fp.minY) * 0.06);\n const groundPoly = rectPolygon(fp.minX - padU, fp.minY - padU, (fp.maxX - fp.minX) + padU * 2, (fp.maxY - fp.minY) + padU * 2);\n extrudePrism(builder, groundPoly, undefined, () => 0, -0.4, S.ground, S.ground, AO);\n\n // Per-section 2D fill colours (member-category mix), carried onto tier tops.\n const sectionFills = resolveSectionFills(doc, seats);\n const catColor = new Map<string, string>();\n for (const c of doc.categories ?? []) catColor.set(c.key, c.color);\n const stageBounds: StageBounds[] = [];\n\n // ONE seating surface per section, resolved from the same model layout.ts uses\n // for eye heights. Consumed below by the tier caps and by the seat instances.\n const surfaces = buildVenueSurfaces(units, seats);\n /** Owning floor per seat, for per-instance floor isolation. */\n const seatFloor = new Float32Array(seats.length);\n\n // Coplanar sections must not overlap (see ClaimedArea). One claim per floor:\n // sections on different floors sit at different heights and cannot z-fight.\n for (let unitIndex = 0; unitIndex < units.length; unitIndex++) {\n const unit = units[unitIndex];\n builder.setFloor(unitIndex);\n const claimed = new ClaimedArea();\n const claimedShapeLayers: ShapeLayerFootprint[] = [];\n const siblings = unit.objects.filter((o): o is SectionObject => o.type === 'section' && !!o.outline && o.outline.length >= 3);\n for (const o of unit.objects) {\n if (o.type === 'section') buildTier(builder, o, unit, sectionFill(o, sectionFills), surfaces.bySection.get(o.id), claimed, siblings, S);\n else if (o.type === 'shape') {\n builder.setMaterial(o.role === 'screen' ? 1 : 0);\n buildShape(\n builder,\n o,\n unit.baseHeightM,\n claimShapeTopOffset(o, claimedShapeLayers),\n S,\n unit.focal,\n stageBounds,\n );\n builder.setMaterial(0);\n }\n else if (o.type === 'gaArea') buildGa(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'booth') buildBooth(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'table') buildTable(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'decorImage') buildDecorImage(builder, o, unit.baseHeightM, S);\n }\n }\n\n const focal = doc.focalPoint ?? { x: (fp.minX + fp.maxX) / 2, y: (fp.minY + fp.maxY) / 2 };\n\n // NOTE — per-row stepped treads (./rowSteps.ts) are DISABLED pending the\n // unified-surface rework. See docs/handover-2026-07-25-3d-surface-rework.md.\n //\n // They layered a second surface on top of the section prism, and the two could\n // not be kept in agreement across chart shapes: treads became large flat plates\n // that swallowed the stepping, sliver triangles appeared where seat ordering\n // failed, and seat dots were sliced by their own tread. The fix is one surface\n // function per section consumed by cap + steps + dots, not a second layer.\n // The module is kept (with its measurements in the doc comments) as input to\n // that rework; re-enabling it as-is is not the intended path.\n\n const solids = mergeMeshData([builder.build()]);\n const seatData: SeatInstanceData = buildSeatInstances(seats, input.initialState, surfaces, seatFloor, catColor);\n\n // --- Zones -----------------------------------------------------------------\n // Resolved from the SEATS, not the section outlines: a zone's meaning to a\n // buyer is the seats in it, and framing on the outlines would include the\n // aisles and margins a section is drawn with.\n const zoneDefs = doc.zones ?? [];\n const zones: SceneZone[] = [];\n if (zoneDefs.length) {\n const sectionZone = new Map<string, string>();\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'section' && o.zone) sectionZone.set(o.id, o.zone);\n }\n }\n interface Acc { n: number; minX: number; minY: number; maxX: number; maxY: number; sumY: number }\n const acc = new Map<string, Acc>();\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n // Prefer the seat's own resolved zone; fall back to its owning section's.\n const owner = surfaces.seatOwner[i];\n const zid = s.zoneId ?? (owner ? sectionZone.get(owner) : undefined);\n if (!zid) continue;\n let a = acc.get(zid);\n if (!a) { a = { n: 0, minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity, sumY: 0 }; acc.set(zid, a); }\n a.n++;\n if (s.x < a.minX) a.minX = s.x;\n if (s.y < a.minY) a.minY = s.y;\n if (s.x > a.maxX) a.maxX = s.x;\n if (s.y > a.maxY) a.maxY = s.y;\n a.sumY += surfaces.seatDeckY(i);\n }\n for (const z of zoneDefs) {\n const a = acc.get(z.id);\n const sectionIds: string[] = [];\n for (const [secId, zid] of sectionZone) if (zid === z.id) sectionIds.push(secId);\n if (!a || a.n === 0) {\n // A zone with no seats still exists (an empty hall, a zone of GA areas);\n // report it rather than dropping it, so navigation can show it as empty.\n zones.push({\n id: z.id, label: z.label, color: hexToRgb(z.color), sectionIds, seatCount: 0,\n center: [0, 0, 0], radius: 0,\n focalWorld: [(z.focalPoint ?? focal).x * M, 1.5, (z.focalPoint ?? focal).y * M],\n });\n continue;\n }\n zones.push({\n id: z.id,\n label: z.label,\n color: hexToRgb(z.color),\n sectionIds,\n seatCount: a.n,\n center: [((a.minX + a.maxX) / 2) * M, a.sumY / a.n, ((a.minY + a.maxY) / 2) * M],\n radius: 0.5 * Math.hypot((a.maxX - a.minX) * M, (a.maxY - a.minY) * M) || 1,\n // A zone may face its own point (`ZoneDef.focalPoint`); the documented\n // fallback is the floor/chart focal.\n focalWorld: [(z.focalPoint ?? focal).x * M, 1.5, (z.focalPoint ?? focal).y * M],\n });\n }\n }\n\n // --- Labels ----------------------------------------------------------------\n const labels: SceneLabel[] = [];\n const sections: SceneSection[] = [];\n const rows: SceneRow[] = [];\n for (const z of zones) {\n if (z.seatCount === 0) continue;\n // Zone labels float above the seating they name, so they read as belonging\n // to the whole block rather than to whichever seat is under them.\n labels.push({\n id: `zone:${z.id}`,\n kind: 'zone',\n text: z.label,\n anchor: [z.center[0], z.center[1] + ZONE_LABEL_LIFT_M, z.center[2]],\n color: doc.zones?.find((d) => d.id === z.id)?.color,\n });\n }\n {\n // A section is named over its own SEATS, at their mean deck height. Its\n // outline centroid would drift into the aisles a concave section wraps, and\n // on a raked tier the label would sit at the wrong height entirely.\n const acc = new Map<string, { n: number; x: number; y: number; deck: number }>();\n // Seat extent per section, for the camera fit `focusSection` needs.\n const ext = new Map<string, { minX: number; maxX: number; minY: number; maxY: number }>();\n for (let i = 0; i < seats.length; i++) {\n const owner = surfaces.seatOwner[i];\n if (!owner) continue;\n let a = acc.get(owner);\n if (!a) { a = { n: 0, x: 0, y: 0, deck: 0 }; acc.set(owner, a); }\n a.n++; a.x += seats[i].x; a.y += seats[i].y; a.deck += surfaces.seatDeckY(i);\n let e = ext.get(owner);\n if (!e) { e = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity }; ext.set(owner, e); }\n if (seats[i].x < e.minX) e.minX = seats[i].x;\n if (seats[i].x > e.maxX) e.maxX = seats[i].x;\n if (seats[i].y < e.minY) e.minY = seats[i].y;\n if (seats[i].y > e.maxY) e.maxY = seats[i].y;\n }\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type !== 'section') continue;\n const a = acc.get(o.id);\n if (!a || a.n === 0) continue;\n const e = ext.get(o.id)!;\n const centre: [number, number, number] = [(a.x / a.n) * M, a.deck / a.n, (a.y / a.n) * M];\n sections.push({\n id: o.id,\n label: o.displayLabel || o.label || o.id,\n seatCount: a.n,\n center: centre,\n // Half-diagonal of the seat extent — the same fit the zones use.\n radius: Math.max(1, Math.hypot(e.maxX - e.minX, e.maxY - e.minY) * 0.5 * M),\n // A section faces its floor's focal, which is what the camera should\n // look along so the seats present their fronts.\n focalWorld: [unit.focal.x * M, unit.baseHeightM + 1.5, unit.focal.y * M],\n });\n labels.push({\n id: `section:${o.id}`,\n kind: 'section',\n // The buyer-facing name wins over the technical one, as it does in 2D.\n text: o.displayLabel || o.label || o.id,\n anchor: [centre[0], centre[1] + SECTION_LABEL_LIFT_M, centre[2]],\n });\n }\n }\n }\n // --- GA areas, rows and seats ----------------------------------------------\n {\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type !== 'gaArea') continue;\n const c = centroidOf(o.points);\n if (!c) continue;\n // A GA area's capacity IS its buyer-facing fact — \"Standing Floor\" alone\n // does not say whether 40 or 4,000 people fit in it, and 2D shows the\n // number. The label carries both, as one line.\n const name = o.displayLabel || o.label || o.id;\n labels.push({\n id: `ga:${o.id}`,\n kind: 'ga',\n text: o.capacity > 0 ? `${name} · ${o.capacity.toLocaleString()}` : name,\n anchor: [c.x * M, unit.baseHeightM + GA_LABEL_LIFT_M, c.y * M],\n });\n }\n }\n\n // Rows are labelled at the END of the row, where a real venue paints them —\n // on the aisle, not over the middle of the seating. Anchored to the outermost\n // seat so the label tracks the row's own rake instead of a flat guess.\n const rowEnds = new Map<string, { seat: ExpandedSeat; index: number; far: number }>();\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n const far = Math.hypot(s.x - focal.x, s.y - focal.y);\n const cur = rowEnds.get(s.rowId);\n // \"End\" = furthest along the row from its own midpoint; approximated by\n // distance from the venue focal, which for a row is one of its two ends.\n if (!cur || far > cur.far) rowEnds.set(s.rowId, { seat: s, index: i, far });\n }\n for (const [rowId, end] of rowEnds) {\n // A seat's public label is `${rowLabel}-${seatNumber}`, so the row's name\n // is everything before the LAST separator — a row legitimately called\n // \"A-1\" in a segmented plan must not be truncated to \"A\".\n const cut = end.seat.label ? end.seat.label.lastIndexOf('-') : -1;\n const text = cut > 0 ? end.seat.label.slice(0, cut) : rowId;\n if (!text) continue;\n labels.push({\n id: `row:${rowId}`,\n kind: 'row',\n text,\n anchor: [end.seat.x * M, surfaces.seatDeckY(end.index) + ROW_LABEL_LIFT_M, end.seat.y * M],\n });\n }\n\n const rowAcc = new Map<string, {\n n: number; x: number; y: number; z: number;\n minX: number; maxX: number; minZ: number; maxZ: number;\n label: string; sectionId?: string;\n }>();\n for (let i = 0; i < seats.length; i++) {\n const seat = seats[i];\n const cut = seat.label ? seat.label.lastIndexOf('-') : -1;\n let acc = rowAcc.get(seat.rowId);\n if (!acc) {\n acc = {\n n: 0, x: 0, y: 0, z: 0,\n minX: Infinity, maxX: -Infinity, minZ: Infinity, maxZ: -Infinity,\n label: cut > 0 ? seat.label.slice(0, cut) : seat.rowId,\n sectionId: seat.sectionId,\n };\n rowAcc.set(seat.rowId, acc);\n }\n const offset = i * 3;\n const x = seatData.iPosition[offset];\n const y = seatData.iPosition[offset + 1];\n const z = seatData.iPosition[offset + 2];\n acc.n++; acc.x += x; acc.y += y; acc.z += z;\n acc.minX = Math.min(acc.minX, x); acc.maxX = Math.max(acc.maxX, x);\n acc.minZ = Math.min(acc.minZ, z); acc.maxZ = Math.max(acc.maxZ, z);\n }\n for (const [id, acc] of rowAcc) {\n rows.push({\n id,\n label: acc.label,\n sectionId: acc.sectionId,\n seatCount: acc.n,\n center: [acc.x / acc.n, acc.y / acc.n, acc.z / acc.n],\n radius: Math.max(0.8, Math.hypot(acc.maxX - acc.minX, acc.maxZ - acc.minZ) * 0.5),\n });\n }\n\n // Seat labels are the densest rung by far, and unlike every other kind their\n // count is the seat count. On a 52,000-seat ground that is 52,000 DOM-backed\n // labels to hold and project, for text that is only legible when a handful\n // are on screen. So they are emitted for charts where the whole set stays\n // cheap, and above that a seat identifies itself the way it already does on\n // every surface: by being tapped, which raises the seat's own card.\n if (seats.length <= SEAT_LABEL_MAX) {\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n const text = s.displayLabel || s.label;\n if (!text) continue;\n labels.push({\n id: `seat:${s.id}`,\n kind: 'seat',\n text,\n anchor: [s.x * M, surfaces.seatDeckY(i) + SEAT_LABEL_LIFT_M, s.y * M],\n });\n }\n }\n }\n\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'shape' && o.role && ['entrance', 'exit', 'screen', 'suite', 'obstruction'].includes(o.role)) {\n const poly = shapePolygon(o);\n const centre = poly ? centroidOf(poly) : null;\n if (!centre || !o.label) continue;\n labels.push({\n id: `architecture:${o.id}`,\n kind: 'annotation',\n text: o.label,\n anchor: [centre.x * M, unit.baseHeightM + architectureForShape(o).heightM + ANNOTATION_LIFT_M, centre.y * M],\n color: o.fill,\n });\n } else if (o.type === 'text') {\n // Authored wayfinding sits just above the floor it annotates — a door or\n // aisle name belongs to the ground, not to the air above it.\n if (!o.text) continue;\n labels.push({\n id: `text:${o.id}`,\n kind: 'annotation',\n text: o.text,\n anchor: [o.position.x * M, unit.baseHeightM + ANNOTATION_LIFT_M, o.position.y * M],\n color: o.color,\n rotation: o.rotation,\n });\n } else if (o.type === 'booth') {\n // A booth is a sellable unit the buyer picks by NAME, so it is labelled\n // on top of its own stand rather than left as an anonymous box.\n const poly = boothPolygon(o);\n if (!poly) continue;\n const c = centroidOf(poly);\n if (!c) continue;\n labels.push({\n id: `booth:${o.id}`,\n kind: 'booth',\n text: o.displayLabel || o.label || o.id,\n anchor: [c.x * M, unit.baseHeightM + BOOTH_HEIGHT_M + BOOTH_LABEL_LIFT_M, c.y * M],\n });\n }\n }\n }\n\n // --- Floors ----------------------------------------------------------------\n const floors: SceneFloor[] = [];\n {\n const sectionFloor = new Map<string, number>();\n for (let ui = 0; ui < units.length; ui++) {\n for (const o of units[ui].objects) if (o.type === 'section') sectionFloor.set(o.id, ui);\n }\n const acc = units.map(() => ({ n: 0, minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity, sumY: 0 }));\n for (let i = 0; i < seats.length; i++) {\n const owner = surfaces.seatOwner[i];\n const ui = owner !== null ? sectionFloor.get(owner) : undefined;\n if (ui === undefined) continue;\n const a = acc[ui];\n const s = seats[i];\n a.n++;\n if (s.x < a.minX) a.minX = s.x;\n if (s.y < a.minY) a.minY = s.y;\n if (s.x > a.maxX) a.maxX = s.x;\n if (s.y > a.maxY) a.maxY = s.y;\n a.sumY += surfaces.seatDeckY(i);\n seatFloor[i] = ui;\n }\n for (let ui = 0; ui < units.length; ui++) {\n const a = acc[ui];\n const f = doc.floors?.[ui];\n floors.push({\n index: ui,\n id: f?.id ?? `floor-${ui}`,\n label: f?.name ?? (units.length > 1 ? `Floor ${ui + 1}` : 'Venue'),\n seatCount: a.n,\n center: a.n ? [((a.minX + a.maxX) / 2) * M, a.sumY / a.n, ((a.minY + a.maxY) / 2) * M] : [0, 0, 0],\n radius: a.n ? (0.5 * Math.hypot((a.maxX - a.minX) * M, (a.maxY - a.minY) * M) || 1) : 0,\n });\n }\n }\n\n // --- Seat facing -----------------------------------------------------------\n // Resolved here rather than in `buildSeatInstances` because it needs the thing\n // only this layer knows: which floor a seat belongs to, and therefore what it\n // looks at. Runs after the floors block above, which is what fills `seatFloor`.\n seatData.iYaw = computeSeatYaw(\n seatData.iPosition,\n seats.length,\n (i) => seats[i].rowId,\n (i) => {\n const f = units[seatFloor[i]]?.focal ?? focal;\n return [f.x * M, f.y * M];\n },\n );\n\n const cx = ((fp.minX + fp.maxX) / 2) * M;\n const cz = ((fp.minY + fp.maxY) / 2) * M;\n const radius = 0.5 * Math.hypot((fp.maxX - fp.minX) * M, (fp.maxY - fp.minY) * M) || 10;\n // Camera fitting intentionally uses the authored horizontal footprint above.\n // Clipping needs the actual render volume: padded ground, tall screens, upper\n // floors and near-field chairs can all extend beyond that 2D radius.\n let renderMinX = Infinity, renderMinY = Infinity, renderMinZ = Infinity;\n let renderMaxX = -Infinity, renderMaxY = -Infinity, renderMaxZ = -Infinity;\n const includeRenderPoint = (x: number, y: number, z: number): void => {\n renderMinX = Math.min(renderMinX, x); renderMaxX = Math.max(renderMaxX, x);\n renderMinY = Math.min(renderMinY, y); renderMaxY = Math.max(renderMaxY, y);\n renderMinZ = Math.min(renderMinZ, z); renderMaxZ = Math.max(renderMaxZ, z);\n };\n for (let i = 0; i < solids.position.length; i += 3) {\n includeRenderPoint(solids.position[i], solids.position[i + 1], solids.position[i + 2]);\n }\n for (let i = 0; i < seatData.count; i++) {\n const o = i * 3;\n const x = seatData.iPosition[o], y = seatData.iPosition[o + 1], z = seatData.iPosition[o + 2];\n const r = Math.max(0.35, seatData.iMaxRadius[i] ?? 0.35);\n includeRenderPoint(x - r, y, z - r);\n includeRenderPoint(x + r, y + 1.6, z + r);\n }\n if (!Number.isFinite(renderMinX)) {\n renderMinX = cx - radius; renderMaxX = cx + radius;\n renderMinY = 0; renderMaxY = radius * 0.16;\n renderMinZ = cz - radius; renderMaxZ = cz + radius;\n }\n const renderCenter: [number, number, number] = [\n (renderMinX + renderMaxX) / 2,\n (renderMinY + renderMaxY) / 2,\n (renderMinZ + renderMaxZ) / 2,\n ];\n const renderRadius = Math.max(1, 0.5 * Math.hypot(\n renderMaxX - renderMinX,\n renderMaxY - renderMinY,\n renderMaxZ - renderMinZ,\n ));\n\n return {\n solids,\n seats: seatData,\n bounds: { center: [cx, radius * 0.08, cz], radius, groundY: 0 },\n renderBounds: { center: renderCenter, radius: renderRadius },\n stateColorLUT: themeSeatColorLUT(theme, SEAT_STATES),\n theme,\n seatCount: seats.length,\n // Look-at target ~1.5 m up so a seated camera aims slightly down at the stage.\n focalWorld: [focal.x * M, 1.5, focal.y * M],\n zones,\n stages: stageBounds,\n sections,\n rows,\n labels,\n floors,\n };\n}\n","/**\n * THE structure resolver — what a chart's seats actually form, geometrically.\n *\n * A chart is authored in 2D as sections, rows and seats. Rendering it in 3D needs\n * a level above that which the author never states: which rows belong to the same\n * physical STAND, which of them is that stand's front row, and how deep into the\n * stand each row sits. Every height in the venue follows from those three facts.\n *\n * This module resolves them, once, from the authored geometry alone. It is pure —\n * no renderer types, no GL — so the same structure feeds the 3D surface, the\n * eye-height model in `layout.ts`, and anything added later (labels, LOD\n * streaming, per-block camera framing) without any of them re-deriving it. That\n * matters because re-derivation is this codebase's most expensive bug class:\n * three surfaces held apart by offsets, and a cap and its seats measured from\n * different datums, both came from two files computing the same fact differently.\n *\n * ## Why blocks, and why depth is measured inside one\n *\n * A section is not a stand. The amphitheatre's `sec-gall` is a single section\n * holding six wedge blocks that wrap the bowl; the arena's tiers are four blocks\n * each. Height used to be a function of distance from the venue focal, which is\n * wrong for exactly this reason — the side blocks of a tier sit further from the\n * focal than the centre blocks, so they were raised as though they were further\n * back. Measured on `sec-gall`, authored at 6.20 m: its six blocks started at\n * 6.35, 6.37, 8.18, 8.23, 10.60 and 10.65 m. The side wedges were **4.4 m too\n * high**, and that is the tilt-and-lift the owner could see.\n *\n * Depth is therefore measured WITHIN a block, from that block's own front row:\n *\n * level(row) = section.height + blockDepth(row) · tan(rake)\n *\n * so every block of a tier starts at the tier's authored height, and rows rise by\n * the real spacing between them. A venue whose tier is genuinely flat authors\n * `rake = 0` and every row lands on the same level — the same model, no special\n * case, which is what lets one code path serve a stepped bowl and a flat terrace.\n */\n\nimport type { ExpandedSeat, Point } from './types';\n\n/** A row resolved into its stand. */\nexport interface ResolvedRow {\n /** The row's authored id, or a synthetic one for a seat that has none. */\n id: string;\n /** The row's seats as an ordered polyline (the shape the deck is built on). */\n pts: Point[];\n /** Indices into the input seat array, parallel to nothing — for attribution. */\n seatIndices: number[];\n /** Which block (stand) this row belongs to, within its section. */\n blockId: number;\n /** 0 for the block's front row, then 1, 2, … going back. */\n ordinal: number;\n /**\n * Distance back from the block's FRONT row, chart units, accumulated over the\n * real gaps between consecutive rows.\n *\n * Not the ordinal times a nominal pitch: a stand with an aisle break or a\n * widening row gap should rise over that gap, and accumulating the measured\n * spacing keeps the drawn deck on the seats. Not distance from the venue focal\n * either — that is the defect described above.\n */\n blockDepth: number;\n /** Mean distance of the row's seats from the venue focal (front-row ordering). */\n focalDistance: number;\n}\n\nexport interface ResolvedSection {\n sectionId: string;\n /** Rows ordered by block, then front-to-back within the block. */\n rows: ResolvedRow[];\n /** Number of distinct blocks found. */\n blockCount: number;\n}\n\n/**\n * Two rows join the same block when a probe on one lands within this multiple of\n * the section's typical row spacing of the other.\n *\n * Above 1 so consecutive rows of one stand always connect (they are one spacing\n * apart by definition, and real charts vary). Well below the width of an aisle or\n * a vomitory, which is what has to stay a break — otherwise the six wedges of a\n * bowl merge into one block and the defect this module exists to fix comes back.\n */\nconst BLOCK_LINK_RATIO = 1.8;\n\n/** Sample points along a row, used for every distance test here. */\nfunction probesOf(pts: readonly Point[]): Point[] {\n const n = pts.length;\n if (n <= 3) return [...pts];\n return [pts[Math.floor(n * 0.25)], pts[Math.floor(n * 0.5)], pts[Math.floor(n * 0.75)]];\n}\n\n/** Perpendicular distance from a point to a polyline, chart units. */\nexport function distanceToPolyline(pts: readonly Point[], x: number, y: number): number {\n if (!pts.length) return Infinity;\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** A row's bounding circle — the prune that keeps block detection off O(n²) work. */\ninterface RowBound { cx: number; cy: number; r: number }\n\nfunction boundOf(pts: readonly Point[]): RowBound {\n let cx = 0, cy = 0;\n for (const p of pts) { cx += p.x; cy += p.y; }\n const n = pts.length || 1;\n cx /= n; cy /= n;\n let r = 0;\n for (const p of pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > r) r = d;\n }\n return { cx, cy, r };\n}\n\n/**\n * Lower bound on the gap between two rows, from their bounding circles.\n *\n * Admissible: the true gap can never be smaller, so pruning on this cannot\n * discard a pair that would have linked. Pair testing is quadratic in a section's\n * row count, and a 50k-seat venue has sections with thousands of rows — without\n * this prune, resolving one of them is hundreds of millions of segment tests.\n */\nfunction boundGap(a: RowBound, b: RowBound): number {\n return Math.hypot(a.cx - b.cx, a.cy - b.cy) - a.r - b.r;\n}\n\n/** Shortest distance between two rows, measured from probes on each. */\nfunction rowGap(a: readonly Point[], b: readonly Point[]): number {\n let best = Infinity;\n for (const p of probesOf(a)) {\n const d = distanceToPolyline(b, p.x, p.y);\n if (d < best) best = d;\n }\n for (const p of probesOf(b)) {\n const d = distanceToPolyline(a, p.x, p.y);\n if (d < best) best = d;\n }\n return best;\n}\n\n/**\n * Order a row's seats along the row.\n *\n * The chord between the two extremes only decides the ORDER; distances are never\n * measured against it. A curved row orders correctly under this even though its\n * chord is a poor fit to its shape.\n */\nfunction orderAlongRow(pts: Point[], indices: number[]): { pts: Point[]; seatIndices: number[] } {\n if (pts.length < 3) return { pts, seatIndices: indices };\n let cx = 0, cy = 0;\n for (const p of pts) { cx += p.x; cy += p.y; }\n cx /= pts.length; cy /= pts.length;\n let far = -1, ax = pts[0];\n for (const p of pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > far) { far = d; ax = p; }\n }\n let dx = ax.x - cx, dy = ax.y - cy;\n const len = Math.hypot(dx, dy);\n if (len > 1e-9) { dx /= len; dy /= len; } else { dx = 1; dy = 0; }\n const order = pts.map((p, i) => ({ p, i, t: (p.x - cx) * dx + (p.y - cy) * dy }))\n .sort((u, v) => u.t - v.t);\n return { pts: order.map((o) => o.p), seatIndices: order.map((o) => indices[o.i]) };\n}\n\n/**\n * Least-squares circle through a set of points (Kåsa fit), or null if degenerate.\n *\n * Used to find the centre a section's rows were struck about, which is NOT the\n * chart's focal point. The amphitheatre makes this concrete: its rows are arcs\n * about a centre `C`, while `focalPoint` is the stage at {0, 75}. Measuring depth\n * from the focal therefore varied ALONG each arc — the original row tilt — and\n * made concentric rows look radial to every heuristic downstream.\n *\n * The focal is where the audience LOOKS. The row centre is what the seating was\n * drawn around. Conflating them is the mistake underneath several of the defects\n * in this file's history.\n */\nfunction fitCircle(pts: readonly Point[]): { cx: number; cy: number } | null {\n const n = pts.length;\n if (n < 3) return null;\n let mx = 0, my = 0;\n for (const p of pts) { mx += p.x; my += p.y; }\n mx /= n; my /= n;\n let suu = 0, suv = 0, svv = 0, suuu = 0, svvv = 0, suvv = 0, svuu = 0;\n for (const p of pts) {\n const u = p.x - mx, v = p.y - my;\n suu += u * u; svv += v * v; suv += u * v;\n suuu += u * u * u; svvv += v * v * v;\n suvv += u * v * v; svuu += v * u * u;\n }\n const det = suu * svv - suv * suv;\n if (Math.abs(det) < 1e-9) return null;\n const b1 = (suuu + suvv) / 2;\n const b2 = (svvv + svuu) / 2;\n const uc = (b1 * svv - b2 * suv) / det;\n const vc = (b2 * suu - b1 * suv) / det;\n const cx = uc + mx, cy = vc + my;\n if (!Number.isFinite(cx) || !Number.isFinite(cy)) return null;\n return { cx, cy };\n}\n\n/**\n * Largest share of a section's radial depth that one row may span about a fitted\n * centre before the concentric model is rejected.\n *\n * A true arc row spans ~0 radially. A straight row spans a lot. 0.15 separates\n * them decisively without needing to know which kind of venue this is.\n */\nconst CIRCLE_ROW_SPREAD_LIMIT = 0.15;\n\n/**\n * A block's rake axis: the direction its rows recede in, or null when its rows do\n * not share one.\n *\n * Fitted PER BLOCK, never per section. A section routinely holds several stands\n * at different angles (a bowl's six wedges), and one axis over all of them fits\n * none — that was tried and did nothing. Within a block the rows ARE parallel, so\n * the fit is well posed.\n *\n * The fit is then VERIFIED rather than trusted: a row must be narrow along the\n * axis compared with the block's whole depth, which is what \"the rows lie across\n * the axis\" means. Rows that run radially fail this by construction, and the\n * caller falls back rather than producing nonsense from a fit that does not hold.\n */\nfunction fitBlockAxis(group: readonly ResolvedRow[]): { x: number; y: number } | null {\n if (group.length < 2) return null;\n const cents = group.map((r) => {\n let x = 0, y = 0;\n for (const p of r.pts) { x += p.x; y += p.y; }\n return { x: x / r.pts.length, y: y / r.pts.length };\n });\n let ox = 0, oy = 0;\n for (const c of cents) { ox += c.x; oy += c.y; }\n ox /= cents.length; oy /= cents.length;\n\n let sxx = 0, sxy = 0, syy = 0;\n for (const c of cents) {\n const dx = c.x - ox, dy = c.y - oy;\n sxx += dx * dx; sxy += dx * dy; syy += dy * dy;\n }\n const tr = sxx + syy;\n const det = sxx * syy - sxy * sxy;\n const lambda = tr / 2 + Math.sqrt(Math.max(0, (tr * tr) / 4 - det));\n let ax: number, ay: number;\n if (Math.abs(sxy) > 1e-12) { ax = lambda - syy; ay = sxy; }\n else if (sxx >= syy) { ax = 1; ay = 0; }\n else { ax = 0; ay = 1; }\n const len = Math.hypot(ax, ay);\n if (!(len > 1e-12)) return null;\n ax /= len; ay /= len;\n\n // Verify: how wide is a row along the axis, against the block's total range?\n let lo = Infinity, hi = -Infinity;\n const widths: number[] = [];\n for (const r of group) {\n let rlo = Infinity, rhi = -Infinity;\n for (const p of r.pts) {\n const t = p.x * ax + p.y * ay;\n if (t < rlo) rlo = t;\n if (t > rhi) rhi = t;\n if (t < lo) lo = t;\n if (t > hi) hi = t;\n }\n widths.push(rhi - rlo);\n }\n const range = hi - lo;\n if (!(range > 1e-9)) return null;\n widths.sort((a, b) => a - b);\n // The 90th percentile, not the median. \"The rows lie across this axis\" has to\n // hold for nearly ALL of them; a median passes a block that is half wrong. The\n // amphitheatre orchestra is exactly that case: its four wedges merge into one\n // block (their radial rows converge near the focal and genuinely touch), so\n // rows in two of the wedges lie across any fitted axis and rows in the other\n // two run along it. The median was narrow, the fit was accepted, and depth\n // accumulated to 1,716 units — a 9.81 m rise on a tier authored at 0 m, with\n // the terrace above it at 2.20 m. One outlying tail is tolerated; half a block\n // is not.\n const p90 = widths[Math.min(widths.length - 1, Math.floor(widths.length * 0.9))];\n return p90 / range <= AXIS_ROW_WIDTH_LIMIT ? { x: ax, y: ay } : null;\n}\n\n/**\n * Largest share of a block's total depth that a single row may span along the\n * fitted axis before the fit is rejected.\n *\n * A row lying across the axis spans almost nothing along it, so real stands sit\n * far below this. A radially-running row spans nearly the whole range and is\n * rejected. 0.35 sits clear of both.\n */\nconst AXIS_ROW_WIDTH_LIMIT = 0.35;\n\n/**\n * Resolve one section's seats into blocks and depth-ordered rows.\n *\n * `seatIndices` are indices into the caller's own seat array, so a caller can\n * attribute the result back without this module knowing what it is attributing to.\n */\nexport function resolveSection(\n sectionId: string,\n seats: readonly ExpandedSeat[],\n seatIndices: readonly number[],\n focal: Point,\n): ResolvedSection {\n // --- Group by authored row -------------------------------------------------\n const groups = new Map<string, { pts: Point[]; idx: number[] }>();\n for (const i of seatIndices) {\n const s = seats[i];\n // A seat with no row is its own group: it can never drag a real row's shape,\n // and it still gets a deck under it downstream.\n const key = s.rowId || `__seat-${i}`;\n let g = groups.get(key);\n if (!g) { g = { pts: [], idx: [] }; groups.set(key, g); }\n g.pts.push({ x: s.x, y: s.y });\n g.idx.push(i);\n }\n if (!groups.size) return { sectionId, rows: [], blockCount: 0 };\n\n const rows: ResolvedRow[] = [];\n for (const [id, g] of groups) {\n const ordered = orderAlongRow(g.pts, g.idx);\n let sum = 0;\n for (const p of ordered.pts) sum += Math.hypot(p.x - focal.x, p.y - focal.y);\n rows.push({\n id,\n pts: ordered.pts,\n seatIndices: ordered.seatIndices,\n blockId: -1,\n ordinal: 0,\n blockDepth: 0,\n focalDistance: sum / ordered.pts.length,\n });\n }\n\n // --- Typical row spacing, to scale the block-link threshold -----------------\n // The median nearest-row gap: robust to a section holding one isolated row, and\n // to blocks whose internal spacing differs from each other's.\n const bounds = rows.map((r) => boundOf(r.pts));\n const nearest: number[] = [];\n for (let i = 0; i < rows.length; i++) {\n let best = Infinity;\n for (let j = 0; j < rows.length; j++) {\n if (i === j) continue;\n // Prune before the exact test; the bound can only understate the gap.\n if (boundGap(bounds[i], bounds[j]) >= best) continue;\n const d = rowGap(rows[i].pts, rows[j].pts);\n if (d < best) best = d;\n }\n if (Number.isFinite(best)) nearest.push(best);\n }\n nearest.sort((a, b) => a - b);\n const typicalGap = nearest.length ? nearest[Math.floor(nearest.length / 2)] : 1;\n const linkDistance = Math.max(typicalGap * BLOCK_LINK_RATIO, 1e-6);\n\n // --- Blocks: connected components of \"close enough to be the same stand\" ----\n const adjacency: number[][] = rows.map(() => []);\n for (let i = 0; i < rows.length; i++) {\n for (let j = i + 1; j < rows.length; j++) {\n if (boundGap(bounds[i], bounds[j]) > linkDistance) continue;\n if (rowGap(rows[i].pts, rows[j].pts) <= linkDistance) {\n adjacency[i].push(j);\n adjacency[j].push(i);\n }\n }\n }\n let blockCount = 0;\n for (let i = 0; i < rows.length; i++) {\n if (rows[i].blockId !== -1) continue;\n const id = blockCount++;\n const stack = [i];\n rows[i].blockId = id;\n while (stack.length) {\n const k = stack.pop()!;\n for (const n of adjacency[k]) {\n if (rows[n].blockId !== -1) continue;\n rows[n].blockId = id;\n stack.push(n);\n }\n }\n }\n\n // --- Depth within each block, along that block's own rake axis --------------\n const byBlock = new Map<number, ResolvedRow[]>();\n for (const r of rows) {\n const a = byBlock.get(r.blockId) ?? [];\n a.push(r);\n byBlock.set(r.blockId, a);\n }\n // --- Concentric model first: is this section a set of arcs about one centre? --\n //\n // Tried before the per-block axis because it is the stronger statement. When it\n // holds, depth is a single scalar (radius) shared by EVERY block, so the wedges\n // of a tier are level with each other by construction rather than by each\n // block happening to resolve the same way. That is what a real tier is.\n // Fit PER ROW and take the median centre, rather than one circle through every\n // seat. A single algebraic fit over points lying on many concentric rings is\n // biased toward the middle ring — measured, it put the amphitheatre orchestra's\n // centre at y = -265 when the chart struck those arcs about y = -520, which\n // made true arcs look 27 % radially spread and got the concentric model\n // rejected. One row is a single arc, so its own fit is well conditioned, and\n // the median across rows is immune to the few rows that are short or straight.\n const centres: Point[] = [];\n for (const r of rows) {\n if (r.pts.length < 4) continue;\n const c = fitCircle(r.pts);\n if (!c) continue;\n if (!Number.isFinite(c.cx) || !Number.isFinite(c.cy)) continue;\n centres.push({ x: c.cx, y: c.cy });\n }\n const centre = centres.length >= 2\n ? ((): { cx: number; cy: number } => {\n const xs = centres.map((c) => c.x).sort((a, b) => a - b);\n const ys = centres.map((c) => c.y).sort((a, b) => a - b);\n const mid = Math.floor(centres.length / 2);\n return { cx: xs[mid], cy: ys[mid] };\n })()\n : null;\n if (centre) {\n const radiusOf = (p: Point): number => Math.hypot(p.x - centre.cx, p.y - centre.cy);\n let lo = Infinity, hi = -Infinity;\n const spreads: number[] = [];\n for (const r of rows) {\n let rlo = Infinity, rhi = -Infinity;\n for (const p of r.pts) {\n const d = radiusOf(p);\n if (d < rlo) rlo = d;\n if (d > rhi) rhi = d;\n }\n spreads.push(rhi - rlo);\n if (rlo < lo) lo = rlo;\n if (rhi > hi) hi = rhi;\n }\n const range = hi - lo;\n spreads.sort((a, b) => a - b);\n const p90 = spreads[Math.min(spreads.length - 1, Math.floor(spreads.length * 0.9))];\n if (range > 1e-9 && p90 / range <= CIRCLE_ROW_SPREAD_LIMIT) {\n const withRadius = rows.map((r) => {\n let sum = 0;\n for (const p of r.pts) sum += radiusOf(p);\n return { row: r, radius: sum / r.pts.length };\n });\n let minR = Infinity;\n for (const w of withRadius) if (w.radius < minR) minR = w.radius;\n withRadius.sort((a, b) => a.radius - b.radius);\n // Rows at the same radius are the SAME row split by an aisle, so they share\n // an ordinal and a level regardless of which block they landed in.\n let ordinal = -1, lastRadius = -Infinity;\n const tolerance = range / Math.max(1, withRadius.length) * 0.5;\n const ordered: ResolvedRow[] = [];\n for (const w of withRadius) {\n if (w.radius - lastRadius > tolerance) { ordinal++; lastRadius = w.radius; }\n w.row.ordinal = ordinal;\n w.row.blockDepth = w.radius - minR;\n ordered.push(w.row);\n }\n return { sectionId, rows: ordered, blockCount };\n }\n }\n\n const out: ResolvedRow[] = [];\n for (const [, group] of byBlock) {\n const axis = fitBlockAxis(group);\n if (axis) {\n // Rows are parallel and across the axis: order and measure along it. Depth\n // accumulates the REAL gap between consecutive rows, so an aisle break\n // inside a stand rises across it rather than being flattened to one step.\n const key = (r: ResolvedRow): number => {\n let sum = 0;\n for (const p of r.pts) sum += p.x * axis.x + p.y * axis.y;\n return sum / r.pts.length;\n };\n group.sort((a, b) => key(a) - key(b));\n let depth = 0;\n for (let i = 0; i < group.length; i++) {\n if (i > 0) depth += rowGap(group[i - 1].pts, group[i].pts);\n group[i].ordinal = i;\n group[i].blockDepth = depth;\n out.push(group[i]);\n }\n } else {\n // No usable axis — this block's rows are not across a common direction.\n // The amphitheatre orchestra is the real case: its rows run RADIALLY, each\n // spanning radius 6 to 310, so no ordering of them is a front-to-back\n // ordering. Sorting by focal distance and accumulating gaps ran the depth\n // to 8,585 units (197 m) and pinned the tier against its runaway clamp.\n //\n // Fall back to depth measured straight from the focal, relative to this\n // block's own front. It is the pre-block model, which handled this shape\n // correctly, and confining it to the block keeps the property that every\n // block of a tier starts at the tier's authored height.\n let front = Infinity;\n for (const r of group) if (r.focalDistance < front) front = r.focalDistance;\n group.sort((a, b) => a.focalDistance - b.focalDistance);\n for (let i = 0; i < group.length; i++) {\n group[i].ordinal = i;\n group[i].blockDepth = Math.max(0, group[i].focalDistance - front);\n out.push(group[i]);\n }\n }\n }\n return { sectionId, rows: out, blockCount };\n}\n","/**\n * Pure geometry helpers — no Konva, no DOM. Turns the parametric chart\n * document into concrete seat coordinates and computes bounding boxes.\n */\n\nimport type {\n AccessibilityType,\n BoothObject,\n ChartDoc,\n ChartObject,\n ExpandedSeat,\n Floor,\n LabelStyle,\n Point,\n RowObject,\n RectTableSeatCounts,\n RectTableSide,\n SeatConfidenceCoverage,\n SeatConfidenceEvidence,\n SeatOverride,\n SeatViewMetadata,\n SectionObject,\n TableObject,\n} from './types';\nimport { textLayoutMetrics } from './textLayout';\nimport { distributeAlongCubic } from './complexGeometry';\nimport { polygonInscribedAnchor } from './polygonAnchor';\nimport { translateSectionOutlinePath } from './sectionPath';\nimport { resolveSegmentedRowGroups, type SegmentedRowPlacement } from './segmentedRowModel';\nimport { seatDisplayLabel, seatInventoryLabel } from './seatNumbering';\nimport { METRES_PER_CHART_UNIT, SEATED_EYE_HEIGHT_M, sectionGeometry } from './units';\nimport { resolveSection } from './venueStructure';\n\n/** Resolve a seat override's accessibility, honouring the legacy boolean flag. */\nfunction overrideAccessibility(o: SeatOverride | undefined): AccessibilityType[] {\n if (!o) return [];\n if (o.accessibility && o.accessibility.length) {\n return o.wheelchairSpaceType && !o.accessibility.includes('wheelchair')\n ? ['wheelchair', ...o.accessibility]\n : o.accessibility;\n }\n if (o.wheelchairSpaceType) return ['wheelchair'];\n return o.accessible ? ['wheelchair'] : [];\n}\n\nconst DEG = Math.PI / 180;\n/** Seat visual radius (mirrors the renderer) — used only for bounds padding. */\nconst SEAT_R = 9;\n/** How far outside a table's body its seats sit. */\nconst TABLE_SEAT_OFFSET = 16;\n\n/** Rotate a local point clockwise by `deg` (screen coords, +y down) then translate. */\nfunction place(lx: number, ly: number, deg: number, origin: Point): { x: number; y: number } {\n const a = deg * DEG;\n const cos = Math.cos(a);\n const sin = Math.sin(a);\n return {\n x: origin.x + lx * cos - ly * sin,\n y: origin.y + lx * sin + ly * cos,\n };\n}\n\n/**\n * Expand a parametric row into seat positions.\n *\n * Straight (curve === 0): seat i sits at local (i·spacing, 0).\n *\n * Curved: seats lie on a circular arc. Per-seat angular step is\n * `curve/(seatCount-1)`; radius is derived so the chord between neighbours\n * equals seatSpacing → radius = spacing / (2·sin(step/2)). Seat 0 sits at the\n * bottom of the arc (local origin) with the circle centre directly above it at\n * (0,-radius); increasing index sweeps toward +x while the ends rise toward -y,\n * i.e. positive curve is concave toward -y (a row wrapping a stage above it).\n * Local points are then rotated by `rotation` and translated to `origin`.\n *\n * Numbering (labels only, never geometry): `seatNumbering.direction === 'rtl'`\n * numbers from the far physical end; `step === 2` produces odd/even numbering\n * (start at 1 → 1,3,5…; start at 2 → 2,4,6…).\n */\n/** Base (pre-override) seat centre per index — shared by expandRow + designer edit mode. */\nexport function rowSeatPositions(row: RowObject): Point[] {\n const { seatCount, seatSpacing, curve, rotation, origin } = row;\n const out: Point[] = [];\n if (row.path) return distributeAlongCubic(row.path, seatCount);\n if (seatCount <= 1) {\n if (seatCount === 1) out.push({ x: origin.x, y: origin.y });\n return out;\n }\n if (curve === 0) {\n for (let i = 0; i < seatCount; i++) out.push(place(i * seatSpacing, 0, rotation, origin));\n return out;\n }\n const arcStep = (curve / (seatCount - 1)) * DEG; // radians per seat\n const radius = seatSpacing / (2 * Math.sin(Math.abs(arcStep) / 2));\n // Centre above seat 0. φ measured from the downward vertical, growing with index.\n for (let i = 0; i < seatCount; i++) {\n const phi = i * arcStep;\n const lx = radius * Math.sin(phi);\n const ly = -radius + radius * Math.cos(phi); // ≤ 0 → ends rise toward -y\n out.push(place(lx, ly, rotation, origin));\n }\n return out;\n}\n\nfunction overrideMap(row: RowObject): Map<number, SeatOverride> {\n const m = new Map<number, SeatOverride>();\n if (row.overrides) for (const o of row.overrides) m.set(o.index, o);\n return m;\n}\n\n/** Sellable row slots: skipped slots are absent; empty wheelchair bays remain. */\nexport function rowInventoryCount(row: RowObject): number {\n const skipped = new Set((row.overrides ?? [])\n .filter((override) => override.skip && Number.isInteger(override.index)\n && override.index >= 0 && override.index < row.seatCount)\n .map((override) => override.index));\n return Math.max(0, row.seatCount - skipped.size);\n}\n\n/**\n * Every seat slot of a row INCLUDING skipped ones (designer seat-edit mode uses\n * this to draw un-skip handles). Overrides (dx/dy/label/categoryKey) are applied\n * but a skipped slot is flagged, not omitted.\n */\nexport interface RowSeatSlot {\n index: number;\n x: number;\n y: number;\n label: string;\n displayLabel: string;\n categoryKey: string;\n skipped: boolean;\n accessible: boolean;\n accessibility: AccessibilityType[];\n wheelchairSpaceType?: SeatOverride['wheelchairSpaceType'];\n commercial?: RowObject['commercial'];\n viewUrl?: string;\n viewMeta?: SeatViewMetadata;\n labelStyle?: LabelStyle;\n}\n\n/** Every authored table-chair slot, including skipped inventory. This mirrors\n * row seat slots so Designer, MCP and buyer/event expansion share one semantic\n * source while retaining the table's stable numeric slot identity. */\nexport interface TableSeatSlot extends RowSeatSlot {\n side?: RectTableSide;\n}\n\n/**\n * Seat slots for one physical row.\n *\n * `placement` makes the slots read as part of a logical segmented row: labels\n * continue across components (`A-8`, not a second `A-1`) using the logical row's\n * numbering scheme and total seat count.\n */\nexport function expandRowSlots(row: RowObject, placement?: SegmentedRowPlacement): RowSeatSlot[] {\n const ov = overrideMap(row);\n return rowSeatPositions(row).map((p, i) => {\n const o = ov.get(i);\n const accessibility = overrideAccessibility(o);\n // Inventory label stays PHYSICAL (stable booking identity); only the\n // human-facing label follows a segmented row's logical numbering. Both\n // resolved by `seatNumbering` so no surface can make this split differently.\n const inventoryLabel = seatInventoryLabel(row, i, o);\n const commercial = { ...row.commercial, ...o?.commercial };\n return {\n index: i,\n x: p.x + (o?.dx ?? 0),\n y: p.y + (o?.dy ?? 0),\n label: inventoryLabel,\n displayLabel: seatDisplayLabel(row, i, placement, o),\n categoryKey: o?.categoryKey ?? row.categoryKey,\n skipped: !!o?.skip,\n accessible: accessibility.length > 0,\n accessibility,\n wheelchairSpaceType: o?.wheelchairSpaceType,\n commercial: Object.values(commercial).some((value) => value !== undefined && value !== false && value !== '') ? commercial : undefined,\n viewUrl: o?.viewFromSeatUrl ?? row.viewFromSeatUrl,\n viewMeta: o?.viewFromSeatUrl ? o.viewFromSeatMeta : row.viewFromSeatMeta,\n labelStyle: o?.labelStyle,\n };\n });\n}\n\nexport function expandRow(row: RowObject): ExpandedSeat[] {\n const seats: ExpandedSeat[] = [];\n for (const slot of expandRowSlots(row)) {\n if (slot.skipped) continue; // physical seat absent; numbering gap preserved\n seats.push({\n id: `${row.id}:${slot.index}`,\n label: slot.label,\n displayLabel: slot.displayLabel === slot.label ? undefined : slot.displayLabel,\n x: slot.x,\n y: slot.y,\n rowId: row.id,\n categoryKey: slot.categoryKey,\n accessible: slot.accessible || undefined,\n accessibility: slot.accessibility.length ? slot.accessibility : undefined,\n wheelchairSpaceType: slot.wheelchairSpaceType,\n commercial: slot.commercial,\n viewUrl: slot.viewUrl,\n viewMeta: slot.viewMeta,\n labelStyle: slot.labelStyle,\n });\n }\n return seats;\n}\n\n/**\n * Expand a table into its perimeter seats.\n *\n * Round: seats spread evenly on a circle of radius `radius + 16`, the first at\n * angle `rotation` (degrees, clockwise from +x). Rect: seats line the top and\n * bottom edges (split evenly, any remainder to the top), 16u outside the edge,\n * the whole set rotated about the table centre.\n */\n/** Legacy rect tables distribute aggregate capacity round-robin across enabled\n * sides, then emit chairs in canonical top/bottom/left/right order. */\nexport function tableSeatCountsBySide(t: TableObject): RectTableSeatCounts {\n if (t.seatCountsBySide) return { ...t.seatCountsBySide };\n const enabled = t.sides && t.sides.length ? t.sides : ['top', 'bottom'];\n const order = (['top', 'bottom', 'left', 'right'] as const).filter((side) => enabled.includes(side));\n const counts: RectTableSeatCounts = { top: 0, bottom: 0, left: 0, right: 0 };\n if (!order.length) return counts;\n const n = Math.max(0, Math.round(t.seatCount));\n for (let index = 0; index < n; index++) counts[order[index % order.length]] += 1;\n return counts;\n}\n\n/** Expand every authored table chair, including skipped slots needed by the\n * Designer to restore inventory. */\nexport function expandTableSlots(t: TableObject): TableSeatSlot[] {\n const seats: TableSeatSlot[] = [];\n const n = Math.max(0, Math.round(t.seatCount));\n if (n === 0) return seats;\n const overrides = new Map((t.overrides ?? []).map((override) => [override.index, override]));\n const mk = (index: number, x: number, y: number, side?: RectTableSide): TableSeatSlot => {\n const override = overrides.get(index);\n const accessibility = overrideAccessibility(override);\n const label = override?.label ?? `${t.label}-${index + 1}`;\n const displayPrefix = t.displayLabel ?? t.label;\n return {\n index,\n label,\n displayLabel: override?.displayLabel ?? `${displayPrefix}-${index + 1}`,\n x: x + (override?.dx ?? 0),\n y: y + (override?.dy ?? 0),\n categoryKey: override?.categoryKey ?? t.categoryKey,\n skipped: !!override?.skip,\n accessible: accessibility.length > 0,\n accessibility,\n wheelchairSpaceType: override?.wheelchairSpaceType,\n commercial: override?.commercial,\n viewUrl: override?.viewFromSeatUrl,\n viewMeta: override?.viewFromSeatUrl ? override.viewFromSeatMeta : undefined,\n labelStyle: override?.labelStyle,\n ...(side ? { side } : {}),\n };\n };\n\n if (t.shape === 'round') {\n const R = (t.radius ?? 40) + TABLE_SEAT_OFFSET;\n const base = t.rotation * DEG;\n const arc = Math.max(0, Math.min(360, t.seatArc ?? 360));\n if (arc >= 360 || n === 1) {\n for (let i = 0; i < n; i++) {\n const a = base + (i / n) * 2 * Math.PI;\n seats.push(mk(i, t.center.x + R * Math.cos(a), t.center.y + R * Math.sin(a)));\n }\n return seats;\n }\n const arcRad = arc * DEG;\n const halfGap = (2 * Math.PI - arcRad) / 2;\n const start = base + halfGap;\n for (let i = 0; i < n; i++) {\n const a = start + (i / (n - 1)) * arcRad;\n seats.push(mk(i, t.center.x + R * Math.cos(a), t.center.y + R * Math.sin(a)));\n }\n return seats;\n }\n\n const w = t.width ?? 80;\n const h = t.height ?? 50;\n const counts = tableSeatCountsBySide(t);\n const order = ['top', 'bottom', 'left', 'right'] as const;\n let idx = 0;\n for (const side of order) {\n const count = counts[side];\n for (let j = 0; j < count; j++) {\n let localX: number;\n let localY: number;\n if (side === 'top') {\n localX = -w / 2 + ((j + 0.5) * w) / count;\n localY = -h / 2 - TABLE_SEAT_OFFSET;\n } else if (side === 'bottom') {\n localX = -w / 2 + ((j + 0.5) * w) / count;\n localY = h / 2 + TABLE_SEAT_OFFSET;\n } else if (side === 'left') {\n localX = -w / 2 - TABLE_SEAT_OFFSET;\n localY = -h / 2 + ((j + 0.5) * h) / count;\n } else {\n localX = w / 2 + TABLE_SEAT_OFFSET;\n localY = -h / 2 + ((j + 0.5) * h) / count;\n }\n const point = place(localX, localY, t.rotation, t.center);\n seats.push(mk(idx++, point.x, point.y, side));\n }\n }\n return seats;\n}\n\n/** Sellable individual table chairs. Grouped tables own one atomic inventory\n * unit elsewhere and therefore retain their full authored chair capacity. */\nexport function tableInventoryCount(t: TableObject): number {\n if (t.bookAsWhole || t.variableOccupancy) return Math.max(0, Math.round(t.seatCount));\n return expandTableSlots(t).filter((slot) => !slot.skipped).length;\n}\n\nexport function expandTable(t: TableObject): ExpandedSeat[] {\n return expandTableSlots(t).filter((slot) => !slot.skipped).map((slot) => ({\n id: `${t.id}:${slot.index}`,\n label: slot.label,\n ...(slot.displayLabel !== slot.label ? { displayLabel: slot.displayLabel } : {}),\n x: slot.x,\n y: slot.y,\n rowId: t.id,\n categoryKey: slot.categoryKey,\n ...(slot.accessible ? { accessible: true } : {}),\n ...(slot.accessibility.length ? { accessibility: slot.accessibility } : {}),\n ...(slot.wheelchairSpaceType ? { wheelchairSpaceType: slot.wheelchairSpaceType } : {}),\n ...(slot.commercial ? { commercial: slot.commercial } : {}),\n ...(slot.viewUrl ? { viewUrl: slot.viewUrl } : {}),\n ...(slot.viewUrl && slot.viewMeta ? { viewMeta: slot.viewMeta } : {}),\n ...(slot.labelStyle ? { labelStyle: slot.labelStyle } : {}),\n }));\n}\n\n/** Expand a booth into its single bookable block unit. */\nexport function expandBooth(b: BoothObject): ExpandedSeat[] {\n return [\n {\n id: `${b.id}:0`,\n label: b.label,\n ...(b.displayLabel && b.displayLabel !== b.label ? { displayLabel: b.displayLabel } : {}),\n x: b.center.x,\n y: b.center.y,\n rowId: b.id,\n categoryKey: b.categoryKey,\n kind: 'booth',\n },\n ];\n}\n\n/** Ray-cast point-in-polygon test — odd crossings ⇒ inside. */\nexport function pointInPolygon(p: Point, poly: Point[]): boolean {\n let inside = false;\n for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {\n const xi = poly[i].x;\n const yi = poly[i].y;\n const xj = poly[j].x;\n const yj = poly[j].y;\n const hit = yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi;\n if (hit) inside = !inside;\n }\n return inside;\n}\n\nfunction pointOnPolygonBoundary(p: Point, poly: Point[]): boolean {\n return poly.some((start, index) => {\n const end = poly[(index + 1) % poly.length];\n const cross = (p.y - start.y) * (end.x - start.x) - (p.x - start.x) * (end.y - start.y);\n if (Math.abs(cross) > 1e-7) return false;\n const dot = (p.x - start.x) * (end.x - start.x) + (p.y - start.y) * (end.y - start.y);\n const lengthSquared = (end.x - start.x) ** 2 + (end.y - start.y) ** 2;\n return dot >= -1e-7 && dot <= lengthSquared + 1e-7;\n });\n}\n\nexport function pointInPolygonWithHoles(p: Point, outer: Point[], holes: Point[][] | undefined): boolean {\n return pointInPolygon(p, outer)\n && !(holes ?? []).some((hole) => pointInPolygon(p, hole) || pointOnPolygonBoundary(p, hole));\n}\n\n/**\n * Stable interior label anchor: the centre of the largest circle that fits in\n * the polygon (holes excluded).\n *\n * This used to return the average-of-vertices centroid whenever that happened\n * to be inside, and only fell back to a coarse 24×24 grid search when it was\n * not. \"Inside\" is a much weaker property than \"has room\": on a traced arena\n * the outer arc of a wedge carries dozens of sampled vertices and the radial\n * edges carry two, so the average is dragged onto the arc and the label pill —\n * far wider than a point — straddled the boundary into the next section.\n * Measured on the 36-section traced arena corpus, 15 of 36 anchors had under\n * 75% of the clearance actually available, some as little as 43%.\n *\n * `polygonInscribedAnchor` is deterministic and coordinate-free, so callers may\n * memoize it on geometry alone.\n */\nexport function polygonLabelPoint(outer: Point[], holes: Point[][] | undefined): Point {\n return polygonInscribedAnchor(outer, holes).point;\n}\n\n/** Average of polygon vertices (v1 centroid — good enough for labels/membership). */\nfunction polygonCentroid(pts: Point[]): Point {\n if (!pts.length) return { x: 0, y: 0 };\n let x = 0;\n let y = 0;\n for (const p of pts) {\n x += p.x;\n y += p.y;\n }\n return { x: x / pts.length, y: y / pts.length };\n}\n\n/**\n * Visual centre of any object — used for spatial section membership and for\n * rotating an object about its centre. Rows: centroid of expanded seats (or\n * origin when empty); tables/booths: centre; GA/section: polygon centroid;\n * shape: bbox centre; text: its position.\n */\nexport function objectCenter(o: ChartObject): Point {\n switch (o.type) {\n case 'row': {\n const seats = expandRow(o);\n if (!seats.length) return { x: o.origin.x, y: o.origin.y };\n let x = 0;\n let y = 0;\n for (const s of seats) {\n x += s.x;\n y += s.y;\n }\n return { x: x / seats.length, y: y / seats.length };\n }\n case 'table':\n case 'booth':\n return { x: o.center.x, y: o.center.y };\n case 'gaArea':\n return polygonCentroid(o.points);\n case 'section':\n return polygonCentroid(o.outline);\n case 'text':\n return { x: o.position.x, y: o.position.y };\n case 'shape':\n if (o.points && o.points.length) return polygonCentroid(o.points);\n if (o.x != null && o.y != null && o.width != null && o.height != null) {\n return { x: o.x + o.width / 2, y: o.y + o.height / 2 };\n }\n return { x: o.x ?? 0, y: o.y ?? 0 };\n case 'decorImage':\n return { x: o.x + o.width / 2, y: o.y + o.height / 2 };\n }\n}\n\nfunction samePoints(left: Point[], right: Point[]): boolean {\n return left.length === right.length\n && left.every((point, index) => point.x === right[index].x && point.y === right[index].y);\n}\n\nfunction sameGASurfaceAsSection(object: ChartObject, section: SectionObject): boolean {\n if (object.type !== 'gaArea' || !samePoints(object.points, section.outline)) return false;\n const objectHoles = object.holes ?? [];\n const sectionHoles = section.holes ?? [];\n return objectHoles.length === sectionHoles.length\n && objectHoles.every((hole, index) => samePoints(hole, sectionHoles[index]));\n}\n\n/**\n * Resolve one bookable object's owning physical section using the canonical\n * first-match rule. Generated reference inventory may name a logical section,\n * but that provenance is trusted only when the stored geometry confirms it.\n * Keeping this primitive in layout lets section inventory, category painting,\n * and buyer view inheritance share one ownership decision.\n */\nexport function owningSectionForObject(\n objects: ChartObject[],\n object: ChartObject,\n): SectionObject | undefined {\n const sections = objects.filter((candidate): candidate is SectionObject => candidate.type === 'section');\n const referencedLogicalId = 'referenceInventorySource' in object\n ? object.referenceInventorySource?.logicalSectionId\n : undefined;\n const center = objectCenter(object);\n const referencedOwner = referencedLogicalId\n ? sections.find((section) => (\n (section.logicalSectionId ?? section.id) === referencedLogicalId\n && (sameGASurfaceAsSection(object, section)\n || pointInPolygonWithHoles(center, section.outline, section.holes))\n ))\n : undefined;\n return referencedOwner\n ?? sections.find((section) => pointInPolygonWithHoles(center, section.outline, section.holes));\n}\n\n/**\n * Normalized floor list (Batch 5): a multi-floor chart's `floors`, or a synthetic\n * single floor wrapping a single-floor chart's `objects`. Every consumer that needs\n * to reason about floors goes through this so single-floor charts stay untouched.\n */\nexport function floorsOf(doc: ChartDoc): Floor[] {\n if (doc.floors && doc.floors.length) return doc.floors;\n return [{\n id: 'floor-0',\n name: 'Main',\n objects: doc.objects,\n focalPoint: doc.focalPoint,\n referenceImage: doc.referenceImage,\n backgroundImage: doc.backgroundImage,\n }];\n}\n\n/** Objects of one floor by id (defaults to the first floor). */\nexport function floorObjects(doc: ChartDoc, floorId?: string): ChartObject[] {\n const floors = floorsOf(doc);\n return (floorId ? floors.find((f) => f.id === floorId) : floors[0])?.objects ?? [];\n}\n\n/** Every object across ALL floors — the whole venue (single-floor = `doc.objects`). */\nexport function allObjects(doc: ChartDoc): ChartObject[] {\n return doc.floors && doc.floors.length ? doc.floors.flatMap((f) => f.objects) : doc.objects;\n}\n\n/** A copy of an object translated by (dx, dy) — every coordinate field shifted. */\nfunction translateObject(o: ChartObject, dx: number, dy: number): ChartObject {\n const p = (pt: Point): Point => ({ x: pt.x + dx, y: pt.y + dy });\n const pts = (a: Point[]): Point[] => a.map(p);\n switch (o.type) {\n case 'row':\n return { ...o, origin: p(o.origin) };\n case 'table':\n case 'booth':\n return { ...o, center: p(o.center) };\n case 'gaArea':\n return { ...o, points: pts(o.points), ...(o.holes ? { holes: o.holes.map(pts) } : {}) };\n case 'section':\n return {\n ...o,\n outline: pts(o.outline),\n ...(o.outlinePath ? { outlinePath: translateSectionOutlinePath(o.outlinePath, dx, dy) } : {}),\n ...(o.holes ? { holes: o.holes.map(pts) } : {}),\n };\n case 'text':\n return { ...o, position: p(o.position) };\n case 'shape':\n return {\n ...o,\n ...(o.points ? { points: pts(o.points) } : {}),\n ...(o.x != null ? { x: o.x + dx } : {}),\n ...(o.y != null ? { y: o.y + dy } : {}),\n };\n case 'decorImage':\n return { ...o, x: o.x + dx, y: o.y + dy };\n }\n}\n\n/**\n * Multi-floor 3D stack (Batch 5): flatten every floor into ONE doc with floor `i`\n * lifted by `i * spread` in −y, so the isometric view shows the floors as stacked\n * decks (ground at the bottom). Returns a single-floor doc (no `floors`). Meant\n * only for the 3D overview render — 2D still shows one floor via `floorObjects`.\n */\nexport function stackFloors(doc: ChartDoc, spread = 900): ChartDoc {\n if (!doc.floors || doc.floors.length < 2) return doc;\n const objects: ChartObject[] = [];\n doc.floors.forEach((f, i) => {\n const dy = -i * spread;\n for (const o of f.objects) objects.push(dy === 0 ? o : translateObject(o, 0, dy));\n });\n return { ...doc, objects, floors: undefined };\n}\n\nexport interface ExpandChartOptions {\n /** Physical height for a single-floor projection extracted from a multi-floor\n * document. Full multi-floor documents resolve each floor directly. */\n floorBaseHeightM?: number;\n /** Resolve per-seat 3D eye heights (default true). Two-dimensional consumers\n * such as thumbnails can disable this expensive stand-structure pass. */\n resolveEyeHeights?: boolean;\n}\n\nfunction expandFloorObjects(\n objects: ChartObject[],\n zones: ChartDoc['zones'],\n fallbackFocal: Point | undefined,\n activeConfigurationId: string | undefined,\n /**\n * Lowest-priority authored view-from-seat fallback for this floor: the\n * floor-level url if set, else the venue/chart-level default. Applied as the\n * final `??=` after seat/row/section, so any closer photo always wins. Absent\n * ⇒ seats with no closer photo keep `viewUrl` undefined (generated panorama).\n */\n viewFallback?: { url: string; meta?: SeatViewMetadata },\n confidenceFallback?: { evidence: SeatConfidenceEvidence; coverage: SeatConfidenceCoverage },\n): ExpandedSeat[] {\n const out: ExpandedSeat[] = [];\n const segmented = resolveSegmentedRowGroups(objects);\n const appliesToConfiguration = (meta: SeatViewMetadata | undefined): boolean => (\n !meta?.eventConfigurationId || meta.eventConfigurationId === activeConfigurationId\n );\n const confidenceApplies = (evidence: SeatConfidenceEvidence | undefined): evidence is SeatConfidenceEvidence => (\n !!evidence && (!evidence.eventConfigurationId || evidence.eventConfigurationId === activeConfigurationId)\n );\n const resolvedConfidence = (\n evidence: SeatConfidenceEvidence | undefined,\n coverage: SeatConfidenceCoverage,\n ) => confidenceApplies(evidence) ? { ...evidence, coverage } : undefined;\n\n for (const obj of objects) {\n let seats: ExpandedSeat[] = [];\n if (obj.type === 'row') seats = expandRow(obj);\n else if (obj.type === 'table') seats = expandTable(obj);\n else if (obj.type === 'booth') seats = expandBooth(obj);\n if (!seats.length) continue;\n // `expandRow` applies authored row and exact-seat media before this\n // inheritance pass. Drop a source that belongs to another event build so\n // the next valid scope—or generated live geometry—can resolve truthfully.\n for (const seat of seats) {\n if (seat.viewUrl && !appliesToConfiguration(seat.viewMeta)) {\n seat.viewUrl = undefined;\n seat.viewMeta = undefined;\n }\n }\n if (obj.type === 'row') {\n const logical = segmented.get(obj.id);\n if (logical) {\n const overrides = new Map((obj.overrides ?? []).map((override) => [override.index, override]));\n for (const seat of seats) {\n const physicalIndex = Number(seat.id.slice(seat.id.lastIndexOf(':') + 1));\n if (!Number.isInteger(physicalIndex)) continue;\n seat.logicalRowId = logical.groupId;\n seat.logicalSeatIndex = logical.adjacencyOffset + physicalIndex;\n // A seat-level display override remains the highest-precedence copy.\n // Left to `expandRow`, which drops it when it equals the inventory\n // label; only the DERIVED logical label is recomputed here.\n if (!overrides.get(physicalIndex)?.displayLabel) {\n seat.displayLabel = seatDisplayLabel(obj, physicalIndex, logical);\n }\n if (!seat.viewUrl && logical.viewFromSeatUrl && appliesToConfiguration(logical.viewFromSeatMeta)) {\n seat.viewUrl = logical.viewFromSeatUrl;\n seat.viewMeta = logical.viewFromSeatMeta;\n }\n const exactEvidence = overrides.get(physicalIndex)?.confidenceEvidence;\n seat.confidenceEvidence = resolvedConfidence(exactEvidence, 'exact-seat')\n ?? resolvedConfidence(obj.confidenceEvidence, 'row-representative')\n ?? resolvedConfidence(logical.confidenceEvidence, 'row-representative');\n }\n }\n }\n const owner = owningSectionForObject(objects, obj);\n const inheritedView = owner?.viewFromSeatUrl;\n const zone = owner?.zone ? zones?.find((candidate) => candidate.id === owner.zone) : undefined;\n const resolvedFocal = zone?.focalPoint ?? fallbackFocal;\n for (const seat of seats) {\n if (!seat.confidenceEvidence) {\n const physicalIndex = Number(seat.id.slice(seat.id.lastIndexOf(':') + 1));\n const exactEvidence = (obj.type === 'row' || obj.type === 'table') && Number.isInteger(physicalIndex)\n ? obj.overrides?.find((override) => override.index === physicalIndex)?.confidenceEvidence\n : undefined;\n seat.confidenceEvidence = resolvedConfidence(exactEvidence, 'exact-seat')\n ?? (obj.type === 'row' ? resolvedConfidence(obj.confidenceEvidence, 'row-representative') : undefined);\n }\n seat.confidenceEvidence ??= resolvedConfidence(owner?.confidenceEvidence, 'section-representative')\n ?? (confidenceFallback\n ? resolvedConfidence(confidenceFallback.evidence, confidenceFallback.coverage)\n : undefined);\n if (!seat.viewUrl && inheritedView && appliesToConfiguration(owner?.viewFromSeatMeta)) {\n seat.viewUrl = inheritedView;\n seat.viewMeta = owner?.viewFromSeatMeta;\n }\n // Floor > venue default: the last authored tier before a generated panorama.\n if (!seat.viewUrl && viewFallback && appliesToConfiguration(viewFallback.meta)) {\n seat.viewUrl = viewFallback.url;\n seat.viewMeta = viewFallback.meta;\n }\n if (owner) seat.sectionId = owner.logicalSectionId ?? owner.id;\n if (owner?.zone) seat.zoneId = owner.zone;\n if (resolvedFocal) seat.focalPoint = { ...resolvedFocal };\n }\n out.push(...seats);\n }\n return out;\n}\n\n/** Expand every seat-bearing object across all floors (rows, tables, booths).\n * Multi-floor ownership is resolved one floor at a time: local coordinates may\n * overlap between floors and must never assign a seat to another floor's section. */\nexport function expandChart(doc: ChartDoc, options: ExpandChartOptions = {}): ExpandedSeat[] {\n if (doc.floors?.length) {\n const out: ExpandedSeat[] = [];\n for (const floor of doc.floors) {\n const floorFocal = floor.focalPoint ?? doc.focalPoint;\n // Per-floor photo overrides the venue default for seats on this floor.\n const floorView = floor.viewFromSeatUrl\n ? { url: floor.viewFromSeatUrl, meta: floor.viewFromSeatMeta }\n : doc.viewFromSeatUrl\n ? { url: doc.viewFromSeatUrl, meta: doc.viewFromSeatMeta }\n : undefined;\n const confidenceFallback = floor.confidenceEvidence\n ? { evidence: floor.confidenceEvidence, coverage: 'floor-representative' as const }\n : doc.confidenceEvidence\n ? { evidence: doc.confidenceEvidence, coverage: 'venue-representative' as const }\n : undefined;\n const seats = expandFloorObjects(\n floor.objects,\n doc.zones,\n floorFocal,\n doc.eventConfigurationId,\n floorView,\n confidenceFallback,\n );\n if (options.resolveEyeHeights !== false) {\n assignEyeHeights(floor.objects, floor.focalPoint ?? doc.focalPoint, floor.baseHeightM ?? 0, seats);\n }\n out.push(...seats);\n }\n return out;\n }\n const out = expandFloorObjects(\n doc.objects,\n doc.zones,\n doc.focalPoint,\n doc.eventConfigurationId,\n doc.viewFromSeatUrl ? { url: doc.viewFromSeatUrl, meta: doc.viewFromSeatMeta } : undefined,\n doc.confidenceEvidence\n ? { evidence: doc.confidenceEvidence, coverage: 'venue-representative' }\n : undefined,\n );\n if (options.resolveEyeHeights !== false) {\n assignEyeHeights(doc.objects, doc.focalPoint, options.floorBaseHeightM ?? 0, out);\n }\n return out;\n}\n\n/**\n * Phase B2: annotate each expanded seat with a real-world eye height (metres above\n * the focal/stage datum) for the auto-360° generator. Resolved once at expand time\n * — never per render frame — so the 13k-seat render path pays no cost.\n *\n * `eyeHeightM = section front-edge height + row rise + seated eye height`, where\n * the ROW RISE is derived from the seat's DRAWN radial depth into its section\n * (chart units → metres × tan(rake)), NOT a hard-coded row pitch. Deriving rise\n * from drawn geometry (the same distances the panorama already uses horizontally)\n * is the binding fix for the 30–40% under-rise the sightline de-risk study flagged\n * (docs/3d-sightline-derisk-2026-07-21.md §7).\n *\n * A chart with no elevated/raked/height-authored section skips the spatial pass\n * entirely and every seat resolves to the flat seated-eye baseline — so legacy\n * charts stay pixel-identical and `expandChart`'s other callers pay nothing.\n */\nfunction assignEyeHeights(\n objects: ChartObject[],\n focal: Point | undefined,\n floorBaseHeightM: number,\n seats: ExpandedSeat[],\n): void {\n const sections = objects.filter((o): o is SectionObject => o.type === 'section');\n const hasGeometry = floorBaseHeightM > 0\n || sections.some((s) => s.height !== undefined || s.rake !== undefined || (s.elevation ?? 0) > 0);\n if (!sections.length || !hasGeometry || !focal) {\n for (const seat of seats) seat.eyeHeightM = floorBaseHeightM + SEATED_EYE_HEIGHT_M;\n return;\n }\n // Pass 1: owning section (first drawn section containing the seat), grouped by\n // row so each section can fit its own rake axis.\n const owner = new Array<SectionObject | null>(seats.length);\n const geo = new Map<string, { height: number; rake: number }>();\n const seatsBySection = new Map<string, number[]>();\n for (let i = 0; i < seats.length; i++) {\n const seat = seats[i];\n const sec = sections.find((s) => pointInPolygonWithHoles({ x: seat.x, y: seat.y }, s.outline, s.holes)) ?? null;\n owner[i] = sec;\n if (!sec) continue;\n if (!geo.has(sec.id)) geo.set(sec.id, sectionGeometry(sec, { floorBaseHeightM }));\n const list = seatsBySection.get(sec.id);\n if (list) list.push(i); else seatsBySection.set(sec.id, [i]);\n }\n\n // Pass 2: resolve each section's blocks and per-row depth, then bake a level.\n //\n // The SAME resolver the 3D deck is built from (`venueStructure.ts`). Depth is\n // measured back from each BLOCK's own front row, not from the venue focal:\n // measuring from the focal raised a bowl's side wedges as though they sat\n // further back, by up to 4.4 m on the amphitheatre gallery. And it must be the\n // same call in both files — a seat's eye height and the deck it stands on\n // disagreeing is the trap that made every offset constant chart-specific.\n const seatLevel = new Array<number | undefined>(seats.length).fill(undefined);\n for (const [secId, indices] of seatsBySection) {\n const g = geo.get(secId);\n if (!g) continue;\n const rakeTan = g.rake > 0 ? Math.tan((g.rake * Math.PI) / 180) : 0;\n const structure = resolveSection(secId, seats, indices, focal);\n for (const row of structure.rows) {\n const riseM = row.blockDepth * METRES_PER_CHART_UNIT * rakeTan;\n const level = g.height + riseM;\n for (const si of row.seatIndices) seatLevel[si] = level;\n }\n }\n\n for (let i = 0; i < seats.length; i++) {\n const seat = seats[i];\n const sec = owner[i];\n if (!sec) { seat.eyeHeightM = floorBaseHeightM + SEATED_EYE_HEIGHT_M; continue; }\n const g = geo.get(sec.id)!;\n seat.eyeHeightM = (seatLevel[i] ?? g.height) + SEATED_EYE_HEIGHT_M;\n }\n}\n\nconst PAD = 40;\n\n/** Axis-aligned bounds over every object plus the background image, with padding. */\nexport function chartBounds(doc: ChartDoc): { x: number; y: number; width: number; height: number } {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n const acc = (x: number, y: number) => {\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n };\n\n for (const obj of allObjects(doc)) {\n if (obj.type === 'row') {\n // Bounds need only authored 2D coordinates. Going through expandChart()\n // also resolves section ownership, labels, view metadata and (unless\n // explicitly disabled) 3D rake/elevation for every seat. On Mega Stadium\n // that unrelated work dominated both fit-to-view and the Designer minimap.\n const overrides = new Map((obj.overrides ?? []).map((override) => [override.index, override]));\n rowSeatPositions(obj).forEach((point, index) => {\n const override = overrides.get(index);\n if (override?.skip) return;\n acc(point.x + (override?.dx ?? 0), point.y + (override?.dy ?? 0));\n });\n } else if (obj.type === 'gaArea') {\n for (const p of obj.points) acc(p.x, p.y);\n } else if (obj.type === 'section') {\n for (const p of obj.outline) acc(p.x, p.y);\n } else if (obj.type === 'decorImage') {\n acc(obj.x, obj.y);\n acc(obj.x + obj.width, obj.y + obj.height);\n } else if (obj.type === 'shape') {\n if (obj.points && obj.points.length) {\n for (const p of obj.points) acc(p.x, p.y);\n } else if (obj.x != null && obj.y != null && obj.width != null && obj.height != null) {\n acc(obj.x, obj.y);\n acc(obj.x + obj.width, obj.y + obj.height);\n }\n } else if (obj.type === 'table') {\n const off = TABLE_SEAT_OFFSET + SEAT_R;\n const ext =\n obj.shape === 'round'\n ? (obj.radius ?? 40) + off\n : Math.max((obj.width ?? 80) / 2, (obj.height ?? 50) / 2) + off; // rotation-agnostic over-approximation\n acc(obj.center.x - ext, obj.center.y - ext);\n acc(obj.center.x + ext, obj.center.y + ext);\n } else if (obj.type === 'booth') {\n const ext = Math.max(obj.width, obj.height) / 2;\n acc(obj.center.x - ext, obj.center.y - ext);\n acc(obj.center.x + ext, obj.center.y + ext);\n } else if (obj.type === 'text') {\n const box = textLayoutMetrics(obj);\n acc(obj.position.x, obj.position.y);\n acc(obj.position.x + box.width, obj.position.y + box.height);\n }\n }\n\n for (const image of [doc.referenceImage, doc.backgroundImage]) {\n if (!image) continue;\n const { center, width } = image;\n const bh = (width * 3) / 4; // assume 4:3 when the true aspect is unknown at bounds-time\n acc(center.x - width / 2, center.y - bh / 2);\n acc(center.x + width / 2, center.y + bh / 2);\n }\n\n // Empty document → a sane default box around the focal point.\n if (!isFinite(minX)) {\n const f = doc.focalPoint ?? { x: 0, y: 0 };\n return { x: f.x - 200, y: f.y - 200, width: 400, height: 400 };\n }\n\n return {\n x: minX - PAD,\n y: minY - PAD,\n width: maxX - minX + PAD * 2,\n height: maxY - minY + PAD * 2,\n };\n}\n","/**\n * A section's RAKE FIELD — the scalar \"depth\" that its seating rises along.\n *\n * ## The defect this exists to fix\n *\n * Both the eye-height model (`layout.ts`) and the 3D seating surface\n * (`view3d/scene/surface.ts`) used to raise a seat by its radial distance from\n * ONE venue focal point. That is only correct when a section's rows are\n * concentric arcs about that focal.\n *\n * Measured on the amphitheatre gallery, whose rows are straight blocks: a single\n * row's seats span a radial-distance range of 105 chart units on the\n * centre-facing blocks and 240 on the side blocks. Since height is a function of\n * that distance, one row's seats landed at DIFFERENT heights — a within-row deck\n * spread of 2.30 m on the side blocks against 1.05 m on the centre-facing ones.\n * On screen the rows visibly tilt, and the tilt differs block to block, which is\n * exactly the left/right asymmetry the owner reported.\n *\n * ## Why a per-section AXIS is not enough\n *\n * The first attempt fitted one straight rake axis per section by PCA over its row\n * centroids, and scored it against the radial model. It did not fix the\n * amphitheatre, because `sec-gall` is a SINGLE section containing six wedge\n * blocks at six different orientations. No single axis — straight or radial —\n * fits them all, so the fit correctly fell back to radial and the tilt survived.\n * Sections containing several blocks are normal, not exotic.\n *\n * ## The model that does work\n *\n * Depth is defined by the section's OWN ROWS. Each row gets one depth value (the\n * mean distance of its seats from the focal), and the field at an arbitrary point\n * is an inverse-distance blend of the nearest rows' depths.\n *\n * The properties that matter:\n *\n * - **Exact on a row.** A point on a row has zero distance to it, so it takes\n * that row's depth exactly and every seat in the row lands at ONE height.\n * The tilt cannot come back, whatever the row's orientation.\n * - **Orientation-free.** Straight blocks, arcs, fans, in-the-round and blocks\n * at six different angles in one section all work, because nothing is fitted\n * to a direction.\n * - **Continuous.** It is a field over the plane, not a per-seat lookup. The 3D\n * cap is tessellated at points BETWEEN seats and needs a value and a gradient\n * there; a per-row lookup would leave the deck undefined between rows and put\n * the seats back off the surface they stand on.\n *\n * That last point is why quantising per row inside `assignEyeHeights` was tried\n * and removed: the renderer cannot follow a step function it has no definition\n * for, and the two models diverging is the trap that made every offset constant\n * chart-specific.\n */\n\nimport type { Point } from './types';\n\n/** A resolved row: its seats as an ordered polyline, plus its depth ordinate. */\nexport interface RakeRowFit {\n /** The row's seats, ordered along the row. */\n readonly pts: readonly Point[];\n /** Mean distance of the row's seats from the focal — what rise is a function of. */\n readonly depth: number;\n}\n\nexport interface SectionRake {\n readonly kind: 'radial' | 'rows';\n /** Depth ordinate at a chart-unit point; rise is a function of this. */\n depthAt(x: number, y: number): number;\n /** Unit gradient direction of `depthAt` — turns a rake angle into a normal. */\n gradientAt(x: number, y: number): readonly [number, number];\n /** Number of rows the field was built from (0 for the radial fallback). */\n readonly rowCount: number;\n /**\n * The section's rows, ORDERED BY DEPTH (front first).\n *\n * The deck is built directly from these — one level ribbon per row — rather\n * than by tessellating `depthAt`. See `deckBands.ts` for why: a section holding\n * several blocks at different heights has a genuine cliff between them, and no\n * continuous field can be both level on every row and trackable by refinement.\n */\n readonly rows: readonly RakeRowFit[];\n /** Index into `rows` of the row nearest a point, or -1 when there are none. */\n nearestRow(x: number, y: number): number;\n}\n\n/** One row's member points, in any order. */\nexport interface RakeRow {\n points: Point[];\n}\n\n/** How many nearest rows contribute to a blended sample. */\nconst BLEND_ROWS = 3;\n\n/**\n * A row further than this multiple of the nearest row's distance contributes\n * nothing. Just above 2 so the two rows bracketing a point midway between them\n * both count (their distances differ by at most the row pitch), while a row on\n * the far side of an aisle or beyond the last row does not.\n */\nconst BLEND_DISTANCE_RATIO = 2.2;\n\n/** Step used for the finite-difference gradient, chart units. */\nconst GRAD_STEP = 0.5;\n\nfunction radialRake(focal: Point): SectionRake {\n return {\n kind: 'radial',\n rowCount: 0,\n rows: [],\n nearestRow: () => -1,\n depthAt: (x, y) => Math.hypot(x - focal.x, y - focal.y),\n gradientAt: (x, y) => {\n const dx = x - focal.x, dy = y - focal.y;\n const d = Math.hypot(dx, dy);\n // At the focal the gradient is undefined; the surface is flat there anyway.\n return d < 1e-9 ? [0, 0] : [dx / d, dy / d];\n },\n };\n}\n\ninterface RowFit {\n /**\n * The row as an ordered POLYLINE through its own seats — not a straight chord.\n *\n * A chord was tried first and broke the arena: its upper-bowl rows are strong\n * arcs, so a seat at the end of a row sits far from the chord between the row's\n * extremes. The blend below then treated that seat as \"between rows\" and gave\n * it a neighbouring row's depth, which put the within-row spread UP from 0.07 m\n * to 2.83 m. Distance must be measured to the row's real shape, so that every\n * one of its seats reads as being exactly on it.\n */\n pts: Point[];\n /** Row centroid, and the radius of a circle about it containing the row. */\n cx: number;\n cy: number;\n radius: number;\n /** The row's depth ordinate — mean distance of its seats from the focal. */\n depth: number;\n}\n\n/**\n * Reduce a row to a segment plus a depth.\n *\n * The direction comes from the row's own extent (the two furthest-apart seats),\n * not from a fitted line: it is exact for a straight row, good enough for a\n * gently curved one, and cannot be thrown off by an outlier the way a\n * least-squares fit through few points can.\n */\nfunction fitRow(points: Point[], focal: Point): RowFit | null {\n if (!points.length) return null;\n let cx = 0, cy = 0, depth = 0;\n for (const p of points) {\n cx += p.x; cy += p.y;\n depth += Math.hypot(p.x - focal.x, p.y - focal.y);\n }\n const n = points.length;\n cx /= n; cy /= n; depth /= n;\n\n // Order the seats along the row so consecutive pairs are real segments. The\n // chord direction is only used for this ORDERING, never for distance, so a\n // curved row orders correctly even though its chord is a poor fit to it.\n let ax = points[0], far = -1;\n for (const p of points) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > far) { far = d; ax = p; }\n }\n let dx = ax.x - cx, dy = ax.y - cy;\n const len = Math.hypot(dx, dy);\n if (len > 1e-9) { dx /= len; dy /= len; } else { dx = 1; dy = 0; }\n const pts = [...points].sort((p, q) =>\n ((p.x - cx) * dx + (p.y - cy) * dy) - ((q.x - cx) * dx + (q.y - cy) * dy));\n\n let radius = 0;\n for (const p of pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > radius) radius = d;\n }\n return { pts, cx, cy, radius, depth };\n}\n\n/** Distance from a point to a row's polyline. */\nfunction distToRow(r: RowFit, x: number, y: number): number {\n const pts = r.pts;\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** How many rows survive the cheap bounding-circle prefilter. */\nconst CANDIDATE_ROWS = 8;\n\n/**\n * Inverse-distance blend of the nearest rows' depths.\n *\n * Only the nearest few rows contribute, so a distant block on the far side of the\n * venue cannot drag a section's near rows. Squared inverse distance makes the\n * nearest row dominate quickly, which keeps the field flat ALONG a row and\n * varying across it — the shape a rake actually has.\n */\nfunction sampleRows(rows: RowFit[], x: number, y: number): number {\n // Prefilter on each row's bounding circle. `centroidDist - radius` is a true\n // LOWER bound on the polyline distance, so this cannot discard a row that would\n // have won — it just keeps the exact polyline test off ~90 % of the rows, which\n // is what makes the field affordable per cap vertex.\n const candD = new Array<number>(CANDIDATE_ROWS).fill(Infinity);\n const candI = new Array<number>(CANDIDATE_ROWS).fill(-1);\n for (let i = 0; i < rows.length; i++) {\n const r = rows[i];\n const lower = Math.hypot(x - r.cx, y - r.cy) - r.radius;\n for (let k = 0; k < CANDIDATE_ROWS; k++) {\n if (lower < candD[k]) {\n for (let j = CANDIDATE_ROWS - 1; j > k; j--) { candD[j] = candD[j - 1]; candI[j] = candI[j - 1]; }\n candD[k] = lower; candI[k] = i;\n break;\n }\n }\n }\n\n // Nearest BLEND_ROWS by insertion (the list is short and this avoids a sort).\n const bestD = new Array<number>(BLEND_ROWS).fill(Infinity);\n const bestI = new Array<number>(BLEND_ROWS).fill(-1);\n for (const i of candI) {\n if (i < 0) continue;\n const d = distToRow(rows[i], x, y);\n for (let k = 0; k < BLEND_ROWS; k++) {\n if (d < bestD[k]) {\n for (let j = BLEND_ROWS - 1; j > k; j--) { bestD[j] = bestD[j - 1]; bestI[j] = bestI[j - 1]; }\n bestD[k] = d; bestI[k] = i;\n break;\n }\n }\n }\n if (bestI[0] < 0) return 0;\n // Exactly on a row: take its depth, so a seat is never blended off its own row.\n if (bestD[0] < 1e-6) return rows[bestI[0]].depth;\n\n // Blend only across rows at COMPARABLE distance. Absolute inverse-distance\n // weighting was tried and produced the cap's worst error: in the empty parts of\n // a section outline — beyond the last row, or the gap between two blocks — the\n // two nearest rows can both be hundreds of units away, so their weights stay\n // comparable and the field goes on interpolating between them across the void.\n // Measured, 79 of the gallery's 97 out-of-tolerance sample points sat more than\n // 100 units from ANY seat, and a cap triangle spanning that region missed the\n // surface by up to 10.84 m.\n //\n // A relative cutoff makes the far field settle to the nearest row's depth, i.e.\n // locally CONSTANT, which a flat triangle interpolates exactly. Inside the\n // seating the nearest rows are all within the cutoff, so blending is unchanged\n // and rows stay level.\n const cutoff = bestD[0] * BLEND_DISTANCE_RATIO;\n let num = 0, den = 0;\n for (let k = 0; k < BLEND_ROWS; k++) {\n const i = bestI[k];\n if (i < 0 || bestD[k] > cutoff) continue;\n const w = 1 / (bestD[k] * bestD[k]);\n num += w * rows[i].depth;\n den += w;\n }\n return den > 0 ? num / den : rows[bestI[0]].depth;\n}\n\nfunction rowsRake(rows: RowFit[]): SectionRake {\n const depthAt = (x: number, y: number): number => sampleRows(rows, x, y);\n const nearestRow = (x: number, y: number): number => {\n let best = Infinity, bestI = -1;\n for (let i = 0; i < rows.length; i++) {\n // Bounding-circle lower bound first; only test the polyline if it can win.\n const r = rows[i];\n if (Math.hypot(x - r.cx, y - r.cy) - r.radius >= best) continue;\n const d = distToRow(r, x, y);\n if (d < best) { best = d; bestI = i; }\n }\n return bestI;\n };\n return {\n kind: 'rows',\n rowCount: rows.length,\n rows,\n nearestRow,\n depthAt,\n gradientAt: (x, y) => {\n // Central differences: the blend has no closed form, and shading only needs\n // the direction. A half-unit step is far below row pitch, so this tracks\n // the field rather than smoothing across rows.\n const gx = (depthAt(x + GRAD_STEP, y) - depthAt(x - GRAD_STEP, y)) / (2 * GRAD_STEP);\n const gy = (depthAt(x, y + GRAD_STEP) - depthAt(x, y - GRAD_STEP)) / (2 * GRAD_STEP);\n const len = Math.hypot(gx, gy);\n return len < 1e-9 ? [0, 0] : [gx / len, gy / len];\n },\n };\n}\n\n/**\n * Build a section's rake field from its rows.\n *\n * `rows` should be the section's member seats grouped by row. With fewer than two\n * usable rows there is nothing to build a field from, so the radial model is kept\n * — which is also how every chart behaved before this, leaving a row-less or\n * single-row section unchanged.\n */\nexport function buildSectionRake(rows: RakeRow[], focal: Point): SectionRake {\n const fits: RowFit[] = [];\n for (const r of rows) {\n const f = fitRow(r.points, focal);\n if (f) fits.push(f);\n }\n if (fits.length < 2) return radialRake(focal);\n // Front-first ordering is the contract `rows` promises, and the deck builder\n // relies on it to pair each ribbon with the riser below it.\n fits.sort((a, b) => a.depth - b.depth);\n return rowsRake(fits);\n}\n","/**\n * The raked deck, built from a section's ROWS instead of from its outline.\n *\n * ## Why the deck is not a tessellated surface any more\n *\n * The previous deck was one cap over the section outline, tessellated against a\n * height field and refined until it tracked it. That works only while the field\n * is smooth and convex. Once height is defined so that every row is LEVEL — the\n * fix for rows visibly tilting, see `core/rake.ts` — the field stops being\n * either:\n *\n * - A section commonly holds several blocks at different heights (the\n * amphitheatre gallery is one section holding six wedges). Any field that is\n * level on each row must JUMP between blocks. Measured, that jump drove the\n * cap's worst error to 11 m against a 0.15 m bound.\n * - Smoothing the jump away puts the tilt straight back. The two requirements\n * are contradictory, so no amount of refinement resolves it.\n *\n * The jump is not an artefact — it is a step between two blocks, which is a\n * WALL. Walls are geometry. So the deck is emitted as geometry that already has\n * the right shape rather than as a surface something has to approximate:\n *\n * - one **ribbon** per row, horizontal, at that row's level;\n * - a **riser** dropping from each ribbon's front edge to the level below;\n * - a flat **landing** under everything, for the parts of the outline that hold\n * no rows (aisles, margins, the gaps between blocks).\n *\n * Every deck vertex therefore lies on a row, where the height is exactly defined.\n * A seat's clearance above its own ribbon is exact by construction instead of\n * measured against a tessellation error, and because every ribbon is horizontal\n * the whole deck shades with a single up normal.\n */\n\nimport type { Point } from '../../core/types';\nimport type { RGB } from '../palette';\nimport polygonClipping from 'polygon-clipping';\nimport { CHART_UNITS_PER_METRE } from '../../core/units';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport earcut from 'earcut';\nimport { MeshBuilder, M } from './geometry';\n\n/** A row ready to draw: its seats as a polyline, and the level it sits at. */\nexport interface BandRow {\n readonly pts: readonly Point[];\n /** World-metre height of this row's ribbon. */\n readonly y: number;\n /**\n * The row's depth ordinate, chart units — its MEAN distance from the focal.\n *\n * Carried through rather than recomputed here. Deriving it from the row's first\n * seat was tried and buried 20 seats: in a section holding several blocks, one\n * row's first seat and the next row's first seat can be in different blocks\n * entirely, so the implied row gap was wrong by a large factor and a ribbon\n * reached forward far enough to cover the row in front of it.\n */\n readonly depth: number;\n /**\n * Which block (stand) this row belongs to.\n *\n * Neighbour and riser resolution must stay inside a block. Across blocks the\n * nearest row is often the one across an aisle, at a different level, so a\n * ribbon would size itself against a stand it is not part of and a riser would\n * step down onto the wrong deck.\n */\n readonly blockId: number;\n /**\n * A ready-made deck FOOTPRINT for this entry, chart units, convex and closed —\n * used instead of the row-ribbon path when the entry is not a row at all.\n *\n * A table is the case this exists for. Its seats ring a centre rather than\n * running across the view, so \"offset the polyline sideways\" describes nothing:\n * whichever way the ribbon is offset it covers an arc of the ring and leaves\n * the rest of it hanging. Measured on the nightclub VIP terrace (14 six-seat\n * round tables), authoring any rake at all left 5 seats off-deck at 1°, 18 at\n * 5° and 24 at 10°. The count barely moves with the rake because the fault is\n * in the deck's PLAN SHAPE, not in its height — a taller step just exposes the\n * ground that was already missing.\n *\n * The patch is the convex hull of the entry's seats grown outward by the same\n * clearance a ribbon carries ({@link MIN_REACH_U}), so a table's deck reaches\n * past its outermost seat CENTRE by a full seat-dot allowance exactly as a\n * ribbon does past its end seat. It is supplied by the surface builder, which\n * is where the authored objects are known — deriving \"is this a table\" by\n * guessing at seat geometry here would re-answer a question the chart already\n * answers.\n */\n readonly patch?: readonly Point[];\n}\n\n/**\n * How far a ribbon reaches FORWARD, as a fraction of the gap to the row in front.\n *\n * Deliberately past the halfway line. A ribbon is built by offsetting its own\n * row's polyline, and the ribbon in front is built by offsetting a different\n * polyline with a different vertex count, so their edges do not land on exactly\n * the same curve. Meeting them at 0.5 each would leave hairline gaps showing the\n * landing through the deck. Overlapping instead is free: the ribbon behind is\n * HIGHER, so it simply covers the seam, and the riser closes the vertical face.\n */\nconst FRONT_REACH = 0.58;\n\n/**\n * Smallest step worth drawing a riser for, world metres.\n *\n * A 1e-4 threshold was tried and emitted 142 needle triangles with aspect ratios\n * to 17,776: risers 5 m long and 0.3 mm tall, where two rows resolved to almost\n * the same level. Such a step is invisible, and the ribbons already overlap\n * enough to close the seam without it. 1 cm is below anything a viewer can see\n * and far above the degenerate range.\n */\nconst MIN_RISER_M = 0.01;\n\n/**\n * Smallest half-width a ribbon may have, chart units.\n *\n * A seat's dot has a real world radius, so a ribbon narrower than that leaves the\n * dot's rim hanging over the aisle beside it — the same defect the deck padding\n * (`outsetRing`) exists to prevent on the cap path, and it is the same 1.5x\n * margin here. Two orchestra seats were still failing on ribbon width alone\n * before this floor; row pitch happened to be barely over a dot diameter there.\n */\nexport const MIN_REACH_U = SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE;\n\n/** How far a ribbon reaches BACK, as a fraction of the gap to the row behind. */\nconst BACK_REACH = 0.5;\n\n/**\n * How far a ribbon extends past its end seats, as a fraction of the SEAT spacing\n * along the row — not of the row gap.\n *\n * The row gap was tried and buried 23 seats. A section can hold several blocks\n * side by side (the amphitheatre gallery holds six), and the aisle between two\n * blocks is often narrower than a row gap, so a ribbon extended by a row gap\n * reached over its neighbour's seats — which sit at a DIFFERENT level, so the\n * overhanging ribbon painted over them. Seat spacing is the right measure: it is\n * exactly enough to carry the end seat's own dot and cannot cross an aisle.\n */\nconst END_REACH = 0.6;\n\n/**\n * Outward normals along a polyline, pointing in the direction of increasing\n * depth (away from `focal`).\n *\n * Taken from the row's own local direction rather than from the height field's\n * gradient: the field is a step function now, so its gradient is zero almost\n * everywhere and undefined on the steps.\n */\nfunction rowNormals(pts: readonly Point[], focal: Point): Array<readonly [number, number]> {\n const n = pts.length;\n const raw: Array<readonly [number, number]> = [];\n for (let i = 0; i < n; i++) {\n // Central difference gives a smooth normal along a curved row; the ends fall\n // back to their single adjacent segment.\n const a = pts[Math.max(0, i - 1)];\n const b = pts[Math.min(n - 1, i + 1)];\n let dx = b.x - a.x, dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-9) { raw.push([0, 0]); continue; }\n dx /= len; dy /= len;\n raw.push([-dy, dx]);\n }\n\n // Orient the WHOLE ROW at once, from the sum of the per-vertex tests, rather\n // than flipping each vertex on its own.\n //\n // Per-vertex orientation was tried and left a hole in the deck. The test is\n // \"does this normal point away from the focal\", and its value passes through\n // zero when a row runs radially — straight at the stage rather than across it.\n // The amphitheatre orchestra has such rows, and there the sign flipped between\n // two adjacent vertices, so the quad between them was built with its two ends\n // offset in OPPOSITE directions: a bow-tie that covers neither side properly.\n // One seat's dot rim ended up over the void.\n //\n // A row is a single object with one front and one back, so the decision belongs\n // to the row. Summing is also the stable form of the same test: it is dominated\n // by the vertices where the answer is unambiguous.\n let vote = 0;\n for (let i = 0; i < n; i++) {\n vote += (pts[i].x - focal.x) * raw[i][0] + (pts[i].y - focal.y) * raw[i][1];\n }\n const flip = vote < 0;\n return flip ? raw.map(([x, y]) => [-x, -y] as const) : raw;\n}\n\n/** Extend a polyline past both ends along its end tangents by `by` chart units. */\nfunction extendEnds(pts: readonly Point[], by: number): Point[] {\n if (pts.length < 2 || by <= 0) return [...pts];\n const out = [...pts];\n const dir = (p: Point, q: Point): Point => {\n const dx = q.x - p.x, dy = q.y - p.y;\n const len = Math.hypot(dx, dy) || 1;\n return { x: dx / len, y: dy / len };\n };\n const head = dir(out[1], out[0]);\n const tail = dir(out[out.length - 2], out[out.length - 1]);\n out.unshift({ x: out[0].x + head.x * by, y: out[0].y + head.y * by });\n out.push({\n x: out[out.length - 1].x + tail.x * by,\n y: out[out.length - 1].y + tail.y * by,\n });\n return out;\n}\n\n/**\n * Angular step used when rounding a patch's corners, radians (~15°).\n *\n * The offset of a convex hull by a fixed distance is that hull swept by a DISC,\n * so every corner is a circular arc. Sampling it is the only approximation in\n * the patch, and it is taken on the OUTSIDE (see `CORNER_SEGMENTS` below) so the\n * sampled polygon still contains the true offset rather than cutting inside it.\n */\nconst CORNER_STEP_RAD = Math.PI / 12;\n\n/**\n * Convex hull of a point set (Andrew's monotone chain), counter-clockwise.\n *\n * Returns fewer than 3 points when the input is a point or a segment; callers\n * fall back to a circle there, which is the correct offset of both.\n */\nfunction convexHull(pts: readonly Point[]): Point[] {\n if (pts.length < 3) return [...pts];\n const s = [...pts].sort((a, b) => (a.x - b.x) || (a.y - b.y));\n const cross = (o: Point, a: Point, b: Point): number =>\n (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);\n const half = (src: readonly Point[]): Point[] => {\n const out: Point[] = [];\n for (const p of src) {\n while (out.length >= 2 && cross(out[out.length - 2], out[out.length - 1], p) <= 0) out.pop();\n out.push(p);\n }\n out.pop();\n return out;\n };\n const hull = [...half(s), ...half([...s].reverse())];\n return hull.length >= 3 ? hull : [...pts];\n}\n\n/**\n * The deck footprint a cluster of seats needs: their convex hull grown outward\n * by `pad` chart units, as a closed counter-clockwise ring.\n *\n * Exact rather than approximate, and that matters: the property the caller is\n * buying is \"every seat centre lies at least `pad` inside this ring\", which is\n * what makes a table's deck carry its own seat dots. A hull offset is the\n * Minkowski sum of the hull with a disc of radius `pad`, and that sum contains\n * every seat's own disc by construction. Sampling the corner arcs is the one\n * place accuracy could leak, so the samples are placed on the CIRCUMSCRIBED\n * radius — the sampled polygon then contains the true arc instead of chording\n * across it, and the guarantee survives the discretisation.\n */\nexport function seatClusterPatch(pts: readonly Point[], pad: number): Point[] {\n if (!pts.length || !(pad > 0)) return [];\n const hull = convexHull(pts);\n // Round a corner from one outward normal to the next, on the circumscribed\n // radius so the chord between samples still clears `pad`.\n const arc = (v: Point, from: number, to: number, out: Point[]): void => {\n let sweep = to - from;\n while (sweep < 0) sweep += Math.PI * 2;\n while (sweep > Math.PI * 2) sweep -= Math.PI * 2;\n const steps = Math.max(1, Math.ceil(sweep / CORNER_STEP_RAD));\n const r = pad / Math.cos(sweep / steps / 2);\n for (let k = 0; k <= steps; k++) {\n const a = from + (sweep * k) / steps;\n out.push({ x: v.x + Math.cos(a) * r, y: v.y + Math.sin(a) * r });\n }\n };\n\n if (hull.length < 3) {\n // A single seat, or two seats (a collinear \"hull\"). The bounding circle grown\n // by `pad` covers the true offset in both cases — it over-covers a segment\n // slightly, which is harmless at the scale of one or two chairs and avoids a\n // second geometric path for a case a real table never hits.\n let cx = 0, cy = 0;\n for (const p of hull) { cx += p.x; cy += p.y; }\n cx /= hull.length || 1; cy /= hull.length || 1;\n let far = 0;\n for (const p of hull) far = Math.max(far, Math.hypot(p.x - cx, p.y - cy));\n const out: Point[] = [];\n const steps = Math.max(3, Math.ceil((Math.PI * 2) / CORNER_STEP_RAD));\n const r = (far + pad) / Math.cos(Math.PI / steps);\n for (let k = 0; k < steps; k++) {\n const a = (k / steps) * Math.PI * 2;\n out.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r });\n }\n return out;\n }\n\n const n = hull.length;\n // Outward normal of the edge leaving vertex i, for a CCW hull.\n const edgeAngle: number[] = [];\n for (let i = 0; i < n; i++) {\n const a = hull[i], b = hull[(i + 1) % n];\n edgeAngle.push(Math.atan2(-(b.x - a.x), b.y - a.y));\n }\n const out: Point[] = [];\n for (let i = 0; i < n; i++) {\n // At each vertex sweep from the previous edge's normal to this edge's, then\n // walk this edge offset outward. Consecutive vertices' runs join along the\n // shared edge, so the concatenation closes into one ring.\n arc(hull[i], edgeAngle[(i + n - 1) % n], edgeAngle[i], out);\n }\n return dedupeAdjacent(out);\n}\n\n/** Perpendicular distance from a point to a row's polyline, chart units. */\nfunction distToPolyline(pts: readonly Point[], x: number, y: number): number {\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** A row's SPATIAL neighbours: how wide its ribbon is, and what it steps down to. */\ninterface Neighbourhood {\n /** Perpendicular distance to the nearest other row, chart units. */\n pitch: number;\n /** World-metre level of the nearest row in FRONT, or null if this row is first. */\n belowY: number | null;\n}\n\n/**\n * Resolve each row's neighbours by POSITION, not by order in the array.\n *\n * `rows` arrives sorted by depth across the whole section, and a section\n * routinely holds several blocks. Two consecutive entries in that ordering are\n * usually the same row index in two DIFFERENT blocks, at almost the same depth —\n * so treating them as neighbours gave a row pitch of nearly zero. Measured, that\n * produced ribbons a couple of centimetres wide (so seat rims hung off the deck\n * in 394 of 400 samples) and risers 5 m long by 0.3 mm tall.\n *\n * The spatially nearest row is the real neighbour whatever block it is in, and\n * the nearest row that is closer to the focal is the one this ribbon steps down\n * onto.\n */\nexport function rowNeighbourhoods(rows: readonly BandRow[]): Neighbourhood[] {\n // Bounding circle per row, so the exact polyline test is skipped for rows that\n // cannot possibly be nearest. This is O(rows^2) at heart and a big venue has\n // thousands of rows in a section.\n const bounds = rows.map((r) => {\n let cx = 0, cy = 0;\n for (const p of r.pts) { cx += p.x; cy += p.y; }\n const n = r.pts.length || 1;\n cx /= n; cy /= n;\n let rad = 0;\n for (const p of r.pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > rad) rad = d;\n }\n return { cx, cy, rad };\n });\n // Probe from points ON the row, at a quarter, a half and three quarters along\n // it. The centroid was tried and broke the arena: its rows are strong arcs, and\n // an arc's centroid lies well inside the curve rather than on it, so distances\n // measured from there are not the row pitch at all (arena rims went 0 -> 272\n // off deck). A point on the polyline measures the true perpendicular gap, and\n // three of them keep one odd row end from setting the whole ribbon's width.\n const probesOf = (pts: readonly Point[]): Point[] => {\n const n = pts.length;\n if (n <= 2) return [...pts];\n return [pts[Math.floor(n * 0.25)], pts[Math.floor(n * 0.5)], pts[Math.floor(n * 0.75)]];\n };\n\n const out: Neighbourhood[] = [];\n for (let i = 0; i < rows.length; i++) {\n const probes = probesOf(rows[i].pts);\n const pitches: number[] = [];\n let bestFrontD = Infinity, belowY: number | null = null;\n for (const c of probes) {\n let nearest = Infinity;\n for (let j = 0; j < rows.length; j++) {\n // Neighbours are rows at a DIFFERENT LEVEL, not rows in the same block.\n //\n // \"Same block\" was tried and broke the concert hall: block detection\n // legitimately fragments one stand into several (sec-terr-0's 12 rows\n // resolved into 4), and a fragment's front neighbour then sits in another\n // block and was skipped — so the pitch came from a distant row, the\n // ribbon reached far forward, and it buried the seats of the row in front\n // by 0.20 m.\n //\n // Level is the property that actually distinguishes the two cases: a\n // lateral neighbour across an aisle shares this row's level (it is the\n // same row, split), while the row in front or behind does not.\n if (j === i || Math.abs(rows[j].y - rows[i].y) < 1e-3) continue;\n // Admissible lower bound: the true distance can never be smaller than\n // this, so a row that cannot beat EITHER accumulator can be skipped\n // outright. Guarding on only one of them left the prune almost never\n // firing, which is the whole cost of this function.\n const b = bounds[j];\n const lower = Math.hypot(c.x - b.cx, c.y - b.cy) - b.rad;\n if (lower >= nearest && lower >= bestFrontD) continue;\n const d = distToPolyline(rows[j].pts, c.x, c.y);\n if (d < nearest) nearest = d;\n // \"In front\" = nearer the focal, i.e. a smaller depth ordinate.\n if (rows[j].depth < rows[i].depth && d < bestFrontD) {\n bestFrontD = d;\n belowY = rows[j].y;\n }\n }\n if (Number.isFinite(nearest) && nearest > 0) pitches.push(nearest);\n }\n pitches.sort((a, b) => a - b);\n // Median: robust to one probe landing beside an aisle or a short row.\n const pitch = pitches.length ? pitches[Math.floor(pitches.length / 2)] : 1;\n out.push({ pitch, belowY });\n }\n return out;\n}\n\nexport interface BandColors {\n /** Ribbon (tread) top colour, AO already applied. */\n tread: RGB;\n /** Riser face colour, AO already applied. */\n riser: RGB;\n}\n\n\n/** One row's ribbon: the polyline it is built on, its normals, and its reaches. */\ninterface Ribbon {\n pts: Point[];\n nrm: Array<readonly [number, number]>;\n front: number;\n back: number;\n}\n\n/**\n * Resolve one row's ribbon geometry.\n *\n * Shared by the mesh emitter and the footprint builder so the drawn ribbons and\n * the block outline extruded beneath them are derived from ONE computation. They\n * disagreeing is the same class of bug as the cap and the seats disagreeing.\n */\nfunction ribbonOf(rows: readonly BandRow[], i: number, nbrs: Neighbourhood[], focal: Point): Ribbon | null {\n const row = rows[i];\n // A \"row\" of one seat is a free-standing seat, not a row — it still needs a\n // deck under it, so give it a short segment across the view direction and let\n // the normal ribbon path carry it. Skipping these left their dots hanging\n // over the landing.\n const rowPts = row.pts.length >= 2\n ? [...row.pts]\n : row.pts.length === 1\n ? ((): Point[] => {\n const p = row.pts[0];\n let dx = p.x - focal.x, dy = p.y - focal.y;\n const len = Math.hypot(dx, dy) || 1;\n dx /= len; dy /= len;\n // Across the line of sight, half a pitch each way.\n const h = Math.max(nbrs[i].pitch, 1e-3) * 0.5;\n return [{ x: p.x + dy * h, y: p.y - dx * h }, { x: p.x - dy * h, y: p.y + dx * h }];\n })()\n : [];\n if (rowPts.length < 2) return null;\n\n let seatSpan = 0, spanN = 0;\n for (let k = 1; k < rowPts.length; k++) {\n const d = Math.hypot(rowPts[k].x - rowPts[k - 1].x, rowPts[k].y - rowPts[k - 1].y);\n if (d > 1e-6) { seatSpan += d; spanN++; }\n }\n const spacing = spanN > 0 ? seatSpan / spanN : 0;\n const pts = extendEnds(rowPts, Math.max(spacing * END_REACH, MIN_REACH_U));\n return {\n pts,\n nrm: rowNormals(pts, focal),\n front: Math.max(nbrs[i].pitch * FRONT_REACH, MIN_REACH_U),\n back: Math.max(nbrs[i].pitch * BACK_REACH, MIN_REACH_U),\n };\n}\n\n/** A block: the footprint its ribbons cover, and the level its base sits at. */\nexport type { Neighbourhood };\n\nexport interface DeckFootprint {\n /** Outer ring, chart units. */\n outline: Point[];\n /** Any interior rings (an enclosed gap between rows). */\n holes: Point[][];\n /** World-metre height of this block's lowest ribbon — the top of its base. */\n topY: number;\n}\n\n/**\n * The footprint of each BLOCK in a section, as the union of its ribbons.\n *\n * This replaces extruding the section's whole authored outline under the deck.\n * That outline is drawn generously — it reaches past the last row and across the\n * gaps between blocks — so a single flat plate at the front row's level stuck out\n * around the seating as a large slab, and every block's end read as a square cut\n * through that slab rather than as the edge of a stand.\n *\n * Unioning the ribbons instead yields one polygon per block automatically (the\n * amphitheatre's three sections resolve to 4, 5 and 6 blocks — exactly their\n * wedge counts), each hugging its own seating, and each carrying its own base\n * level rather than sharing the section's. Costs 7–13 ms per section at build\n * time, which is paid once.\n */\n/**\n * Extra width given to a ribbon in the block footprint, chart units (~3 cm).\n *\n * The footprint is the base the ribbons stand on, so a small margin keeps its\n * edge from coinciding exactly with a tread edge and z-fighting it.\n */\nconst FOOTPRINT_MARGIN_U = 1.5;\n\n/**\n * Douglas-Peucker tolerance for a block outline, chart units (~7 mm).\n *\n * The base carries no detail at this scale — the SEATS are carried by the\n * ribbons above it — and long runs of near-collinear vertices are what earcut\n * fans into needle triangles.\n */\nconst FOOTPRINT_TOLERANCE_U = 0.3;\n\n/** Douglas-Peucker on an open point run. */\nfunction simplifyRun(pts: Point[], tol: number): Point[] {\n if (pts.length < 3) return pts;\n const a = pts[0], b = pts[pts.length - 1];\n const dx = b.x - a.x, dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n let worst = -1, worstI = -1;\n for (let i = 1; i < pts.length - 1; i++) {\n const p = pts[i];\n const d = len > 1e-12\n ? Math.abs((p.x - a.x) * dy - (p.y - a.y) * dx) / len\n : Math.hypot(p.x - a.x, p.y - a.y);\n if (d > worst) { worst = d; worstI = i; }\n }\n if (worst <= tol || worstI < 0) return [a, b];\n const left = simplifyRun(pts.slice(0, worstI + 1), tol);\n const right = simplifyRun(pts.slice(worstI), tol);\n return [...left.slice(0, -1), ...right];\n}\n\n/** Simplify a closed ring, keeping it closed. */\nfunction simplifyRing(ring: Point[], tol: number): Point[] {\n if (ring.length < 4) return ring;\n const out = simplifyRun([...ring, ring[0]], tol);\n out.pop();\n return out.length >= 3 ? out : ring;\n}\n\nexport function deckFootprints(rows: readonly BandRow[], focal: Point, shared?: Neighbourhood[]): DeckFootprint[] {\n if (rows.length < 2) return [];\n const nbrs = shared ?? rowNeighbourhoods(rows);\n\n // Group by the block the structure resolver already identified. The first\n // implementation instead recovered blocks by UNIONING every ribbon and reading\n // off the resulting polygons — correct, but it made a boolean solve the\n // question a resolved field already answers, and ribbons overlap each other by\n // design (FRONT_REACH is past the halfway line), which is the worst case for a\n // sweep-line union. Profiled at 48 % of scene build on a 50k venue and 58 % on\n // 99k, growing superlinearly: 1,024 ms -> 5,285 ms for twice the seats.\n const byBlock = new Map<number, number[]>();\n for (let i = 0; i < rows.length; i++) {\n const a = byBlock.get(rows[i].blockId);\n if (a) a.push(i); else byBlock.set(rows[i].blockId, [i]);\n }\n\n const out: DeckFootprint[] = [];\n for (const [, allIndices] of byBlock) {\n // A patch entry (a table) stands on its own base. It is not part of the\n // ribbon stack — including it in the front-to-back trace would drag the\n // block's outline through a ring of seats that has no front or back — and a\n // pedestal per table is what a raked table terrace physically is.\n const indices: number[] = [];\n for (const i of allIndices) {\n const patch = rows[i].patch;\n if (patch && patch.length >= 3) out.push({ outline: [...patch], holes: [], topY: rows[i].y });\n else indices.push(i);\n }\n if (!indices.length) continue;\n // Front to back, so the ring below traces the block's real outline.\n indices.sort((a, b) => rows[a].depth - rows[b].depth);\n const ribbons = indices.map((i) => ribbonOf(rows, i, nbrs, focal));\n const usable = ribbons.filter((r): r is Ribbon => r !== null);\n if (!usable.length) continue;\n\n const frontOf = (r: Ribbon, k: number): Point => ({\n x: r.pts[k].x - r.nrm[k][0] * (r.front + FOOTPRINT_MARGIN_U),\n y: r.pts[k].y - r.nrm[k][1] * (r.front + FOOTPRINT_MARGIN_U),\n });\n const backOf = (r: Ribbon, k: number): Point => ({\n x: r.pts[k].x + r.nrm[k][0] * (r.back + FOOTPRINT_MARGIN_U),\n y: r.pts[k].y + r.nrm[k][1] * (r.back + FOOTPRINT_MARGIN_U),\n });\n\n // Trace the boundary directly: the front row's front edge, down the right\n // ends, the back row's back edge reversed, and up the left ends. O(rows)\n // instead of a boolean over every ribbon.\n const first = usable[0], last = usable[usable.length - 1];\n const ring: Point[] = [];\n for (let k = 0; k < first.pts.length; k++) ring.push(frontOf(first, k));\n for (const r of usable) ring.push(backOf(r, r.pts.length - 1));\n for (let k = last.pts.length - 1; k >= 0; k--) ring.push(backOf(last, k));\n for (let i = usable.length - 1; i >= 0; i--) ring.push(frontOf(usable[i], 0));\n\n const deduped = dedupeAdjacent(ring);\n let topY = Infinity;\n for (const i of indices) if (rows[i].y < topY) topY = rows[i].y;\n if (!Number.isFinite(topY)) continue;\n\n // VERIFY the traced ring before trusting it. Tracing assumes a block's rows\n // stack cleanly front to back; a block that folds or wraps can trace a\n // self-intersecting ring, which still has positive area but covers the wrong\n // ground — on the West End theatre that left seats standing over a base that\n // had moved out from under them. The property that matters is that the base\n // covers its own ribbons, so test exactly that and fall back to the boolean\n // union for the rare block that fails.\n const covers = deduped.length >= 3\n && Math.abs(ringArea(deduped)) > 1e-6\n && usable.every((r) => {\n const mid = r.pts[Math.floor(r.pts.length / 2)];\n return pointInRing(deduped, mid.x, mid.y);\n });\n\n if (covers) {\n out.push({ outline: simplifyRing(deduped, FOOTPRINT_TOLERANCE_U), holes: [], topY });\n continue;\n }\n for (const poly of unionRibbons(usable)) out.push({ outline: poly, holes: [], topY });\n }\n return out;\n}\n\n/**\n * The original boolean union, kept as the fallback for blocks the direct trace\n * cannot describe. Correct but expensive, so it now runs for a handful of blocks\n * rather than for every one.\n */\nfunction unionRibbons(ribbons: readonly Ribbon[]): Point[][] {\n const rings: Array<[number, number][][]> = [];\n for (const r of ribbons) {\n const f: [number, number][] = [];\n const b: [number, number][] = [];\n const rf = r.front + FOOTPRINT_MARGIN_U;\n const rb = r.back + FOOTPRINT_MARGIN_U;\n for (let k = 0; k < r.pts.length; k++) {\n const p = r.pts[k], n = r.nrm[k];\n f.push([p.x - n[0] * rf, p.y - n[1] * rf]);\n b.push([p.x + n[0] * rb, p.y + n[1] * rb]);\n }\n const ring = [...f, ...b.reverse()];\n if (ring.length < 3) continue;\n ring.push(ring[0]);\n rings.push([ring]);\n }\n if (!rings.length) return [];\n try {\n const merged = polygonClipping.union(rings[0], ...rings.slice(1));\n const out: Point[][] = [];\n for (const poly of merged) {\n if (!poly.length || poly[0].length < 4) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n const first = pts[0], last = pts[pts.length - 1];\n if (pts.length > 1 && Math.abs(first.x - last.x) < 1e-9 && Math.abs(first.y - last.y) < 1e-9) pts.pop();\n if (pts.length >= 3) out.push(simplifyRing(pts, FOOTPRINT_TOLERANCE_U));\n }\n return out;\n } catch {\n return [];\n }\n}\n\n/** Shoelace area of a ring. */\nfunction ringArea(ring: readonly Point[]): number {\n let a = 0;\n for (let i = 0, n = ring.length; i < n; i++) {\n const p = ring[i], q = ring[(i + 1) % n];\n a += p.x * q.y - q.x * p.y;\n }\n return a / 2;\n}\n\n/** Drop consecutive duplicates, which the traced ring produces at its corners. */\nfunction dedupeAdjacent(ring: readonly Point[]): Point[] {\n const out: Point[] = [];\n for (const p of ring) {\n const last = out[out.length - 1];\n if (last && Math.hypot(p.x - last.x, p.y - last.y) < 1e-9) continue;\n out.push(p);\n }\n while (out.length > 2 && Math.hypot(out[0].x - out[out.length - 1].x, out[0].y - out[out.length - 1].y) < 1e-9) out.pop();\n return out;\n}\n\n/** Even-odd point-in-ring test. */\nfunction pointInRing(ring: readonly Point[], x: number, y: number): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const a = ring[i], b = ring[j];\n if ((a.y > y) !== (b.y > y) && x < ((b.x - a.x) * (y - a.y)) / (b.y - a.y) + a.x) inside = !inside;\n }\n return inside;\n}\n\n/**\n * Cheap containment test for the clip region, so the boolean is only run for the\n * quads that actually straddle a boundary.\n */\nclass ClipTest {\n private rings: Point[][];\n private minX = Infinity; private minY = Infinity;\n private maxX = -Infinity; private maxY = -Infinity;\n\n constructor(rings: Point[][]) {\n this.rings = rings;\n for (const r of rings) {\n for (const p of r) {\n if (p.x < this.minX) this.minX = p.x;\n if (p.y < this.minY) this.minY = p.y;\n if (p.x > this.maxX) this.maxX = p.x;\n if (p.y > this.maxY) this.maxY = p.y;\n }\n }\n }\n\n /** True when every point lies strictly inside ONE ring of the clip region. */\n containsAll(pts: readonly Point[]): boolean {\n for (const p of pts) {\n if (p.x < this.minX || p.x > this.maxX || p.y < this.minY || p.y > this.maxY) return false;\n }\n // A quad spanning two disjoint rings must go through the boolean, so the\n // test demands a SINGLE ring contain all of it.\n for (const ring of this.rings) {\n let all = true;\n for (const p of pts) {\n if (!pointInRing(ring, p.x, p.y)) { all = false; break; }\n }\n if (all) return true;\n }\n return false;\n }\n}\n\n/**\n * Emit a horizontal CONVEX polygon clipped to `clipRing`, triangulated.\n *\n * Treads are horizontal, so every piece shades with the same up normal whatever\n * shape the clip leaves behind — which is what makes clipping cheap here.\n *\n * Convex is all the callers produce: a ribbon segment is a quad and a table\n * patch is an offset convex hull, so the unclipped fast path can fan from one\n * vertex instead of running a triangulator.\n */\nfunction emitClippedPoly(\n builder: MeshBuilder,\n clipRing: [number, number][][],\n clipTest: ClipTest,\n poly: Point[],\n y: number,\n color: RGB,\n): void {\n if (poly.length < 3) return;\n // FAST PATH: a polygon wholly inside the clip contributes nothing to clip, and\n // the overwhelming majority are — only ribbons at a section's edge straddle it.\n // Running a polygon boolean per ribbon SEGMENT regardless cost ~100,000\n // intersections on a 99k-seat venue and dominated scene build (8.5 s of 11 s).\n if (clipTest.containsAll(poly)) {\n const UPF = [0, 1, 0] as const;\n const a = poly[0];\n for (let i = 1; i + 1 < poly.length; i++) {\n const b = poly[i], c = poly[i + 1];\n builder.tri([a.x * M, y, a.y * M], [b.x * M, y, b.y * M], [c.x * M, y, c.y * M], UPF, color);\n }\n return;\n }\n const ring: [number, number][] = poly.map((p) => [p.x, p.y]);\n ring.push(ring[0]);\n let pieces: ReturnType<typeof polygonClipping.intersection>;\n try {\n pieces = polygonClipping.intersection([ring], clipRing);\n } catch {\n return; // a degenerate quad simply contributes nothing\n }\n const UP = [0, 1, 0] as const;\n for (const poly of pieces) {\n if (!poly.length || poly[0].length < 4) continue;\n const outer = poly[0];\n const flat: number[] = [];\n const pts: Array<[number, number]> = [];\n for (let i = 0; i < outer.length - 1; i++) { // drop the repeated closing point\n flat.push(outer[i][0], outer[i][1]);\n pts.push([outer[i][0], outer[i][1]]);\n }\n if (pts.length < 3) continue;\n const tris = earcut(flat, undefined, 2);\n for (let i = 0; i < tris.length; i += 3) {\n const a = pts[tris[i]], b = pts[tris[i + 1]], c = pts[tris[i + 2]];\n builder.tri(\n [a[0] * M, y, a[1] * M],\n [b[0] * M, y, b[1] * M],\n [c[0] * M, y, c[1] * M],\n UP, color,\n );\n }\n }\n}\n\n/**\n * Emit one section's ribbons and risers.\n *\n * `rows` must be ordered front first (nearest the focal), which is the contract\n * `SectionRake.rows` provides.\n */\nexport function emitDeckBands(\n builder: MeshBuilder,\n rows: readonly BandRow[],\n focal: Point,\n landingY: number,\n colors: BandColors,\n clip?: readonly Point[],\n shared?: Neighbourhood[],\n): void {\n if (rows.length < 2) return;\n // Reused from the footprint pass when the caller has one: this is the O(rows^2)\n // step and computing it twice per section doubled it for nothing.\n const nbrs = shared ?? rowNeighbourhoods(rows);\n const UP = [0, 1, 0] as const;\n const clipRing: [number, number][][] | null = clip && clip.length >= 3\n ? [[...clip.map((p) => [p.x, p.y] as [number, number]), [clip[0].x, clip[0].y]]]\n : null;\n const clipTest = clip && clip.length >= 3 ? new ClipTest([[...clip]]) : null;\n // Row treads/risers are useful close-range structure, but their overlapping\n // centimetre-scale surfaces alias into a black radial comb at whole-venue\n // scale. Stamp them separately so the renderer can use the clean block cap in\n // overview, then restore the true stepped surface inside a section.\n const emitRiser = (draw: () => void): void => {\n builder.setMaterial(2);\n draw();\n builder.setMaterial(0);\n };\n const emitTread = (draw: () => void): void => {\n builder.setMaterial(3);\n draw();\n builder.setMaterial(0);\n };\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n\n // A table: draw its own footprint, not a ribbon. See `BandRow.patch`.\n if (row.patch && row.patch.length >= 3) {\n const patch = [...row.patch];\n const belowY = nbrs[i].belowY ?? landingY;\n emitTread(() => {\n if (clipRing && clipTest) emitClippedPoly(builder, clipRing, clipTest, patch, row.y, colors.tread);\n else {\n const a = patch[0];\n for (let k = 1; k + 1 < patch.length; k++) {\n const b = patch[k], c = patch[k + 1];\n builder.tri([a.x * M, row.y, a.y * M], [b.x * M, row.y, b.y * M], [c.x * M, row.y, c.y * M], UP, colors.tread);\n }\n }\n });\n // A skirt all the way round, not just across the front. A patch is a\n // free-standing pedestal rather than a step in a stack, so every side of\n // it is exposed; leaving the sides open showed the void under the deck\n // from any angle but dead ahead.\n if (row.y > belowY + MIN_RISER_M) {\n let cx = 0, cy = 0;\n for (const p of patch) { cx += p.x; cy += p.y; }\n cx /= patch.length; cy /= patch.length;\n emitRiser(() => {\n for (let k = 0; k < patch.length; k++) {\n const p = patch[k], q = patch[(k + 1) % patch.length];\n const dx = q.x - p.x, dy = q.y - p.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-6) continue;\n // Outward = the side of the edge the patch centre is NOT on.\n let nx = dy / len, ny = -dx / len;\n if ((p.x - cx) * nx + (p.y - cy) * ny < 0) { nx = -nx; ny = -ny; }\n const rn: readonly [number, number, number] = [nx, 0, ny];\n const pt: [number, number, number] = [p.x * M, row.y, p.y * M];\n const qt: [number, number, number] = [q.x * M, row.y, q.y * M];\n const pb: [number, number, number] = [p.x * M, belowY, p.y * M];\n const qb: [number, number, number] = [q.x * M, belowY, q.y * M];\n builder.tri(pt, qt, qb, rn, colors.riser);\n builder.tri(pt, qb, pb, rn, colors.riser);\n }\n });\n }\n continue;\n }\n\n const rib = ribbonOf(rows, i, nbrs, focal);\n if (!rib) continue;\n const { pts, nrm, front, back } = rib;\n\n // The level below this ribbon: the spatially nearest row in front of it, or\n // the landing when nothing is in front (this row is its block's first).\n const belowY = nbrs[i].belowY ?? landingY;\n\n for (let k = 0; k + 1 < pts.length; k++) {\n const p = pts[k], q = pts[k + 1];\n const np = nrm[k], nq = nrm[k + 1];\n // Skip degenerate input: a duplicated seat position, or a vertex whose\n // normal collapsed. Emitting these produced needle triangles with aspect\n // ratios to 17,776 — meaningless geometry that flat-shades as dark streaks,\n // the exact defect the ring cleanup removed from the cap path.\n if (Math.hypot(q.x - p.x, q.y - p.y) < 1e-6) continue;\n if ((np[0] === 0 && np[1] === 0) || (nq[0] === 0 && nq[1] === 0)) continue;\n\n const pF: [number, number, number] = [(p.x - np[0] * front) * M, row.y, (p.y - np[1] * front) * M];\n const qF: [number, number, number] = [(q.x - nq[0] * front) * M, row.y, (q.y - nq[1] * front) * M];\n const pB: [number, number, number] = [(p.x + np[0] * back) * M, row.y, (p.y + np[1] * back) * M];\n const qB: [number, number, number] = [(q.x + nq[0] * back) * M, row.y, (q.y + nq[1] * back) * M];\n\n // Tread: horizontal, so one shared up normal is exact.\n emitTread(() => {\n if (clipRing && clipTest) {\n // A ribbon reaches past its own row on all four sides, and near a section\n // boundary that reach crosses into the NEXT section. Where the neighbour\n // sits lower, the overhang covers its seats: 27 concert-hall seats were\n // buried by 0.20 m under the terrace beside them, which is 0.60 m higher.\n // Clipping to the section's own padded outline keeps a deck inside the\n // section that drew it.\n emitClippedPoly(builder, clipRing, clipTest!, [\n { x: p.x - np[0] * front, y: p.y - np[1] * front },\n { x: q.x - nq[0] * front, y: q.y - nq[1] * front },\n { x: q.x + nq[0] * back, y: q.y + nq[1] * back },\n { x: p.x + np[0] * back, y: p.y + np[1] * back },\n ], row.y, colors.tread);\n } else {\n builder.tri(pF, qF, qB, UP, colors.tread);\n builder.tri(pF, qB, pB, UP, colors.tread);\n }\n });\n\n // Riser: the vertical face under the ribbon's front edge. Skipped when it\n // would be inverted or degenerate (a level or descending step).\n if (row.y > belowY + MIN_RISER_M) {\n const pFd: [number, number, number] = [pF[0], belowY, pF[2]];\n const qFd: [number, number, number] = [qF[0], belowY, qF[2]];\n // Faces the focal — the direction the audience looks from.\n const rn: readonly [number, number, number] = [-np[0], 0, -np[1]];\n emitRiser(() => {\n builder.tri(pF, qF, qFd, rn, colors.riser);\n builder.tri(pF, qFd, pFd, rn, colors.riser);\n });\n }\n }\n }\n}\n","/**\n * THE seating surface — one function per section, consumed by everything.\n *\n * Before this module the venue had three independently-computed surfaces (tier\n * cap, row treads, seat dots) held apart by hardcoded offsets that had to agree,\n * and never did across chart shapes. Two measured defects came out of that:\n *\n * - The tier cap was a raw earcut triangulation of the section outline with a\n * *radial, nonlinear* height evaluated only at outline vertices. A 90 m chord\n * across a curved bowl is a flat plane through the seating: 23 % of arena\n * seats and 48 % of amphitheatre seats rendered UNDER their own tier cap\n * (worst overshoot 5.54 m).\n * - The cap took each section's front-edge datum from its OUTLINE vertices and\n * the doc focal, while `layout.ts`'s `assignEyeHeights` takes it from the\n * section's SEATS and each seat's own `focalPoint`. Different datum ⇒ a\n * per-section constant disagreement even where tessellation was exact.\n *\n * `deckAt(x, y)` here is the single definition of \"the height of the walkable\n * seating surface\". It mirrors `assignEyeHeights` exactly — same front-edge\n * datum, same drawn-radial-depth rise — so a seat dot and the deck it stands on\n * can no longer be computed from different models. The cap is tessellated\n * against this same function (see `extrudePrism`'s `maxCapError`), so the drawn\n * triangle mesh tracks it rather than chording across it.\n *\n * Pure: no OGL, no DOM. Unit-tested in workers/api/test/view3dSurface.test.ts.\n */\n\nimport type { ChartObject, ExpandedSeat, Point, SectionObject } from '../../core/types';\nimport { pointInPolygonWithHoles } from '../../core/layout';\nimport { buildSectionRake } from '../../core/rake';\nimport { resolveSection, distanceToPolyline, type ResolvedRow } from '../../core/venueStructure';\nimport { CHART_UNITS_PER_METRE, METRES_PER_CHART_UNIT, SEATED_EYE_HEIGHT_M, sectionGeometry } from '../../core/units';\nimport { outsetRing } from './geometry';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport { MIN_REACH_U, seatClusterPatch } from './deckBands';\n\n/** Top of the thin slab drawn for a section with no authored height/rake. Seats\n * on a flat chart rest on this, so it is part of the surface definition rather\n * than a constant duplicated by the seat builder. */\nexport const FLAT_SLAB_TOP_M = 0.05;\n\n/**\n * Subdivision TARGET for the drawn tier cap, metres — how closely it tries to\n * track `deckAt`. Consumed by `sceneModel` when it extrudes a tier.\n *\n * A target, not a guarantee: refinement is uniform per section (the only way to\n * stay watertight — see `emitCapUniform`) and bounded by a triangle budget, so a\n * section whose surface folds sharply stops refining before it reaches this.\n * {@link SEAT_CLEARANCE_M} is what actually protects the seats.\n */\nexport const CAP_MAX_ERROR_M = 0.05;\n\n/**\n * Clearance between a seat dot's origin and the deck it stands on, metres.\n *\n * The cap's error is ONE-SIDED and that is what makes a single clearance work.\n * `deckAt` is a cone — front-edge height plus a linear function of radial\n * distance — which is convex, so a triangle interpolating it lies at or above it\n * everywhere inside the triangle, never below. The cap can therefore only ever\n * float above the true surface, and only a seat needs protecting from it.\n *\n * The size, however, is MEASURED rather than proven. Refinement aims at\n * {@link CAP_MAX_ERROR_M} but is uniform per section and capped by a triangle\n * budget, and the residual peaks at the surface's fold, where a section's flat\n * front plateau turns into its rake. Measured worst cap error across all four\n * harness charts is 13 cm. 15 cm carries that while staying under the ~22 cm\n * seat-dot radius, so it is invisible. THIS is the number that must exceed the\n * cap's real error — the test suite asserts exactly that relationship.\n *\n * The test suite enforces the real invariant directly — zero buried seats on\n * every harness chart — so a regression in either number fails loudly rather\n * than silently eating the margin. If a chart ever needs a tighter cap, the fix\n * is fold-aware refinement (splitting triangles that straddle `frontU`), not a\n * bigger clearance.\n */\nexport const SEAT_CLEARANCE_M = 0.15;\n\n/**\n * How far outside its authored outline a section still owns seats, chart units.\n *\n * Matches the deck padding in `buildTier` exactly (1.5 seat-dot radii), because\n * it answers the same question: the deck is drawn at that padded ring, so a seat\n * standing on drawn deck must belong to the section that drew it. Any smaller and\n * boundary seats are orphaned onto the floor beneath their own tier.\n */\nexport const SEAT_OWNERSHIP_PAD_U = SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE;\n\n/** One section's resolved seating surface. */\nexport interface SectionSurface {\n readonly sectionId: string;\n /** World-metre height of the seating deck at a chart-unit point. */\n deckAt(x: number, y: number): number;\n /**\n * ANALYTIC unit normal of the deck at a chart-unit point, world axes (y up).\n *\n * The drawn cap is a piecewise-linear approximation of `deckAt`, so its FACE\n * normals are discontinuous across every shared edge — measured at 46 % of cap\n * edges breaking >2°, worst 110°, which flat-shades into the dark streaks\n * across a raked deck. Worse, the near-degenerate triangles earcut emits at a\n * densified boundary (aspect ratios to 8.7e6) have numerically meaningless\n * face normals, so they shade as random dark needles.\n *\n * `deckAt` is a cone and therefore differentiable everywhere except its fold,\n * so the true normal is available in closed form and costs nothing. Shading the\n * cap with it instead of the face normal makes the surface read as smooth\n * regardless of tessellation AND makes a sliver's shading harmless — it now\n * matches its neighbours even when its own geometry is degenerate.\n */\n normalAt(x: number, y: number): [number, number, number];\n /** True when the section carries no rake — the deck is a constant plane. */\n readonly flat: boolean;\n /** Bottom of the section prism (floor base), world metres. */\n readonly bottomY: number;\n /**\n * The section's rows with the world-metre LEVEL each one sits at, front first.\n *\n * This is what the deck is actually built from. A row is a level ribbon and\n * consecutive ribbons are joined by a riser, so the drawn surface passes\n * exactly through every row instead of approximating a field between them —\n * and because a ribbon is horizontal, its cap error against the seats it\n * carries is zero by construction rather than by measurement.\n *\n * Empty for a flat section, and for a section with fewer than two rows (which\n * keeps the old smooth-cone path, since there is no row structure to build on).\n */\n readonly rowLevels: readonly {\n readonly pts: readonly Point[];\n readonly y: number;\n readonly depth: number;\n readonly blockId: number;\n /** Deck footprint for an entry that is a TABLE rather than a row — see\n * `BandRow.patch` in deckBands.ts for why a ring of seats cannot be decked\n * by offsetting a polyline. */\n readonly patch?: readonly Point[];\n }[];\n /** The landing height for the parts of the outline that hold no rows. */\n readonly landingY: number;\n}\n\nexport interface VenueSurfaces {\n /** Surface by section object id (NOT logical id — heights are per object). */\n bySection: Map<string, SectionSurface>;\n /** Owning section object id per seat index, or null when a seat sits outside\n * every section (free-standing rows). Parallel to the input seat array. */\n seatOwner: Array<string | null>;\n /** Deck height in world metres for seat index i (falls back to the seat's own\n * resolved eye height when it owns no section). */\n seatDeckY(i: number): number;\n /**\n * Seat pitch in CHART UNITS for seat index i, or undefined when its section\n * has no resolved rows.\n *\n * The spacing a seat actually has to itself — the smaller of its own row's\n * seat spacing and the gap to the neighbouring row. Callers size a seat's\n * marker from this.\n *\n * It is emphatically NOT the distance to the nearest other seat. That measure\n * shrank 80 amphitheatre dots to as little as 0.089 m, every one of them at a\n * WEDGE BOUNDARY: the last seat of one wedge sits 0.212 m from the first seat\n * of the next across an aisle, far closer than the 0.55 m spacing inside either\n * row. Two seats being near each other across an aisle says nothing about how\n * much room either one has.\n */\n seatPitchU(i: number): number | undefined;\n}\n\ninterface Bbox { minX: number; minY: number; maxX: number; maxY: number }\n\nfunction bboxOf(pts: Point[]): Bbox {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const p of pts) {\n if (p.x < minX) minX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.x > maxX) maxX = p.x;\n if (p.y > maxY) maxY = p.y;\n }\n return { minX, minY, maxX, maxY };\n}\n\n/** Even-odd point-in-ring test, for the closed table patches above. */\nfunction pointInRing(ring: readonly Point[], x: number, y: number): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const a = ring[i], b = ring[j];\n if ((a.y > y) !== (b.y > y) && x < ((b.x - a.x) * (y - a.y)) / (b.y - a.y) + a.x) inside = !inside;\n }\n return inside;\n}\n\n/** Hard ceiling on rake rise so a mis-authored or focal-wrapping section can\n * never produce a runaway spike (defect guard, carried over from buildTier). */\nconst MAX_TIER_RISE_M = 25;\n\nexport interface SurfaceUnit {\n objects: ChartObject[];\n focal: Point;\n baseHeightM: number;\n}\n\n/**\n * Resolve every section's seating surface and each seat's owning section.\n *\n * The front-edge datum is taken from the section's member SEATS when it has any\n * (matching `assignEyeHeights`), and only falls back to the outline's nearest\n * vertex for a section with no seats — where nothing can disagree with it.\n */\nexport function buildVenueSurfaces(units: SurfaceUnit[], seats: ExpandedSeat[]): VenueSurfaces {\n const bySection = new Map<string, SectionSurface>();\n const seatOwner = new Array<string | null>(seats.length).fill(null);\n const seatDeck = new Float64Array(seats.length);\n /** Level of the seat's own row, when its section resolved into rows. */\n const seatRowLevel = new Array<number | undefined>(seats.length).fill(undefined);\n /** Pitch of the seat's own row (chart units), when resolved. */\n const seatPitch = new Array<number | undefined>(seats.length).fill(undefined);\n\n // --- Pass 1: ownership + per-section seat front distance -------------------\n interface Acc {\n section: SectionObject;\n unit: SurfaceUnit;\n /** Nearest drawn focal distance among member seats (chart units). */\n frontU: number;\n hasSeats: boolean;\n /** Member seat positions grouped by row — the input the rake axis is fitted from. */\n rows: Map<string, Point[]>;\n /** Indices of this section's member seats, for the structure resolver. */\n seatIndices: number[];\n }\n const acc = new Map<string, Acc>();\n const boxes: Array<{ id: string; box: Bbox; section: SectionObject; unit: SurfaceUnit; outline: Point[] }> = [];\n /**\n * Object ids that expand into a RING of seats rather than a run of them.\n *\n * `expandTable` stamps every chair with `rowId = table.id`, so the structure\n * resolver — which only ever sees seats — groups a whole table into what it\n * calls a row. Geometrically it is nothing of the kind, and the deck builder\n * has to know the difference. Reading it off the authored objects is exact;\n * inferring \"these seats look like a ring\" from the points would be a second\n * opinion about a fact the chart already states.\n */\n const tableRowIds = new Set<string>();\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'table') tableRowIds.add(o.id);\n if (o.type !== 'section' || !o.outline || o.outline.length < 3) continue;\n // Ownership is tested against the PADDED outline — the same ring the deck\n // is actually drawn with (see `buildTier`'s `outsetRing`). Testing the raw\n // outline instead left seats authored just outside it unowned, so they fell\n // to the floor while the drawn deck covered them: measured as 9 buried\n // cinema seats and a 0.20 m drop on the uber arena. A seat resting on drawn\n // deck belongs to the section that drew it.\n const owned = outsetRing(o.outline, SEAT_OWNERSHIP_PAD_U);\n acc.set(o.id, { section: o, unit, frontU: Infinity, hasSeats: false, rows: new Map(), seatIndices: [] });\n boxes.push({ id: o.id, box: bboxOf(owned), section: o, unit, outline: owned });\n }\n }\n\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n for (const b of boxes) {\n // Bbox prefilter keeps the 14k-seat charts cheap; the polygon test only\n // runs for genuine candidates.\n if (s.x < b.box.minX || s.x > b.box.maxX || s.y < b.box.minY || s.y > b.box.maxY) continue;\n if (!pointInPolygonWithHoles({ x: s.x, y: s.y }, b.outline, b.section.holes)) continue;\n seatOwner[i] = b.id;\n const a = acc.get(b.id)!;\n a.hasSeats = true;\n const f = s.focalPoint ?? b.unit.focal;\n const d = Math.hypot(s.x - f.x, s.y - f.y);\n if (d < a.frontU) a.frontU = d;\n // Group by row, exactly as assignEyeHeights does — the rake axis is fitted\n // from the same grouping in both files, or the two models diverge again.\n a.seatIndices.push(i);\n const rowKey = s.rowId || `__seat-${i}`;\n const arr = a.rows.get(rowKey);\n if (arr) arr.push({ x: s.x, y: s.y }); else a.rows.set(rowKey, [{ x: s.x, y: s.y }]);\n break; // first containing section wins, exactly as assignEyeHeights does\n }\n }\n\n // --- Pass 2: build one surface function per section ------------------------\n for (const [id, a] of acc) {\n const geo = sectionGeometry(a.section, { floorBaseHeightM: a.unit.baseHeightM });\n const bottomY = a.unit.baseHeightM;\n const rakeTan = geo.rake > 0 ? Math.tan((geo.rake * Math.PI) / 180) : 0;\n // An authored override wins over inference (see `SectionObject.surfaceKind`).\n // `rakedRows` cannot manufacture relief a section does not carry, so it only\n // prevents a raked section from being flattened, never the reverse.\n const inferredFlat = geo.rake <= 0.01 && geo.height <= bottomY + 0.001;\n const kind = a.section.surfaceKind;\n const flat = kind === 'flat' ? true : kind === 'rakedRows' ? geo.rake > 0.01 ? false : inferredFlat : inferredFlat;\n\n // Resolve the structure FIRST: when it yields row levels, the rake field is\n // never consulted and building it is pure waste. Profiled at 7 % of scene\n // build on a 50k venue — `sampleRows` doing inverse-distance work for a field\n // nothing would read.\n const structure = a.hasSeats\n ? resolveSection(id, seats, a.seatIndices, a.unit.focal)\n : { sectionId: id, rows: [] as ResolvedRow[], blockCount: 0 };\n const needsRake = structure.rows.length < 2;\n const rake = needsRake\n ? buildSectionRake([...a.rows.values()].map((points) => ({ points })), a.unit.focal)\n : null;\n\n let frontU = Infinity;\n if (rake) {\n if (a.hasSeats) {\n for (const pts of a.rows.values()) {\n for (const p of pts) {\n const d = rake.depthAt(p.x, p.y);\n if (d < frontU) frontU = d;\n }\n }\n }\n if (!a.hasSeats || !Number.isFinite(frontU)) {\n frontU = Infinity;\n for (const p of a.section.outline) {\n const d = rake.depthAt(p.x, p.y);\n if (d < frontU) frontU = d;\n }\n }\n }\n const baseFloor = bottomY + FLAT_SLAB_TOP_M;\n // A level deck sits at the section's AUTHORED height, not on the floor.\n //\n // This used to be `bottomY + FLAT_SLAB_TOP_M` outright, which meant asking\n // for `surfaceKind: 'flat'` silently threw the authored `height` away: a\n // 7.4 m loge box and a 2.6 m choir terrace both rendered with every seat at\n // 0.20 m (slab top plus seat clearance). It made `flat` unusable for the\n // exact things it exists for — boxes, suites and terraces, which are genuinely\n // LEVEL platforms — so both curated venues had to fake a shallow rake instead.\n //\n // The datum is now taken from the same expression the raked path uses\n // (`levelFor`/`levelForBlockDepth` below, at zero rise), so \"flat\" and\n // \"raked at 0°\" agree by construction rather than by coincidence. A section\n // with no authored height still lands on `baseFloor` exactly as before, which\n // is why legacy height-less charts do not move: `inferredFlat` only fires when\n // `geo.height <= bottomY + 0.001`, so this max can only bite when a height was\n // explicitly authored alongside `surfaceKind: 'flat'`.\n const flatTop = Math.max(baseFloor, geo.height);\n\n /** Height for a depth ordinate measured from the venue focal (fallback path). */\n const levelFor = (depthU: number): number => {\n const depthM = Math.max(0, depthU - frontU) * METRES_PER_CHART_UNIT;\n const rise = Math.min(depthM * rakeTan, MAX_TIER_RISE_M);\n return Math.max(baseFloor, geo.height + rise);\n };\n\n /** Height for a depth measured back from a BLOCK's own front row. */\n const levelForBlockDepth = (blockDepthU: number): number => {\n const depthM = Math.max(0, blockDepthU) * METRES_PER_CHART_UNIT;\n const rise = Math.min(depthM * rakeTan, MAX_TIER_RISE_M);\n return Math.max(baseFloor, geo.height + rise);\n };\n\n // One level per row, from the resolved structure. THIS is the deck.\n //\n // Depth is measured inside the row's own BLOCK, so every block of a tier\n // starts at the tier's authored height. Measuring it from the venue focal\n // (which is what `rake.depthAt` does, and what this used to do) raised a\n // bowl's side wedges as though they were further back: sec-gall's six blocks\n // are one tier authored at 6.20 m and started at 6.35 to 10.65 m.\n // A table's deck is its own footprint, grown past its outermost seat by the\n // same clearance a ribbon carries past its end seat (MIN_REACH_U). The\n // radius is taken from where the seats ACTUALLY are, not from the table's\n // authored `radius`: `expandTable` seats a round table at `radius + 16`\n // chart units, and per-chair `dx/dy` overrides move individual seats again,\n // so the authored radius understates the ring it has to cover. On the\n // nightclub that is 50 u of real reach against an authored 34.\n const rowLevels = flat\n ? []\n : structure.rows.map((r) => ({\n pts: r.pts,\n y: levelForBlockDepth(r.blockDepth),\n depth: r.blockDepth,\n blockId: r.blockId,\n ...(tableRowIds.has(r.id)\n ? { patch: seatClusterPatch(r.pts, MIN_REACH_U) }\n : {}),\n }));\n const landingY = rowLevels.length ? rowLevels[0].y : flatTop;\n\n // Bounding circles over the rows, so the nearest-row lookup below prunes\n // instead of walking every polyline in the section. A 50k venue has sections\n // with thousands of rows and this is called per cap sample.\n // Bounded over the entry's DRAWN extent — the patch when it has one, since\n // that is the ground it actually covers and a bound taken from the seat ring\n // alone would prune away the deck at the patch's rim.\n const rowBounds = rowLevels.map((r) => {\n const ext = r.patch && r.patch.length >= 3 ? r.patch : r.pts;\n let cx = 0, cy = 0;\n for (const p of ext) { cx += p.x; cy += p.y; }\n const n = ext.length || 1;\n cx /= n; cy /= n;\n let rad = 0;\n for (const p of ext) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > rad) rad = d;\n }\n return { cx, cy, rad };\n });\n\n const deckAt = flat\n ? (): number => flatTop\n : rowLevels.length >= 2\n ? (x: number, y: number): number => {\n // The deck is the level of the row you are standing on. A step\n // function, deliberately: it is the drawn geometry, not an\n // approximation of it, so a seat and its ribbon cannot disagree.\n let best = Infinity, bestY = landingY;\n for (let i = 0; i < rowLevels.length; i++) {\n const b = rowBounds[i];\n // Admissible lower bound — cannot discard a row that would have won.\n if (Math.hypot(x - b.cx, y - b.cy) - b.rad >= best) continue;\n // Over a table, the deck is the table's patch — and a point inside\n // the patch is ON it, at distance zero. Measuring to the seat ring's\n // polyline instead would hand the middle of a table to whichever\n // ROW happened to be nearest, so the deck height and the drawn patch\n // would disagree by a whole step.\n const patch = rowLevels[i].patch;\n const d = patch && patch.length >= 3\n ? (pointInRing(patch, x, y) ? 0 : distanceToPolyline(patch, x, y))\n : distanceToPolyline(rowLevels[i].pts, x, y);\n if (d < best) { best = d; bestY = rowLevels[i].y; }\n }\n return bestY;\n }\n : (x: number, y: number): number => (rake ? levelFor(rake.depthAt(x, y)) : flatTop);\n\n // The gradient of the same expression. Height rises with radial distance\n // only, so the surface tilts purely along the outward radial direction and\n // the slope is exactly `rakeTan` — except on the flat front plateau\n // (d <= frontU), past the runaway clamp, and where the baseFloor floor wins,\n // all of which are locally horizontal.\n const UP: [number, number, number] = [0, 1, 0];\n // Every ribbon and every landing is HORIZONTAL, so the deck's normal is up\n // everywhere and the analytic-normal machinery has nothing left to correct.\n // The only non-horizontal deck surfaces are the risers, and those carry their\n // own normals from the band builder.\n const normalAt = rowLevels.length >= 2 || flat\n ? (): [number, number, number] => UP\n : (x: number, y: number): [number, number, number] => {\n if (!rake) return UP;\n const d = rake.depthAt(x, y);\n if (d <= frontU) return UP;\n const depthM = (d - frontU) * METRES_PER_CHART_UNIT;\n if (depthM * rakeTan >= MAX_TIER_RISE_M) return UP; // clamped: flat again\n if (geo.height + depthM * rakeTan <= baseFloor) return UP; // floored: flat\n const [gx, gy] = rake.gradientAt(x, y);\n if (gx === 0 && gy === 0) return UP;\n const inv = 1 / Math.hypot(rakeTan, 1);\n return [-gx * rakeTan * inv, inv, -gy * rakeTan * inv];\n };\n\n // A seat takes its own row's level DIRECTLY. Searching for the nearest row\n // would give the same answer almost always and the wrong one occasionally\n // (where two blocks interleave), and it would cost a polyline search per seat\n // on a chart that may hold 50,000 of them. Row membership is already known.\n if (!flat) {\n for (const r of structure.rows) {\n const y = levelForBlockDepth(r.blockDepth);\n for (const si of r.seatIndices) seatRowLevel[si] = y;\n }\n }\n\n // Per-row pitch: the median spacing ALONG the row (immune to the aisle, which\n // falls between rows, not inside one) capped by the gap to the nearest other\n // row, so a dot cannot grow into the row in front either.\n for (const r of structure.rows) {\n const gaps: number[] = [];\n for (let k = 1; k < r.pts.length; k++) {\n const d = Math.hypot(r.pts[k].x - r.pts[k - 1].x, r.pts[k].y - r.pts[k - 1].y);\n if (d > 1e-6) gaps.push(d);\n }\n gaps.sort((x, y) => x - y);\n const along = gaps.length ? gaps[Math.floor(gaps.length / 2)] : Infinity;\n\n // Measured against rows in the SAME BLOCK only. A row in a neighbouring\n // block runs BESIDE this one, not in front of it, and near a block boundary\n // it can pass within a hair of the probe: measured across the catalog that\n // collapsed the pitch to 0.04 mm on the opera house and 0.8 mm on the\n // esports arena, which then drove every dot there to the visibility floor.\n // This is the same mistake as sizing a seat against a neighbour across an\n // aisle, one level up.\n let across = Infinity;\n const probe = r.pts[Math.floor(r.pts.length / 2)];\n if (probe) {\n for (const other of structure.rows) {\n if (other === r || other.blockId !== r.blockId) continue;\n const d = distanceToPolyline(other.pts, probe.x, probe.y);\n if (d > 1e-6 && d < across) across = d;\n }\n }\n // Never below half the in-row spacing. Two rows can genuinely be authored\n // on top of each other (chairs around a table cross their neighbours'\n // arcs), and a seat's own row spacing is still real when that happens.\n const pitch = Math.min(along, Math.max(across, along * 0.5));\n if (Number.isFinite(pitch) && pitch > 0) {\n for (const si of r.seatIndices) seatPitch[si] = pitch;\n }\n }\n\n bySection.set(id, { sectionId: id, deckAt, normalAt, flat, bottomY, rowLevels, landingY });\n }\n\n // --- Pass 3: bake each seat's deck height from its owner's surface ---------\n for (let i = 0; i < seats.length; i++) {\n const ownerId = seatOwner[i];\n const s = seats[i];\n if (ownerId) {\n const own = seatRowLevel[i];\n seatDeck[i] = (own ?? bySection.get(ownerId)!.deckAt(s.x, s.y)) + SEAT_CLEARANCE_M;\n continue;\n }\n // Seat outside every section: fall back to its own resolved eye height, the\n // only surface information it carries.\n const eye = s.eyeHeightM;\n seatDeck[i] = Number.isFinite(eye) ? Math.max(0, (eye as number) - SEATED_EYE_HEIGHT_M) : 0;\n }\n\n return {\n bySection,\n seatOwner,\n seatDeckY: (i: number): number => seatDeck[i],\n seatPitchU: (i: number): number | undefined => seatPitch[i],\n };\n}\n","/**\n * The DOM half of venue labels: one absolutely-positioned element per visible\n * label, repositioned from projected 3D anchors as the camera moves.\n *\n * Kept out of `index.ts` so the render loop stays about rendering, and out of\n * `labels.ts` so the anchor/LOD logic stays pure and testable.\n *\n * ## Accessibility\n *\n * These are real DOM text nodes, which is the point. A screen reader can read the\n * venue's structure — zones, sections, booth names — where a GPU-drawn glyph is\n * invisible to it. The overlay itself is `aria-hidden` only for the decorative\n * pointer-events layer; the labels are a live region-free list of static text,\n * announced in document order.\n *\n * Pointer events pass straight through: a label must never eat a seat tap.\n */\n\nimport {\n DENSE_LABEL_BUDGET, cullOverlapping, focusScore, pickDenseLabels, projectToScreen,\n visibleLabelKinds, type SceneLabel,\n} from './labels';\n\n/**\n * Minimum gap before the farther label is dropped, CSS px — wide and short,\n * matching the shape of a line of text rather than a disc around it.\n */\nconst SEPARATION_X_PX = 88;\nconst SEPARATION_Y_PX = 20;\n\n/** Per-kind styling. Sizes are CSS px at a nominal viewport. */\nconst KIND_STYLE: Record<SceneLabel['kind'], { size: number; weight: string; opacity: number }> = {\n zone: { size: 15, weight: '600', opacity: 0.95 },\n section: { size: 12, weight: '500', opacity: 0.88 },\n ga: { size: 12, weight: '500', opacity: 0.88 },\n booth: { size: 11, weight: '500', opacity: 0.85 },\n annotation: { size: 11, weight: '400', opacity: 0.75 },\n // Row and seat identity are quieter than the structure they sit inside: at\n // this range the venue is already understood and the label is a detail, so it\n // must not compete with the seating it is printed over.\n row: { size: 10.5, weight: '600', opacity: 0.8 },\n seat: { size: 9.5, weight: '500', opacity: 0.72 },\n};\n\n/**\n * Per-kind declutter spacing, CSS px. Seat and row labels are short strings sat\n * on a tight grid, so the wide section-name box would throw away almost all of\n * them — a row of seat numbers needs to survive side by side.\n */\nconst DENSE_KINDS = new Set<SceneLabel['kind']>(['row', 'seat']);\n/**\n * Per-kind, because the two are different shapes of text. A seat number is 1–3\n * characters and wants to survive shoulder to shoulder along a row; a row name\n * is a word (\"ORCH-L\") and needs roughly twice the width before the next one is\n * legible. One shared value crowded the row names into each other.\n */\nconst DENSE_SEPARATION: Record<'row' | 'seat', { x: number; y: number }> = {\n row: { x: 62, y: 16 },\n // Widened from 24: at close range seat labels stopped overlapping at all, so\n // the separation had no work left to do and the budget was carrying the whole\n // load. A wider box means the few labels that ARE kept are spread across the\n // seating instead of clustering into one stack.\n seat: { x: 46, y: 18 },\n};\n\nexport interface LabelOverlayOptions {\n /** `ChartTheme.fontFamily`, when the chart authors one. */\n fontFamily?: string;\n /** Ink colour for labels that carry no authored colour. */\n ink?: string;\n}\n\nexport class LabelOverlay {\n private root: HTMLDivElement;\n private nodes = new Map<string, HTMLDivElement>();\n private labels: SceneLabel[] = [];\n private opts: LabelOverlayOptions;\n private forcedDense = false;\n private selectedSeatLabelIds = new Set<string>();\n\n constructor(container: HTMLElement, opts: LabelOverlayOptions = {}) {\n this.opts = opts;\n this.root = document.createElement('div');\n this.root.setAttribute('data-view3d-labels', '');\n const s = this.root.style;\n s.position = 'absolute';\n s.inset = '0';\n // Never intercept a seat tap — the canvas below owns all pointer input.\n s.pointerEvents = 'none';\n s.overflow = 'hidden';\n if (opts.fontFamily) s.fontFamily = opts.fontFamily;\n container.appendChild(this.root);\n }\n\n /**\n * Hide or show the whole overlay without disturbing which labels exist.\n *\n * The in-scene panorama sphere is GL, so it draws BEHIND every DOM label —\n * row and seat labels floated on top of a 360 photo until this existed. The\n * old DOM panorama never needed it because its opaque div covered them.\n *\n * Deliberately not `setLabels([])`: that destroys the nodes and the declutter\n * state, so closing the panorama would rebuild and re-rank the whole overlay\n * and flash. Visibility is a view concern, not a data one.\n */\n setVisible(visible: boolean): void {\n this.root.style.visibility = visible ? '' : 'hidden';\n }\n\n setLabels(labels: SceneLabel[]): void {\n this.labels = labels;\n for (const [id, node] of this.nodes) {\n if (!labels.some((l) => l.id === id)) { node.remove(); this.nodes.delete(id); }\n }\n }\n\n /** Section/row drill-in makes their dense labels explicit, regardless of the\n * whole-venue distance heuristic used during free orbit. */\n setForcedDense(enabled: boolean): void {\n this.forcedDense = enabled;\n }\n\n /** Keep the authoritative selected identities pinned above nearby dense\n * labels. The seat mesh and its DOM label must never describe different\n * rows merely because collision suppression preferred the neighbour. */\n setSelectedSeatIds(ids: Iterable<string>): void {\n this.selectedSeatLabelIds = new Set(Array.from(ids, (id) => `seat:${id}`));\n }\n\n /**\n * Reposition every label for the current camera.\n *\n * `viewProjection` is column-major, as OGL supplies it.\n */\n update(\n viewProjection: ArrayLike<number>,\n width: number,\n height: number,\n cameraDistance: number,\n venueRadius: number,\n /**\n * Camera world position, for ranking the dense rungs by real distance.\n * Optional so callers that only need structure labels need not supply it;\n * without it the dense rungs fall back to ranking by screen centre alone.\n */\n cameraWorld?: readonly [number, number, number],\n ): void {\n if (!this.labels.length) return;\n const kinds = visibleLabelKinds(cameraDistance, venueRadius);\n if (this.forcedDense) {\n kinds.add('row');\n kinds.add('seat');\n }\n\n const candidates: Array<{\n label: SceneLabel;\n screen: ReturnType<typeof projectToScreen>;\n focus: number;\n priority?: number;\n }> = [];\n for (const label of this.labels) {\n if (!kinds.has(label.kind)) continue;\n const screen = projectToScreen(viewProjection, label.anchor, width, height);\n if (!screen.visible) continue;\n const world = cameraWorld\n ? Math.hypot(\n label.anchor[0] - cameraWorld[0],\n label.anchor[1] - cameraWorld[1],\n label.anchor[2] - cameraWorld[2],\n )\n : 1;\n candidates.push({\n label,\n screen,\n focus: focusScore(screen, world, width, height),\n ...(this.selectedSeatLabelIds.has(label.id) ? { priority: 1 } : {}),\n });\n }\n\n // Nearest-wins declutter, then paint. Everything not kept is hidden rather\n // than removed, so a small camera move does not thrash the DOM. Dense kinds\n // declutter against their OWN spacing and separately from the structure\n // labels, so a section name never suppresses the seat numbers under it.\n const structure = candidates.filter((c) => !DENSE_KINDS.has(c.label.kind));\n const kept = [\n ...cullOverlapping(structure, SEPARATION_X_PX, SEPARATION_Y_PX),\n // The dense rungs are BUDGETED, not merely deduplicated — see\n // DENSE_LABEL_BUDGET for why the overlap test alone gets worse the closer\n // the camera gets.\n ...(['row', 'seat'] as const).flatMap((kind) => pickDenseLabels(\n candidates.filter((c) => c.label.kind === kind),\n DENSE_SEPARATION[kind].x,\n DENSE_SEPARATION[kind].y,\n DENSE_LABEL_BUDGET[kind],\n )),\n ];\n const keptIds = new Set(kept.map((k) => k.label.id));\n\n for (const { label, screen } of kept) {\n const node = this.nodeFor(label);\n const st = node.style;\n st.display = '';\n st.transform = `translate(-50%, -50%) translate(${screen.x.toFixed(1)}px, ${screen.y.toFixed(1)}px)`;\n }\n for (const [id, node] of this.nodes) {\n if (!keptIds.has(id)) node.style.display = 'none';\n }\n }\n\n private nodeFor(label: SceneLabel): HTMLDivElement {\n let node = this.nodes.get(label.id);\n if (!node) {\n node = document.createElement('div');\n const s = node.style;\n s.position = 'absolute';\n s.left = '0';\n s.top = '0';\n s.whiteSpace = 'nowrap';\n s.textShadow = '0 1px 3px rgba(0,0,0,0.85), 0 0 8px rgba(0,0,0,0.55)';\n s.display = 'none';\n this.root.appendChild(node);\n this.nodes.set(label.id, node);\n }\n node.textContent = label.text;\n node.setAttribute('data-label-kind', label.kind);\n const selected = this.selectedSeatLabelIds.has(label.id);\n node.toggleAttribute('data-selected-seat', selected);\n const style = KIND_STYLE[label.kind];\n const s = node.style;\n s.fontSize = `${style.size}px`;\n s.fontWeight = selected ? '800' : style.weight;\n s.opacity = selected ? '1' : String(style.opacity);\n s.color = selected ? '#ecf8ff' : (label.color ?? this.opts.ink ?? '#e8edf5');\n s.background = selected ? 'rgba(10,28,45,.9)' : '';\n s.border = selected ? '1px solid rgba(89,199,255,.92)' : '';\n s.borderRadius = selected ? '999px' : '';\n s.padding = selected ? '3px 7px' : '';\n s.boxShadow = selected ? '0 0 0 2px rgba(41,176,255,.18), 0 5px 14px rgba(0,0,0,.35)' : '';\n s.letterSpacing = label.kind === 'zone' ? '0.08em' : '0.02em';\n if (label.kind === 'zone') s.textTransform = 'uppercase';\n return node;\n }\n\n dispose(): void {\n this.root.remove();\n this.nodes.clear();\n }\n}\n","/**\n * Builds (and rebuilds) all GPU resources from a SceneModel. Kept separate from\n * the model so a context-loss restore can throw the old GpuScene away and call\n * `buildGpuScene(gl, model)` again — the model never changes.\n *\n * Draw calls: background (1) + merged solids (1) + instanced seats (1) = 3, plus\n * ONE more instanced draw for the near-field chairs whenever the camera is close\n * enough that any seat qualifies. That mesh is attached and detached from the\n * scene graph rather than drawn empty, so an overview frame still costs exactly\n * the three calls it always did.\n */\n\nimport { Geometry, Mesh, Program, Transform, type OGLRenderingContext } from 'ogl';\nimport { SEAT_STATES, upholsteryTone, type RGB } from '../palette';\nimport { createBackgroundProgram, createChairProgram, createSeatProgram, createSolidProgram } from './materials';\nimport { CHAIR_MAX_INSTANCES } from '../lod';\nimport { buildChairMesh } from './seatChair';\nimport type { SceneModel } from './sceneModel';\nimport { seatOccupantSeed, type DirtyRun } from './seatInstances';\n\n/**\n * Fill an iColor buffer range from the current iState values (state → colour).\n *\n * Reads the THEME's colours, not the module palette, so an organizer's brand\n * selection colour applies to the 3D seats as it already does in the picker.\n */\nfunction writeSeatColors(\n iColor: Float32Array,\n iState: Float32Array,\n start: number,\n count: number,\n states: readonly RGB[],\n /** Per-instance category colours; an AVAILABLE seat wears its own. */\n iCategory?: Float32Array,\n): void {\n for (let i = start; i < start + count; i++) {\n // State index 0 is 'available'. It is the only state that defers to the\n // category, exactly as 2D does: `paintSeat` fills a free seat with\n // `seatBaseColor(categoryKey)` and only overrides once it is held, sold or\n // selected. Those overrides carry information the category cannot, so they\n // must keep winning.\n const useCategory = iCategory && iState[i] === 0;\n const c = useCategory ? null : (states[iState[i]] ?? states[0]);\n iColor[i * 3] = c ? c[0] : iCategory![i * 3];\n iColor[i * 3 + 1] = c ? c[1] : iCategory![i * 3 + 1];\n iColor[i * 3 + 2] = c ? c[2] : iCategory![i * 3 + 2];\n }\n}\n\n// Two-triangle quad in [-1,1] (billboard base).\nconst SEAT_QUAD = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);\n// Fullscreen triangle.\nconst BG_TRI = new Float32Array([-1, -1, 3, -1, -1, 3]);\n\nexport interface GpuScene {\n /** Main scene (solids + seats), drawn with the camera. */\n main: Transform;\n /** Background scene, drawn first without depth. */\n background: Transform;\n seatProgram: Program;\n solidProgram: Program;\n /** Near-field chair program (shares uFocusFloor / fade-band uniforms). */\n chairProgram: Program;\n /** Shared instanced seat geometry (reused by the pick pass — no buffer copy). */\n seatGeometry: Geometry;\n /** Shared near-field chair/person geometry, including global pick ids. */\n chairGeometry: Geometry;\n /** Merged solid geometry (reused as the pick occluder). */\n solidGeometry: Geometry;\n drawCalls: number;\n /** Upload only the changed instance-state ranges (never the whole buffer). */\n uploadSeatStateRuns(runs: DirtyRun[]): void;\n /**\n * Hand the chair mesh the seat indices the near-field gather selected.\n *\n * `indices[0…count)` are instance indices into the seat cloud — the SAME\n * indices the dots and the pick pass use, which is what guarantees a chair and\n * its dot are always the same seat. Passing count 0 detaches the mesh.\n */\n setNearSeats(indices: Int32Array, count: number): void;\n /** How many chairs are currently instanced (0 = the near field is off). */\n nearSeatCount(): number;\n dispose(): void;\n}\n\nexport function buildGpuScene(gl: OGLRenderingContext, model: SceneModel): GpuScene {\n const main = new Transform();\n const background = new Transform();\n\n // --- Background ---\n const bgGeo = new Geometry(gl, { position: { size: 2, data: BG_TRI } });\n // The chart's own background, not the library's — see theme.ts.\n const bgProg = createBackgroundProgram(gl, model.theme.background.top as number[], model.theme.background.bottom as number[]);\n const bgMesh = new Mesh(gl, { geometry: bgGeo, program: bgProg });\n bgMesh.frustumCulled = false;\n bgMesh.setParent(background);\n\n // --- Solids (floor + tiers + stage + décor + GA, merged) ---\n const solidGeo = new Geometry(gl, {\n position: { size: 3, data: model.solids.position },\n normal: { size: 3, data: model.solids.normal },\n color: { size: 3, data: model.solids.color },\n floorIndex: { size: 1, data: model.solids.floor },\n materialIndex: { size: 1, data: model.solids.material },\n });\n const solidProg = createSolidProgram(gl);\n const solidMesh = new Mesh(gl, { geometry: solidGeo, program: solidProg });\n solidMesh.frustumCulled = false;\n solidMesh.setParent(main);\n\n // --- Seats (one instanced billboard mesh) ---\n // Per-instance colour resolved CPU-side from iState (no dynamically-indexed\n // array uniform — OGL only binds an array uniform whose value is a plain\n // Array, and a dynamic LUT index is best avoided anyway).\n const seatProg = createSeatProgram(gl);\n const iColor = new Float32Array(model.seats.count * 3);\n const stateColors: RGB[] = SEAT_STATES.map((st) => model.theme.seatStates[st]);\n writeSeatColors(iColor, model.seats.iState, 0, model.seats.count, stateColors, model.seats.iCategory);\n const seatGeo = new Geometry(gl, {\n position: { size: 2, data: SEAT_QUAD },\n iOffset: { size: 3, data: model.seats.iPosition, instanced: 1 },\n iColor: { size: 3, data: iColor, instanced: 1 },\n // Per-seat world-radius ceiling: what stops distant rows merging into one\n // mass when the shader grows a dot to hold its minimum pixel size.\n iMaxRadius: { size: 1, data: model.seats.iMaxRadius, instanced: 1 },\n iPhysicalSeat: { size: 1, data: model.seats.iPhysicalSeat, instanced: 1 },\n // Accommodation ring colour; (0,0,0) means the seat carries no access type.\n iRing: { size: 3, data: model.seats.iRing, instanced: 1 },\n iFloor: { size: 1, data: model.seats.iFloor, instanced: 1 },\n });\n const seatMesh = new Mesh(gl, { geometry: seatGeo, program: seatProg });\n seatMesh.frustumCulled = false;\n if (model.seats.count > 0) seatMesh.setParent(main);\n\n const colorAttr = seatGeo.attributes.iColor;\n\n // --- Near-field chairs (one extra instanced draw, only when in range) ------\n // Fixed-capacity instance buffers: the near set is bounded by\n // CHAIR_MAX_INSTANCES whatever the venue holds, so these are allocated once at\n // ~200 KB total and only ever refilled — never grown, never reallocated per\n // frame. `instancedCount` is what varies.\n const chairBase = buildChairMesh();\n const chairProg = createChairProgram(gl);\n const CAP = CHAIR_MAX_INSTANCES;\n const cOffset = new Float32Array(CAP * 3);\n const cColor = new Float32Array(CAP * 3);\n const cRadius = new Float32Array(CAP);\n const cYaw = new Float32Array(CAP);\n const cRing = new Float32Array(CAP * 3);\n const cFloor = new Float32Array(CAP);\n const cPhysicalSeat = new Float32Array(CAP);\n const cPickIndex = new Float32Array(CAP);\n // Occupancy + a stable per-person variation, in one attribute.\n //\n // Negative means the seat is EMPTY and the occupant geometry collapses. A\n // non-negative value is a per-seat hash in [0,1) that varies build and skin\n // tone, so a full row is not a rank of identical mannequins — the same trick\n // the generated panorama used on its silhouettes.\n const cSeed = new Float32Array(CAP);\n const chairGeo = new Geometry(gl, {\n position: { size: 3, data: chairBase.position },\n normal: { size: 3, data: chairBase.normal },\n part: { size: 1, data: chairBase.part },\n index: { data: chairBase.index },\n iOffset: { size: 3, data: cOffset, instanced: 1 },\n iColor: { size: 3, data: cColor, instanced: 1 },\n iRadius: { size: 1, data: cRadius, instanced: 1 },\n iYaw: { size: 1, data: cYaw, instanced: 1 },\n iRing: { size: 3, data: cRing, instanced: 1 },\n iFloor: { size: 1, data: cFloor, instanced: 1 },\n iPhysicalSeat: { size: 1, data: cPhysicalSeat, instanced: 1 },\n iSeed: { size: 1, data: cSeed, instanced: 1 },\n iPickIndex: { size: 1, data: cPickIndex, instanced: 1 },\n });\n const chairMesh = new Mesh(gl, { geometry: chairGeo, program: chairProg });\n chairMesh.frustumCulled = false;\n let nearCount = 0;\n /** The gathered set, kept so a state change can recolour it without a re-gather. */\n let nearIndices: Int32Array = new Int32Array(0);\n\n const writeChairColors = (): void => {\n const src = model.seats;\n for (let k = 0; k < nearCount; k++) {\n const i = nearIndices[k];\n cSeed[k] = seatOccupantSeed(src.iState[i], i);\n // An available chair wears its category as UPHOLSTERY, not as the map's\n // full-strength tier colour. Only the category case is muted: held, sold\n // and selected are saying something the buyer needs to see from where they\n // are sitting, and quietening those would lose real information.\n const useCategory = src.iCategory && src.iState[i] === 0;\n const c = useCategory ? null : (stateColors[src.iState[i]] ?? stateColors[0]);\n if (c) {\n cColor[k * 3] = c[0];\n cColor[k * 3 + 1] = c[1];\n cColor[k * 3 + 2] = c[2];\n } else {\n const u = upholsteryTone([src.iCategory[i * 3], src.iCategory[i * 3 + 1], src.iCategory[i * 3 + 2]]);\n cColor[k * 3] = u[0];\n cColor[k * 3 + 1] = u[1];\n cColor[k * 3 + 2] = u[2];\n }\n }\n chairGeo.attributes.iColor.needsUpdate = true;\n // Occupancy is live inventory too. A held seat that becomes available must\n // lose both its displayed person and that person's exact pick silhouette.\n chairGeo.attributes.iSeed.needsUpdate = true;\n };\n\n const scene: GpuScene = {\n main,\n background,\n seatProgram: seatProg,\n solidProgram: solidProg,\n chairProgram: chairProg,\n seatGeometry: seatGeo,\n chairGeometry: chairGeo,\n solidGeometry: solidGeo,\n drawCalls: 3,\n setNearSeats(indices: Int32Array, count: number): void {\n const n = Math.min(count, CAP);\n nearIndices = indices;\n nearCount = n;\n if (n === 0) {\n // Detached rather than drawn with instancedCount 0: an overview frame\n // must cost the same three draw calls it did before the near field\n // existed, and a zero-instance draw is still a state change + a call.\n if (chairMesh.parent) chairMesh.setParent(null);\n chairGeo.instancedCount = 0;\n scene.drawCalls = 3;\n return;\n }\n const src = model.seats;\n for (let k = 0; k < n; k++) {\n const i = indices[k];\n cOffset[k * 3] = src.iPosition[i * 3];\n cOffset[k * 3 + 1] = src.iPosition[i * 3 + 1];\n cOffset[k * 3 + 2] = src.iPosition[i * 3 + 2];\n // The chair's OWN pitch rule, not the dot's ceiling — see iChairWidth.\n cRadius[k] = src.iChairWidth[i];\n cYaw[k] = src.iYaw[i];\n cRing[k * 3] = src.iRing[i * 3];\n cRing[k * 3 + 1] = src.iRing[i * 3 + 1];\n cRing[k * 3 + 2] = src.iRing[i * 3 + 2];\n cFloor[k] = src.iFloor[i];\n cPhysicalSeat[k] = src.iPhysicalSeat[i];\n cPickIndex[k] = i + 1;\n }\n writeChairColors();\n chairGeo.attributes.iOffset.needsUpdate = true;\n chairGeo.attributes.iRadius.needsUpdate = true;\n chairGeo.attributes.iYaw.needsUpdate = true;\n chairGeo.attributes.iRing.needsUpdate = true;\n chairGeo.attributes.iFloor.needsUpdate = true;\n chairGeo.attributes.iPhysicalSeat.needsUpdate = true;\n chairGeo.attributes.iSeed.needsUpdate = true;\n chairGeo.attributes.iPickIndex.needsUpdate = true;\n chairGeo.instancedCount = n;\n if (!chairMesh.parent) chairMesh.setParent(main);\n scene.drawCalls = 4;\n },\n nearSeatCount(): number {\n return nearCount;\n },\n uploadSeatStateRuns(runs: DirtyRun[]): void {\n if (!runs.length) return;\n // A selection or availability change has to reach the chairs too, or a\n // seat picked at close range stays available-green under the buyer. The\n // gathered set is small and bounded, so it is simply rewritten rather than\n // diffed against the runs.\n if (nearCount) writeChairColors();\n // Refresh only the changed instance colours from the (already-mutated)\n // iState, then upload just those contiguous ranges — never the whole buffer.\n // iCategory must be passed HERE too, not just on the initial fill: a seat\n // whose hold expires goes back to 'available', and without it that seat\n // would repaint flat green while every untouched neighbour kept its\n // category colour.\n for (const run of runs) {\n writeSeatColors(iColor, model.seats.iState, run.start, run.length, stateColors, model.seats.iCategory);\n }\n const buffer = colorAttr.buffer;\n if (!buffer) {\n // Not uploaded yet (no draw has happened) — full upload on next draw.\n colorAttr.needsUpdate = true;\n return;\n }\n // Direct bufferSubData: OGL's render-state boundBuffer cache is not touched\n // here, which is safe because OGL rebinds attribute buffers per draw via the\n // geometry's VAO; if a future dynamic attribute relies on the cache, rebind\n // through OGL instead. 3 floats per instance (vec3 iColor).\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n for (const run of runs) {\n const sub = iColor.subarray(run.start * 3, (run.start + run.length) * 3);\n gl.bufferSubData(gl.ARRAY_BUFFER, run.start * 3 * Float32Array.BYTES_PER_ELEMENT, sub);\n }\n },\n dispose(): void {\n // OGL geometries/programs delete their GL resources on remove().\n bgGeo.remove();\n bgProg.remove();\n solidGeo.remove();\n solidProg.remove();\n seatGeo.remove();\n seatProg.remove();\n chairGeo.remove();\n chairProg.remove();\n },\n };\n return scene;\n}\n","/**\n * Inline GLSL (WebGL2 / GLSL ES 3.00) for the three scene programs. Zero\n * textures, zero shadow maps, zero post: a procedural matcap-style hemisphere +\n * warm key + fresnel rim on solids, a soft top-lit round dot for seats, and a\n * vertical-gradient + vignette background. OGL injects the built-in matrix\n * uniforms (modelViewMatrix / projectionMatrix / normalMatrix) by name.\n */\n\nimport { Program } from 'ogl';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport {\n CHAIR_FULL_M, CHAIR_NONE_M, OCCUPANT_FULL_M, OCCUPANT_NONE_M,\n OCCUPANT_NEAR_FULL_M, OCCUPANT_NEAR_NONE_M, SEAT_MIN_PIXELS_NEAR,\n} from '../lod';\nimport { BACK_BASE_M, BACK_RAKE_SLOPE } from './seatChair';\nimport type { OGLRenderingContext } from 'ogl';\n\n/**\n * The near-field weight, shared verbatim by the chair and the dot so the two\n * always sum to one at every depth and no seat can be drawn twice or not at all.\n * Mirrors `chairWeight()` in lod.ts.\n */\nconst CHAIR_WEIGHT_GLSL = /* glsl */ `\nfloat chairWeight(float depth) {\n return 1.0 - smoothstep(uChairFull, uChairNone, depth);\n}`;\n\n/** Mirrors `occupantWeight()` in lod.ts without adding per-program uniforms. */\nconst OCCUPANT_WEIGHT_GLSL = /* glsl */ `\nfloat occupantWeight(float depth) {\n float nearWeight = smoothstep(${OCCUPANT_NEAR_NONE_M.toFixed(1)}, ${OCCUPANT_NEAR_FULL_M.toFixed(1)}, depth);\n float farWeight = 1.0 - smoothstep(${OCCUPANT_FULL_M.toFixed(1)}, ${OCCUPANT_NONE_M.toFixed(1)}, depth);\n return nearWeight * farWeight;\n}`;\n\nconst SOLID_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nin vec3 normal;\nin vec3 color;\nin float floorIndex;\nin float materialIndex;\nuniform mat4 modelMatrix;\nuniform float uFocusFloor; // -1 = show every floor\nuniform float uStructureDetail; // 0 = venue overview, 1 = section/row/seat\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform mat3 normalMatrix;\nout vec3 vColor;\nout vec3 vNormalWorld;\nout vec3 vNormalView;\nout vec3 vPosView;\nout vec3 vPosWorld;\nout float vDim;\nout float vMaterial;\nvoid main() {\n // Per-floor isolation without splitting the merged mesh into a draw call per\n // floor: a floor that is not the focused one is dimmed, not hidden, so the\n // buyer keeps the whole venue as context while looking at one level.\n vDim = (uFocusFloor < -0.5 || abs(floorIndex - uFocusFloor) < 0.5) ? 0.0 : 1.0;\n vMaterial = materialIndex;\n vec4 world = modelMatrix * vec4(position, 1.0);\n vec4 mv = modelViewMatrix * vec4(position, 1.0);\n vPosView = mv.xyz;\n vPosWorld = world.xyz;\n vNormalView = normalize(normalMatrix * normal);\n // World normal drives the key + hemisphere so the lighting stays welded to the\n // venue as the camera orbits (the scene has no non-uniform scale, so mat3 of\n // the model matrix is the correct normal transform).\n vNormalWorld = normalize(mat3(modelMatrix) * normal);\n vColor = color;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst SOLID_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 vColor;\nin vec3 vNormalWorld;\nin vec3 vNormalView;\nin vec3 vPosView;\nin vec3 vPosWorld;\nin float vDim;\nin float vMaterial;\nuniform vec3 uKeyDir; // WORLD space, unit, points at the light\nuniform float uStructureDetail;\nout vec4 fragColor;\nvoid main() {\n // Row treads/risers are honest and useful when the buyer is inside a stand.\n // At whole-venue scale their overlapping centimetre-scale surfaces quantise\n // and alias into camera-dependent radial combs. The clean structural block\n // cap remains, so overview keeps every stand without pretending to show row\n // detail below a useful pixel size.\n float rowDetail = step(1.5, vMaterial);\n if (rowDetail > 0.5 && uStructureDetail < 0.5) discard;\n vec3 N = normalize(vNormalWorld);\n vec3 V = normalize(-vPosView);\n float hemi = 0.5 + 0.5 * N.y; // sky/ground gradient about WORLD up\n float key = max(dot(N, uKeyDir), 0.0); // fixed key — does not orbit with you\n // Low opposite fill so faces turned away from the key keep their form instead\n // of crushing to a single flat value.\n vec3 fillDir = normalize(vec3(-uKeyDir.x, 0.25, -uKeyDir.z));\n float fill = max(dot(N, fillDir), 0.0);\n vec3 base = vColor * (0.52 + 0.34 * hemi) + vColor * key * 0.34 + vColor * fill * 0.10;\n float fres = pow(1.0 - max(dot(normalize(vNormalView), V), 0.0), 3.0);\n base += vec3(0.26, 0.31, 0.38) * fres * 0.35; // cool rim, restrained (view-dependent by design)\n // Bright, saturated authored screen faces should read as powered displays,\n // not painted slabs. Vertex colour is deliberately the material signal here:\n // muted tiers and structure never cross these thresholds. The broad waves\n // supply a restrained procedural LED treatment without textures, downloads,\n // solid fake-light geometry or post-processing.\n float maxChannel = max(max(vColor.r, vColor.g), vColor.b);\n float minChannel = min(min(vColor.r, vColor.g), vColor.b);\n float poweredDisplay = (1.0 - step(0.5, abs(vMaterial - 1.0)))\n * smoothstep(0.58, 0.72, maxChannel)\n * smoothstep(0.28, 0.48, maxChannel - minChannel);\n float verticalFace = 1.0 - smoothstep(0.20, 0.62, abs(N.y));\n float wave = 0.5 + 0.5 * sin(vPosWorld.x * 0.44 + vPosWorld.y * 0.28);\n float localX = vPosWorld.x;\n float localY = vPosWorld.y;\n float beamWest = exp(-pow((localX + 2.6 - localY * 0.22) * 0.44, 2.0));\n float beamEast = exp(-pow((localX - 2.6 + localY * 0.22) * 0.44, 2.0));\n float band = smoothstep(0.02, 0.16, 0.18 - abs(localY - 3.9));\n float focalHalo = exp(-pow(localX * 0.26, 2.0) - pow((localY - 3.9) * 0.38, 2.0));\n vec3 display = vColor * (0.74 + wave * 0.26)\n + vec3(0.10, 0.30, 0.52) * beamWest\n + vec3(0.34, 0.13, 0.42) * beamEast\n + vec3(0.62, 0.46, 0.20) * band * 0.28\n + vec3(0.20, 0.10, 0.34) * focalHalo;\n base = mix(base, display, poweredDisplay * verticalFace * 0.92);\n // Unfocused floors fall back toward the background rather than vanishing.\n base = mix(base, base * 0.45, vDim);\n fragColor = vec4(base, 1.0);\n}`;\n\nconst SEAT_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 position; // quad corner in [-1,1]\nin vec3 iOffset; // per-instance world position\nin vec3 iColor; // per-instance state colour (resolved CPU-side)\nin float iMaxRadius; // per-instance world-radius ceiling (seat pitch derived)\nin float iPhysicalSeat;// 1 = chair; 0 = empty wheelchair bay\nin vec3 iRing; // accommodation ring colour; (0,0,0) = not accessible\nin float iFloor; // owning floor index\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uSeatRadius;\nuniform float uSeatScale;\nuniform float uMinPixels;\nuniform float uPixelToWorld; // (2*tan(fovY/2)) / viewportHeightPx\nuniform float uFocusFloor; // -1 = show every floor\nuniform float uChairFull; // view depth at which the chair mesh is full size\nuniform float uChairNone; // ...and at which it has scaled away entirely\nout vec2 vUv;\nout vec3 vColor;\nout float vBudget; // 1 = dot holds its minimum pixel size, <1 = it cannot\nout vec3 vRing;\nout float vDim;\nout float vDotWeight; // 1 = the dot IS this seat, 0 = the chair has taken over\nout float vPhysicalSeat;\nout float vPixelRadius;\n${CHAIR_WEIGHT_GLSL}\nvoid main() {\n vec4 mv = modelViewMatrix * vec4(iOffset, 1.0);\n float depth = max(-mv.z, 0.001);\n // Hand the seat over to the chair mesh as it comes into range. Derived from\n // this instance's OWN depth rather than from a global uniform, so a row two\n // metres away and the far side of the bowl resolve differently in the same\n // frame — which is the entire point of a ladder over a switch.\n vDotWeight = 1.0 - chairWeight(depth);\n float minR = uMinPixels * depth * uPixelToWorld; // screen-space floor\n // Grow to hold the pixel floor, but never past this seat's own pitch ceiling:\n // unbounded growth is what merges neighbouring rows into one mass at range.\n float r = min(max(uSeatRadius * uSeatScale, minR), iMaxRadius);\n vPixelRadius = r / max(depth * uPixelToWorld, 0.000001);\n // How much of the requested pixel floor the dot could actually afford. Below 1\n // it is losing legibility to distance, and the fragment stage dissolves it\n // toward the tier top rather than letting a sub-pixel dot alias and shimmer.\n vBudget = minR > 0.0 ? clamp(r / minR, 0.0, 1.0) : 1.0;\n mv.xy += position * r; // camera-facing billboard\n // Seat the dot ON the deck instead of centring it in the deck. iOffset is the\n // exact surface point, so half the billboard would otherwise sit below the cap\n // and be clipped by it — and because uMinPixels grows r with distance, no\n // constant world-space lift can prevent that at every range. Offsetting by r\n // along the screen projection of WORLD up is self-correcting: it is full at a\n // grazing view (where slicing happens) and vanishes looking straight down\n // (where the dot must stay centred on its seat).\n mv.xy += normalize(vec3(modelViewMatrix * vec4(0.0, 1.0, 0.0, 0.0))).xy * r;\n vUv = position;\n vColor = iColor;\n vRing = iRing;\n vPhysicalSeat = iPhysicalSeat;\n vDim = (uFocusFloor < -0.5 || abs(iFloor - uFocusFloor) < 0.5) ? 0.0 : 1.0;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst SEAT_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nin vec3 vColor;\nin float vBudget;\nin vec3 vRing;\nin float vDim;\nin float vDotWeight;\nin float vPhysicalSeat;\nin float vPixelRadius;\nuniform float uSeatFade; // fade toward tier colour with distance (LOD)\nuniform float uSeatDetail; // 0 for venue/area overview; 1 in section/row/seat\nuniform vec3 uFadeColor;\nout vec4 fragColor;\nvoid main() {\n if (uSeatDetail < 0.5) discard;\n // Empty wheelchair provision is a square bay, never a round chair marker.\n float d = mix(max(abs(vUv.x), abs(vUv.y)), length(vUv), vPhysicalSeat);\n if (d > 1.0) discard;\n float alpha = smoothstep(1.0, 0.72, d);\n float shade = 0.80 + 0.28 * (0.5 - vUv.y * 0.5); // subtle top-lit\n vec3 c = vColor * shade;\n c = mix(c, uFadeColor, uSeatFade);\n // Accommodation ring — the 3D echo of the coloured ring 2D draws around every\n // accessible seat. Painted INSIDE the dot's own radius rather than outside it,\n // so an accessible seat still respects the row-pitch ceiling and cannot grow\n // into its neighbour just for carrying a ring.\n float ringMask = step(0.001, dot(vRing, vRing));\n float ring = smoothstep(0.58, 0.70, d) * (1.0 - smoothstep(0.90, 1.0, d));\n c = mix(c, vRing, ring * ringMask * 0.95);\n // A dot that can no longer afford its pixel floor dissolves instead of\n // aliasing; the tier cap underneath already carries the section's category\n // tint, so the block reads as coloured seating rather than empty concrete.\n alpha *= smoothstep(0.35, 1.0, vBudget);\n // Do not rasterise inventory states until a dot is large enough to read as a\n // seat rather than a sample in a dense screen-space pattern. This is based on\n // actual projected pixels, so it remains correct for unusually large or small\n // venues where a radius-normalised camera distance is misleading.\n alpha *= smoothstep(2.4, 3.2, vPixelRadius);\n // Inventory states are a section-level decision aid. At whole-venue/area\n // scale the category-tinted structural surface is the honest, stable signal;\n // thousands of enlarged status dots only form a moiré pattern. The journey\n // state enables them when the buyer enters a section, row or exact seat.\n // Whole-venue status detail is below a useful pixel size. Drawing every dot\n // there turns concentric rows into camera-dependent moiré streaks, especially\n // while zooming. uSeatFade already measures this exact LOD transition: let\n // the category-tinted tier carry the overview, then reveal individual live\n // availability as the buyer approaches a section.\n // Seats on an unfocused floor recede with their structure.\n c = mix(c, uFadeColor, vDim * 0.75);\n alpha *= mix(1.0, 0.30, vDim);\n // Yield to the chair. The chair grows out of this exact point, so through the\n // band the dot is always at least as big as the chair inside it and the seat\n // never thins out to nothing in between.\n alpha *= vDotWeight;\n if (alpha <= 0.0) discard;\n fragColor = vec4(c, alpha);\n}`;\n\n// --- Near-field seat chairs ------------------------------------------------\n// One instanced draw over the 72-vertex base mesh in ./seatChair.ts, carrying\n// only the seats the CPU gathered as near (see ./nearField.ts). Lit by the same\n// rig as the solids so a chair reads as part of the room and not as a decal.\nconst CHAIR_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position; // local: x/z in units of the seat radius, y in METRES\nin vec3 normal;\nin float part; // 0 = pedestal, 1 = pad, 2 = back, 3 = body, 4 = head\nin vec3 iOffset; // per-instance world deck point (identical to the dot's)\nin vec3 iColor; // per-instance state colour\nin float iRadius; // per-instance horizontal half-width, world metres\nin float iYaw; // per-instance facing, radians (local +Z -> facing dir)\nin vec3 iRing; // accommodation ring colour; (0,0,0) = not accessible\nin float iFloor;\nin float iSeed; // <0 = seat is empty; else per-person hash in [0,1)\nin float iPhysicalSeat;// 1 = chair; 0 = empty wheelchair bay\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uChairFull;\nuniform float uChairNone;\nuniform float uFocusFloor;\nuniform float uBackRake; // metres of z per metre of rise, above uBackBase\nuniform float uBackBase; // local height at which the back starts\nout vec3 vColor;\nout vec3 vNormalWorld;\nout vec3 vNormalView;\nout vec3 vPosView;\nout float vPart;\nout float vHeight; // local height in metres, for the vertical occlusion ramp\nout vec3 vRing;\nout float vDim;\nout float vOccupant; // 1 = this vertex belongs to a person, not to the chair\nout vec3 vOccupantTint;\n${CHAIR_WEIGHT_GLSL}\n${OCCUPANT_WEIGHT_GLSL}\nvoid main() {\n vec4 anchor = modelViewMatrix * vec4(iOffset, 1.0);\n float viewDepth = -anchor.z;\n float depth = max(viewDepth, 0.001);\n // A stale near-field gather can contain seats now behind the camera after a\n // row→overview move. Treating negative depth as 0.001 made those chairs full\n // size across the near plane, producing the long black zoom streaks.\n float w = viewDepth > 0.05 ? chairWeight(depth) : 0.0;\n // 1. Local units -> world metres. Only x/z scale: narrow rows get narrow\n // chairs, but nobody gets a short one (people are the same height at every\n // seat pitch).\n vec3 p = position;\n // The occupant. Parts 3 and 4 are a person; everything below is furniture.\n //\n // One instanced mesh draws both an empty seat and a taken one, because the\n // alternative — a second geometry and a second draw call gathered per frame —\n // would double the near-field cost to show what is already known per instance.\n // An unoccupied seat collapses its person to a point at the seat, which the\n // rasteriser discards, exactly as the chair itself collapses at w=0.\n float occupant = step(2.5, part);\n float taken = step(0.0, iSeed);\n vOccupant = occupant;\n if (iPhysicalSeat < 0.5) {\n p = vec3(0.0); // sellable space, but no physical chair/person\n } else if (occupant > 0.5) {\n if (taken < 0.5) {\n p = vec3(0.0); // empty seat: no person\n } else {\n // Vary the build so a sold-out row is people rather than a rank of\n // identical mannequins: +/-6% height and +/-8% width off the hash.\n p.y *= 0.94 + 0.12 * iSeed;\n p.xz *= 0.92 + 0.16 * fract(iSeed * 7.13);\n // People only appear once they are large enough to read as people. At\n // section/overview distance their tiny dark geometry aliases into radial\n // streaks; seat dots already communicate held/booked state there.\n p *= occupantWeight(depth);\n }\n }\n p.xz *= iRadius;\n // 2. Lean the back. Done here rather than in the base mesh so the lean is a\n // real angle in METRES — baked into the mesh it would scale with the seat's\n // width and the same chair would lean 20 degrees on a wide stadium row and\n // 6 on a tight theatre one.\n // Only the BACK panel rakes. The old test (part > 1.5) meant \"the back\", and\n // now also catches the occupant — leaning a person by the panel's rule would\n // translate their head backwards by a rake measured from the panel's base.\n float rake = (part > 1.5 && part < 2.5) ? max(p.y - uBackBase, 0.0) * uBackRake : 0.0;\n p.z -= rake;\n // 3. Scale-in. At w=0 the chair is a point at the seat, under a dot at full\n // opacity — which is what makes the handover invisible. sqrt front-loads\n // the growth so the chair is already near full size while the dot is still\n // half there; see chairScale() in lod.ts.\n p *= sqrt(w);\n float c = cos(iYaw), s = sin(iYaw);\n vec3 rp = vec3(p.x * c + p.z * s, p.y, -p.x * s + p.z * c);\n // Normals under the same two transforms, in reverse and inverted-transposed.\n // The xz scale is non-uniform, so an axis-aligned normal does NOT survive it\n // unchanged; and the rake is a shear, whose normal transform adds a y term.\n // Skipping either lights the raked back as though it were still vertical.\n vec3 n = vec3(normal.x / iRadius, normal.y, normal.z / iRadius);\n if (part > 1.5 && part < 2.5) n.y += uBackRake * n.z;\n n = normalize(n);\n vec3 rn = vec3(n.x * c + n.z * s, n.y, -n.x * s + n.z * c);\n vec4 mv = modelViewMatrix * vec4(iOffset + rp, 1.0);\n vPosView = mv.xyz;\n vNormalWorld = rn;\n vNormalView = normalize(mat3(modelViewMatrix) * rn);\n vColor = iColor;\n // Occupant colour, resolved here so the fragment stage needs no extra\n // varyings beyond this one. A person is NOT painted in the seat's state\n // colour: a sold seat is red, and a hall of red people reads as a warning,\n // not an audience. Hair/clothing for the body, a warm tone for the head,\n // both varied by the same per-person hash.\n float t = fract(iSeed * 3.71);\n vec3 clothes = mix(vec3(0.18, 0.22, 0.30), vec3(0.42, 0.29, 0.34), t);\n vec3 skin = mix(vec3(0.52, 0.38, 0.29), vec3(0.86, 0.70, 0.58), fract(iSeed * 11.3));\n vOccupantTint = (part > 3.5) ? skin : clothes;\n vPart = part;\n vHeight = position.y;\n vRing = iRing;\n vDim = (uFocusFloor < -0.5 || abs(iFloor - uFocusFloor) < 0.5) ? 0.0 : 1.0;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst CHAIR_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 vColor;\nin vec3 vNormalWorld;\nin vec3 vNormalView;\nin vec3 vPosView;\nin float vPart;\nin float vHeight;\nin vec3 vRing;\nin float vDim;\nin float vOccupant;\nin vec3 vOccupantTint;\nuniform vec3 uKeyDir;\nuniform vec3 uFadeColor;\nout vec4 fragColor;\nvoid main() {\n vec3 N = normalize(vNormalWorld);\n vec3 V = normalize(-vPosView);\n // The solids' rig, unchanged, so the chairs sit in the venue's light.\n float hemi = 0.5 + 0.5 * N.y;\n float key = max(dot(N, uKeyDir), 0.0);\n vec3 fillDir = normalize(vec3(-uKeyDir.x, 0.25, -uKeyDir.z));\n float fill = max(dot(N, fillDir), 0.0);\n vec3 tint = vColor;\n if (vOccupant > 0.5) tint = vOccupantTint;\n // The accommodation ring, kept legible once the dot (which drew it) is gone:\n // an accessible seat's PEDESTAL is painted in the ring colour, so the marker\n // survives to close range instead of vanishing exactly when the buyer arrives.\n float ringMask = step(0.001, dot(vRing, vRing));\n if (vPart < 0.5 && ringMask > 0.5) tint = vRing;\n // Pad brightest, back a step below it, pedestal darkest. Three untextured\n // boxes only read as one object if they are separated tonally — with a single\n // flat colour the chair silhouettes as a crate.\n // A person is lit as a person, not as upholstery: no part shading, and less\n // of the pad's sheen, so a head does not read as a polished box.\n float partShade = vOccupant > 0.5 ? 0.95 : (vPart < 0.5 ? 0.50 : (vPart < 1.5 ? 1.10 : 0.80));\n // Cheap vertical occlusion: a chair is in a dense row, so the closer a surface\n // sits to the deck the less sky it can actually see. This is the depth cue —\n // without it the pad top, the back and the deck all resolve to the same flat\n // value and the row loses its form entirely.\n // Occupants rise above the 0.92 m chair back, so their ramp uses their own\n // height or every head would clamp to full brightness and float.\n float ao = mix(0.58, 1.0, clamp(vHeight / (vOccupant > 0.5 ? 1.30 : 0.92), 0.0, 1.0));\n vec3 base = tint * partShade * ao * (0.52 + 0.40 * hemi) + tint * key * 0.38 + tint * fill * 0.10;\n float fres = pow(1.0 - max(dot(normalize(vNormalView), V), 0.0), 3.0);\n // A brighter rim than the solids get: it picks out every chair's own edge,\n // which is what stops a block of them merging into one mass up close.\n base += vec3(0.26, 0.31, 0.38) * fres * 0.55;\n base = mix(base, uFadeColor, vDim * 0.75);\n fragColor = vec4(base, 1.0);\n}`;\n\nconst BG_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 position;\nout vec2 vUv;\nvoid main() {\n vUv = position * 0.5 + 0.5;\n gl_Position = vec4(position, 0.999, 1.0);\n}`;\n\nconst BG_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform vec3 uTop;\nuniform vec3 uBottom;\nout vec4 fragColor;\nvoid main() {\n vec3 col = mix(uBottom, uTop, vUv.y);\n vec2 c = vUv - 0.5;\n float vig = 1.0 - dot(c, c) * 0.85; // soft vignette\n fragColor = vec4(col * vig, 1.0);\n}`;\n\n// --- GPU pick pass ---------------------------------------------------------\n// Seats encode gl_InstanceID+1 as an RGB colour (no extra per-instance buffer);\n// solids write pure black + depth first so a seat occluded by a tier reads as\n// \"no hit\". Same billboard maths as the display seat program.\nconst SEAT_PICK_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec3 iOffset;\nin float iMaxRadius;\nin float iPhysicalSeat;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uSeatRadius;\nuniform float uSeatScale;\nuniform float uMinPixels;\nuniform float uPixelToWorld;\nuniform float uChairFull;\nuniform float uChairNone;\nout vec2 vUv;\nout float vPhysicalSeat;\nout float vDotWeight;\nflat out vec3 vPick;\n${CHAIR_WEIGHT_GLSL}\nvoid main() {\n int id = gl_InstanceID + 1; // 0 reserved for no-hit\n vPick = vec3(float(id & 255), float((id >> 8) & 255), float((id >> 16) & 255)) / 255.0;\n vec4 mv = modelViewMatrix * vec4(iOffset, 1.0);\n float depth = max(-mv.z, 0.001);\n vDotWeight = 1.0 - chairWeight(depth);\n float minR = uMinPixels * depth * uPixelToWorld;\n float r = min(max(uSeatRadius * uSeatScale, minR), iMaxRadius);\n mv.xy += position * r;\n // Must match SEAT_VERT exactly, or the hit mask drifts off the drawn dot.\n mv.xy += normalize(vec3(modelViewMatrix * vec4(0.0, 1.0, 0.0, 0.0))).xy * r;\n vUv = position;\n vPhysicalSeat = iPhysicalSeat;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst SEAT_PICK_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nin float vPhysicalSeat;\nin float vDotWeight;\nflat in vec3 vPick;\nout vec4 fragColor;\nvoid main() {\n if (vDotWeight <= 0.02) discard; // invisible dot cannot steal a chair tap\n float d = mix(max(abs(vUv.x), abs(vUv.y)), length(vUv), vPhysicalSeat);\n if (d > 1.0) discard; // hit-mask matches chair/bay shape\n fragColor = vec4(vPick, 1.0);\n}`;\n\n// The chair/person pick pass uses the exact same base geometry and transforms\n// as CHAIR_VERT. Each gathered instance carries its GLOBAL seat index because\n// gl_InstanceID is only the index inside the bounded near-field subset.\nconst CHAIR_PICK_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nin float part;\nin vec3 iOffset;\nin float iRadius;\nin float iYaw;\nin float iSeed;\nin float iPhysicalSeat;\nin float iPickIndex;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uChairFull;\nuniform float uChairNone;\nuniform float uBackRake;\nuniform float uBackBase;\nflat out vec3 vPick;\n${CHAIR_WEIGHT_GLSL}\n${OCCUPANT_WEIGHT_GLSL}\nvoid main() {\n int id = int(iPickIndex + 0.5); // already global index + 1\n vPick = vec3(float(id & 255), float((id >> 8) & 255), float((id >> 16) & 255)) / 255.0;\n vec4 anchor = modelViewMatrix * vec4(iOffset, 1.0);\n float viewDepth = -anchor.z;\n float depth = max(viewDepth, 0.001);\n float w = viewDepth > 0.05 ? chairWeight(depth) : 0.0;\n vec3 p = position;\n float occupant = step(2.5, part);\n float taken = step(0.0, iSeed);\n if (iPhysicalSeat < 0.5) {\n p = vec3(0.0);\n } else if (occupant > 0.5) {\n if (taken < 0.5) {\n p = vec3(0.0);\n } else {\n p.y *= 0.94 + 0.12 * iSeed;\n p.xz *= 0.92 + 0.16 * fract(iSeed * 7.13);\n p *= occupantWeight(depth);\n }\n }\n p.xz *= iRadius;\n float rake = (part > 1.5 && part < 2.5) ? max(p.y - uBackBase, 0.0) * uBackRake : 0.0;\n p.z -= rake;\n p *= sqrt(w);\n float c = cos(iYaw), s = sin(iYaw);\n vec3 rp = vec3(p.x * c + p.z * s, p.y, -p.x * s + p.z * c);\n gl_Position = projectionMatrix * modelViewMatrix * vec4(iOffset + rp, 1.0);\n}`;\n\nconst CHAIR_PICK_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nflat in vec3 vPick;\nout vec4 fragColor;\nvoid main() { fragColor = vec4(vPick, 1.0); }`;\n\nconst PICK_DEPTH_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst PICK_DEPTH_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nout vec4 fragColor;\nvoid main() { fragColor = vec4(0.0, 0.0, 0.0, 1.0); }`;\n\nexport function createSeatPickProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: SEAT_PICK_VERT,\n fragment: SEAT_PICK_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n uniforms: {\n uSeatRadius: { value: SEAT_DOT_RADIUS_M },\n uSeatScale: { value: 1 },\n uMinPixels: { value: SEAT_MIN_PIXELS_NEAR },\n uPixelToWorld: { value: 0.002 },\n uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n },\n });\n}\n\nexport function createChairPickProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: CHAIR_PICK_VERT,\n fragment: CHAIR_PICK_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n uniforms: {\n uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n uBackRake: { value: BACK_RAKE_SLOPE },\n uBackBase: { value: BACK_BASE_M },\n },\n });\n}\n\n/** Occluder pass: solids to black + depth so occluded seats read as no-hit. */\nexport function createPickDepthProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: PICK_DEPTH_VERT,\n fragment: PICK_DEPTH_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n });\n}\n\nexport function createSolidProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n // No backface culling: free-hand section polygons are stored in raw click\n // order (either winding), so a culled solid would render see-through. The\n // shader lights both faces and closed opaque prisms + depth test keep\n // overdraw negligible; extrudePrism also normalises winding as a belt.\n vertex: SOLID_VERT,\n fragment: SOLID_FRAG,\n cullFace: false,\n depthTest: true,\n depthWrite: true,\n uniforms: {\n // High and off-axis, in world space: reads as a house rig rather than a\n // headlamp welded to the camera.\n uKeyDir: { value: new Float32Array([0.38, 0.86, 0.34]) },\n uFocusFloor: { value: -1 },\n uStructureDetail: { value: 0 },\n },\n });\n}\n\nexport function createSeatProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: SEAT_VERT,\n fragment: SEAT_FRAG,\n transparent: true,\n depthTest: true,\n depthWrite: false,\n cullFace: false,\n uniforms: {\n uSeatRadius: { value: SEAT_DOT_RADIUS_M },\n uSeatScale: { value: 1 },\n uMinPixels: { value: SEAT_MIN_PIXELS_NEAR },\n uPixelToWorld: { value: 0.002 },\n uSeatFade: { value: 0 },\n uSeatDetail: { value: 0 },\n uFocusFloor: { value: -1 },\n uFadeColor: { value: new Float32Array([0.32, 0.37, 0.43]) },\n uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n },\n });\n}\n\n/**\n * The near-field chair program.\n *\n * OPAQUE and depth-writing, unlike the dots. That is the reason the transition\n * is a scale-in and not a cross-fade: a translucent chair either writes depth\n * and punches its own silhouette through the chairs behind it, or does not and\n * shows its own back panel through its own seat pad. Growing an opaque chair out\n * of the seat point avoids both, and it composes correctly with the dot, which\n * still draws afterwards in the transparent pass.\n *\n * `cullFace` stays off to match the rest of the renderer — the meshes are closed\n * opaque boxes, so the depth test already suppresses the back faces' cost, and a\n * winding mistake here would silently blank the chairs rather than fail loudly.\n */\nexport function createChairProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: CHAIR_VERT,\n fragment: CHAIR_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n uniforms: {\n uKeyDir: { value: new Float32Array([0.38, 0.86, 0.34]) },\n uFadeColor: { value: new Float32Array([0.32, 0.37, 0.43]) },\n uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n uBackRake: { value: BACK_RAKE_SLOPE },\n uBackBase: { value: BACK_BASE_M },\n uFocusFloor: { value: -1 },\n },\n });\n}\n\nexport function createBackgroundProgram(gl: OGLRenderingContext, top: number[], bottom: number[]): Program {\n return new Program(gl, {\n vertex: BG_VERT,\n fragment: BG_FRAG,\n depthTest: false,\n depthWrite: false,\n cullFace: false,\n uniforms: {\n uTop: { value: new Float32Array(top) },\n uBottom: { value: new Float32Array(bottom) },\n },\n });\n}\n","/**\n * GPU color-pick (Slice 2). On a TAP (not hover, not drag) the seat instance\n * index is rendered as RGB into a small scissored offscreen target and a single\n * pixel is read back → O(1) regardless of seat count. Solids are drawn first as\n * black + depth so a seat occluded by a tier reads as \"no hit\".\n *\n * The pick meshes reuse the display geometry buffers (iOffset / position), so no\n * per-seat data is duplicated on the GPU.\n */\n\nimport { Geometry, Mesh, Program, RenderTarget, Transform } from 'ogl';\nimport type { Camera, OGLRenderingContext, Renderer } from 'ogl';\nimport { createChairPickProgram, createPickDepthProgram, createSeatPickProgram } from '../scene/materials';\n\nimport { pickNearestFromBuffer } from './encode';\n\nconst SYNC_KEYS = ['uSeatRadius', 'uSeatScale', 'uMinPixels', 'uPixelToWorld'] as const;\n\nexport class PickPipeline {\n private gl: OGLRenderingContext;\n private renderer: Renderer;\n private seatProg: Program;\n private chairProg: Program;\n private depthProg: Program;\n private seatScene = new Transform();\n private chairScene = new Transform();\n private solidScene = new Transform();\n private target: RenderTarget | null = null;\n private maxIndex: number;\n\n /** Display clear colour to restore after the pick pass (theme-dependent). */\n private restoreClear: readonly number[] = [0, 0, 0];\n\n /** Follow the theme's background when the scene is (re)built. */\n setRestoreClear(rgb: readonly number[]): void {\n this.restoreClear = [rgb[0], rgb[1], rgb[2]];\n }\n\n constructor(renderer: Renderer, seatGeo: Geometry, chairGeo: Geometry, solidGeo: Geometry, seatCount: number) {\n this.renderer = renderer;\n this.gl = renderer.gl;\n this.maxIndex = seatCount;\n this.seatProg = createSeatPickProgram(this.gl);\n this.chairProg = createChairPickProgram(this.gl);\n this.depthProg = createPickDepthProgram(this.gl);\n const seatMesh = new Mesh(this.gl, { geometry: seatGeo, program: this.seatProg });\n seatMesh.frustumCulled = false;\n seatMesh.setParent(this.seatScene);\n const chairMesh = new Mesh(this.gl, { geometry: chairGeo, program: this.chairProg });\n chairMesh.frustumCulled = false;\n chairMesh.setParent(this.chairScene);\n const solidMesh = new Mesh(this.gl, { geometry: solidGeo, program: this.depthProg });\n solidMesh.frustumCulled = false;\n solidMesh.setParent(this.solidScene);\n }\n\n /** Match the display seat sizing so the pick mask lines up with the dots. */\n syncFromSeatProgram(seatProgram: Program): void {\n for (const k of SYNC_KEYS) this.seatProg.uniforms[k].value = seatProgram.uniforms[k].value;\n }\n\n private ensureTarget(): RenderTarget {\n const w = this.gl.drawingBufferWidth;\n const h = this.gl.drawingBufferHeight;\n if (this.target && (this.target.width !== w || this.target.height !== h)) {\n this.destroyTarget();\n }\n if (!this.target) {\n this.target = new RenderTarget(this.gl, { width: w, height: h, depth: true });\n }\n return this.target;\n }\n\n private destroyTarget(): void {\n if (!this.target) return;\n const gl = this.gl;\n if (this.target.buffer) gl.deleteFramebuffer(this.target.buffer);\n for (const t of this.target.textures ?? []) if (t.texture) gl.deleteTexture(t.texture);\n if (this.target.depthBuffer) gl.deleteRenderbuffer(this.target.depthBuffer);\n this.target = null;\n }\n\n /**\n * Read back the seat instance index NEAREST framebuffer pixel (px, py), or -1.\n * `radius` is the tap tolerance in buffer px: a box of side (2·radius+1) is\n * rendered + read so a tap that lands between the ~2px overview dots still\n * finds the closest seat. px/py/radius are bottom-left-origin buffer pixels.\n */\n pick(camera: Camera, px: number, py: number, radius: number): number {\n const gl = this.gl;\n const target = this.ensureTarget();\n const bw = gl.drawingBufferWidth;\n const bh = gl.drawingBufferHeight;\n const x0 = Math.max(0, px - radius);\n const y0 = Math.max(0, py - radius);\n const boxW = Math.max(1, Math.min(bw, px + radius + 1) - x0);\n const boxH = Math.max(1, Math.min(bh, py + radius + 1) - y0);\n\n gl.enable(gl.SCISSOR_TEST);\n gl.scissor(x0, y0, boxW, boxH);\n // Clear the pick target to TRUE BLACK so empty + occluded pixels decode to\n // no-hit structurally (not by a range guard). Occluders drawn black + depth\n // first, then seats (pick colours) depth-tested.\n // Restore whatever the display clear colour currently is — the theme may have\n // replaced it, and restoring the library default would flash grey on a\n // white-labelled chart every time the pointer moves.\n const [br, bg, bb] = this.restoreClear;\n gl.clearColor(0, 0, 0, 1);\n this.renderer.render({ scene: this.solidScene, camera, target, clear: true });\n this.renderer.render({ scene: this.seatScene, camera, target, clear: false });\n // Exact visible chair/person geometry wins over a projected seat behind it.\n // The shared geometry carries global ids for the bounded gathered subset.\n this.renderer.render({ scene: this.chairScene, camera, target, clear: false });\n gl.clearColor(br, bg, bb, 1); // restore the display clear colour\n gl.disable(gl.SCISSOR_TEST);\n\n const buf = new Uint8Array(boxW * boxH * 4);\n this.renderer.bindFramebuffer(target);\n gl.readPixels(x0, y0, boxW, boxH, gl.RGBA, gl.UNSIGNED_BYTE, buf);\n this.renderer.bindFramebuffer();\n\n return pickNearestFromBuffer(buf, boxW, boxH, px - x0, py - y0, this.maxIndex);\n }\n\n dispose(): void {\n this.destroyTarget();\n this.seatProg.remove();\n this.chairProg.remove();\n this.depthProg.remove();\n }\n}\n","/**\n * GPU color-pick id encoding — pure, DOM-free, so the round-trip and the tap →\n * framebuffer pixel maths are unit-testable. The seat instance index is offset\n * by +1 so id 0 is reserved for \"no hit\" (the cleared black background).\n */\n\n/** instanceIndex → normalised RGB (0..1) the pick shader writes. */\nexport function encodePickId(instanceIndex: number): [number, number, number] {\n const id = instanceIndex + 1;\n return [(id & 255) / 255, ((id >> 8) & 255) / 255, ((id >> 16) & 255) / 255];\n}\n\n/** RGB bytes (0..255) read back → instanceIndex, or -1 for the no-hit clear. */\nexport function decodePickRGB(r: number, g: number, b: number): number {\n const id = r + (g << 8) + (b << 16);\n return id === 0 ? -1 : id - 1;\n}\n\n/**\n * Scan a readback window (RGBA, bottom-left origin, row-major) for the seat hit\n * NEAREST the tap centre. A single tap on a low-res overview lands between ~2px\n * dots, so we read a small box and pick the closest non-empty seat instead of a\n * single pixel. `centerI/centerJ` are the tap's box-local pixel coords.\n * `maxIndex` bounds valid indices (defence against a stray decode).\n */\nexport function pickNearestFromBuffer(\n pixels: Uint8Array,\n boxW: number,\n boxH: number,\n centerI: number,\n centerJ: number,\n maxIndex: number,\n): number {\n let best = -1;\n let bestDist = Infinity;\n for (let j = 0; j < boxH; j++) {\n for (let i = 0; i < boxW; i++) {\n const o = (j * boxW + i) * 4;\n const idx = decodePickRGB(pixels[o], pixels[o + 1], pixels[o + 2]);\n if (idx < 0 || idx >= maxIndex) continue;\n const di = i - centerI;\n const dj = j - centerJ;\n const d = di * di + dj * dj;\n if (d < bestDist) { bestDist = d; best = idx; }\n }\n }\n return best;\n}\n\n/**\n * Map a tap in CSS pixels (relative to the canvas bounding rect) to a\n * bottom-left-origin framebuffer pixel, clamped in range. `rect` is the canvas\n * getBoundingClientRect; `dpr` the renderer device-pixel-ratio.\n */\nexport function pickPixelCoords(\n clientX: number,\n clientY: number,\n rect: { left: number; top: number; width: number; height: number },\n dpr: number,\n bufferWidth: number,\n bufferHeight: number,\n): { x: number; y: number } {\n const cssX = clientX - rect.left;\n const cssY = clientY - rect.top;\n const x = Math.round(cssX * dpr);\n // WebGL framebuffer origin is bottom-left → flip Y.\n const y = Math.round((rect.height - cssY) * dpr);\n return {\n x: Math.max(0, Math.min(bufferWidth - 1, x)),\n y: Math.max(0, Math.min(bufferHeight - 1, y)),\n };\n}\n","/**\n * Pure selection-state diffing. Selection is a colour layer over availability:\n * a selected seat shows the 'selected' colour and, on deselect, restores the\n * base availability state it had when it was selected (remembered in `prev`).\n * Kept DOM/GPU-free so the transitions are unit-testable.\n */\n\nimport { SEAT_STATES, seatStateIndex, type SeatState3D } from '../palette';\n\nexport interface SelectionUpdate {\n seatId: string;\n state: SeatState3D;\n}\n\n/**\n * Reconcile an availability update against the current selection. A selected\n * seat that changes availability must STAY 'selected' on screen while its\n * remembered base state is updated (so a later deselect restores the CURRENT\n * availability, not the pre-change one). Mutates `selection` in place and\n * returns the updates that should actually be written to iState (the\n * non-selected ones — selected seats keep their 'selected' colour).\n */\nexport function mergeAvailabilityIntoSelection(\n selection: Map<string, number>,\n updates: SelectionUpdate[],\n): SelectionUpdate[] {\n const passthrough: SelectionUpdate[] = [];\n for (const u of updates) {\n if (selection.has(u.seatId)) selection.set(u.seatId, seatStateIndex(u.state));\n else passthrough.push(u);\n }\n return passthrough;\n}\n\nexport interface SelectionDiff {\n updates: SelectionUpdate[];\n /** New seatId → remembered base-state index map. */\n next: Map<string, number>;\n}\n\n/**\n * Diff the current selection (`prev`: seatId → base-state index) against the\n * desired seat ids. `baseStateIndex` reads the seat's CURRENT state index (used\n * only for newly-selected seats, which are not yet recoloured).\n */\nexport function diffSelection(\n prev: Map<string, number>,\n desiredIds: string[],\n baseStateIndex: (seatId: string) => number | undefined,\n): SelectionDiff {\n const desired = new Set<string>();\n for (const id of desiredIds) {\n if (prev.has(id) || baseStateIndex(id) !== undefined) desired.add(id);\n }\n const next = new Map(prev);\n const updates: SelectionUpdate[] = [];\n\n // Deselect: restore base availability for ids leaving the selection.\n for (const [id, base] of prev) {\n if (!desired.has(id)) {\n updates.push({ seatId: id, state: SEAT_STATES[base] ?? 'available' });\n next.delete(id);\n }\n }\n // Select: remember base state, then recolour as selected.\n for (const id of desired) {\n if (next.has(id)) continue; // already selected → unchanged\n const base = baseStateIndex(id);\n if (base === undefined) continue;\n next.set(id, base);\n updates.push({ seatId: id, state: 'selected' });\n }\n return { updates, next };\n}\n","/**\n * Slice 3 — the purchase-moment fly-to-seat cinematic controller. One continuous\n * shot from the venue overview into the picked seat: a catmull-rom position\n * spline (smootherstep timing) + a look-at quaternion slerp whose orientation\n * slightly LEADS the position, with a gentle FOV push-in. The pure maths lives\n * in cinematicMath.ts (tested); this drives the OGL camera with it.\n *\n * Technique locked in docs/3d-usp-strategy-2026-07-23.md §3.\n */\n\nimport { Quat, Vec3 } from 'ogl';\nimport type { Camera } from 'ogl';\nimport {\n FLIGHT_DURATION_MS, FOV_END, FOV_START, ORIENTATION_LEAD,\n sampleFlight, orientationLeadT, type Vec3Arr,\n} from './cinematicMath';\n\nexport {\n FLIGHT_DURATION_MS, FOV_START, FOV_END, ORIENTATION_LEAD,\n smootherstep, orientationLeadT, catmullRom, buildWaypoints, sampleFlight,\n type Vec3Arr, type FlightSample,\n} from './cinematicMath';\n\n/** Look-at quaternion from `from` toward `to`, computed via the OGL camera\n * (save/restore) — no manual quat maths. Synchronous, no render between. */\nexport function lookAtQuat(camera: Camera, from: Vec3Arr, to: Vec3Arr): Quat {\n const savedPos = camera.position.clone();\n const savedQuat = new Quat().copy(camera.quaternion);\n camera.position.set(from[0], from[1], from[2]);\n camera.lookAt(new Vec3(to[0], to[1], to[2]));\n const q = new Quat().copy(camera.quaternion);\n camera.position.copy(savedPos);\n camera.quaternion.copy(savedQuat);\n return q;\n}\n\n/** Drives the OGL camera along a flight. Integrated with the render loop: the\n * loop calls update() each frame while active. */\nexport class Cinematic {\n active = false;\n private camera: Camera;\n private waypoints: Vec3Arr[] = [];\n private startQuat = new Quat();\n private endQuat = new Quat();\n private outQuat = new Quat();\n private startTime = 0;\n private duration = FLIGHT_DURATION_MS;\n private endFov = FOV_END;\n private resolveFn: (() => void) | null = null;\n\n constructor(camera: Camera) {\n this.camera = camera;\n }\n\n /** Begin (or retarget) a flight. Resolves when it lands or is cancelled. */\n start(\n waypoints: Vec3Arr[],\n startQuat: Quat,\n endQuat: Quat,\n duration = FLIGHT_DURATION_MS,\n endFov = FOV_END,\n ): Promise<void> {\n this.settle(); // resolve any in-flight promise before retargeting\n this.waypoints = waypoints;\n this.startQuat.copy(startQuat);\n this.endQuat.copy(endQuat);\n this.duration = duration;\n this.endFov = endFov;\n this.startTime = performance.now();\n this.active = true;\n return new Promise<void>((res) => { this.resolveFn = res; });\n }\n\n /** Advance the flight, mutating the camera. Returns true while still flying. */\n update(now: number): boolean {\n if (!this.active) return false;\n const u = Math.min(1, (now - this.startTime) / this.duration);\n const { pos, fov, eased } = sampleFlight(this.waypoints, u, FOV_START, this.endFov);\n this.camera.position.set(pos[0], pos[1], pos[2]);\n this.outQuat.copy(this.startQuat).slerp(this.endQuat, orientationLeadT(eased, ORIENTATION_LEAD));\n this.camera.quaternion.copy(this.outQuat);\n this.camera.fov = fov;\n this.camera.updateProjectionMatrix();\n if (u >= 1) { this.settle(); return false; }\n return true;\n }\n\n /** Stop where we are (no snap) — the camera keeps its current pose. */\n cancel(): void {\n this.settle();\n }\n\n private settle(): void {\n this.active = false;\n const r = this.resolveFn;\n this.resolveFn = null;\n if (r) r();\n }\n}\n","/**\n * Pure, DOM/GPU-free maths for the fly-to-seat cinematic — spline, timing, and\n * waypoint construction. Split from cinematic.ts (which imports OGL) so this is\n * unit-testable in a plain runtime.\n */\n\nexport const FLIGHT_DURATION_MS = 2500;\nexport const FOV_START = 35;\nexport const FOV_END = 28;\n/** Orientation t leads position t by this much (clamped) — aim before arrival. */\nexport const ORIENTATION_LEAD = 0.15;\n/** Final approach: this far behind the seat (≈2–3 rows) and above its eye. */\nconst BACK_M = 2.5;\nconst ABOVE_EYE_M = 1.5;\n\nexport type Vec3Arr = [number, number, number];\n\nfunction sub(a: Vec3Arr, b: Vec3Arr): Vec3Arr { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; }\nfunction add(a: Vec3Arr, b: Vec3Arr): Vec3Arr { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; }\nfunction scale(a: Vec3Arr, k: number): Vec3Arr { return [a[0] * k, a[1] * k, a[2] * k]; }\nfunction norm(a: Vec3Arr): Vec3Arr {\n const l = Math.hypot(a[0], a[1], a[2]);\n return l > 1e-6 ? [a[0] / l, a[1] / l, a[2] / l] : [0, 0, 0];\n}\n\n/** Smootherstep (Ken Perlin) — zero 1st & 2nd derivatives at the ends. */\nexport function smootherstep(t: number): number {\n const x = Math.max(0, Math.min(1, t));\n return x * x * x * (x * (x * 6 - 15) + 10);\n}\n\n/** Orientation t = position t nudged ahead by `lead`, clamped to [0,1]. */\nexport function orientationLeadT(posT: number, lead: number): number {\n return Math.max(0, Math.min(1, posT + lead));\n}\n\n/**\n * Uniform multi-segment Catmull-Rom passing THROUGH every waypoint. `u` in\n * [0,1] spans the whole path; endpoints are duplicated for tangents.\n */\nexport function catmullRom(points: Vec3Arr[], u: number): Vec3Arr {\n const n = points.length;\n if (n === 0) return [0, 0, 0];\n if (n === 1) return [...points[0]];\n const cu = Math.max(0, Math.min(1, u));\n const segCount = n - 1;\n let seg = Math.floor(cu * segCount);\n if (seg >= segCount) seg = segCount - 1;\n const t = cu * segCount - seg;\n const p0 = points[Math.max(0, seg - 1)];\n const p1 = points[seg];\n const p2 = points[seg + 1];\n const p3 = points[Math.min(n - 1, seg + 2)];\n const t2 = t * t;\n const t3 = t2 * t;\n const out: Vec3Arr = [0, 0, 0];\n for (let i = 0; i < 3; i++) {\n out[i] = 0.5 * (\n 2 * p1[i]\n + (-p0[i] + p2[i]) * t\n + (2 * p0[i] - 5 * p1[i] + 4 * p2[i] - p3[i]) * t2\n + (-p0[i] + 3 * p1[i] - 3 * p2[i] + p3[i]) * t3\n );\n }\n return out;\n}\n\n/**\n * Build the flight waypoints from the current camera position to a seat. The\n * mid arc is pushed OUTSIDE the venue bounds sphere and high up, so the swoop\n * never clips through the tier solids; the final anchor sits behind + above the\n * seat, looking toward the focal point.\n */\nexport function buildWaypoints(\n start: Vec3Arr,\n seatEye: Vec3Arr,\n focal: Vec3Arr,\n center: Vec3Arr,\n radius: number,\n): { waypoints: Vec3Arr[]; finalPos: Vec3Arr } {\n let away = norm(sub(seatEye, focal)); // \"behind\" the seat (away from stage)\n if (away[0] === 0 && away[1] === 0 && away[2] === 0) away = [0, 0, 1];\n const finalPos = add(add(seatEye, scale(away, BACK_M)), [0, ABOVE_EYE_M, 0]);\n\n const horiz: Vec3Arr = [seatEye[0] - center[0], 0, seatEye[2] - center[2]];\n let hn = norm(horiz);\n if (hn[0] === 0 && hn[2] === 0) hn = [away[0], 0, away[2]];\n const r = Math.max(1, radius);\n const arc: Vec3Arr = [\n center[0] + hn[0] * r * 1.3,\n center[1] + r * 0.75,\n center[2] + hn[2] * r * 1.3,\n ];\n return { waypoints: [start, arc, finalPos], finalPos };\n}\n\nexport interface FlightSample {\n pos: Vec3Arr;\n fov: number;\n /** Eased position parameter (drives the orientation lead). */\n eased: number;\n}\n\n/** Sample the flight at raw parameter `u` in [0,1]. */\nexport function sampleFlight(waypoints: Vec3Arr[], u: number, fovStart = FOV_START, fovEnd = FOV_END): FlightSample {\n const eased = smootherstep(u);\n const pos = catmullRom(waypoints, eased);\n const fovT = smootherstep(Math.max(0, Math.min(1, (u - 0.66) / 0.34))); // push-in over final third\n return { pos, fov: fovStart + (fovEnd - fovStart) * fovT, eased };\n}\n","import type { Point } from '../../core/types';\nimport { M } from '../scene/geometry';\nimport type { Vec3Arr } from './cinematicMath';\n\n/** Seated eye height above the rendered seat deck. */\nexport const SEAT_EYE_ABOVE_DECK_M = 1.02;\n\n/** The authored focal is a floor point; aim at show/stage eye level. */\nexport const FOCAL_LOOK_HEIGHT_M = 1.5;\n\n/**\n * Resolve the truthful pose for an in-scene view from a selected seat.\n *\n * The cinematic deliberately arrives behind and above this pose. Entering 360\n * is the point at which the camera must move to the buyer's actual seated eye,\n * while still looking at the focal resolved for that seat's zone/floor.\n */\nexport function seatViewPose(\n seatDeckWorld: Vec3Arr,\n focalPoint: Point,\n floorBaseHeightM = 0,\n authoredEyeHeightM?: number,\n): { eye: Vec3Arr; focal: Vec3Arr } {\n // ExpandedSeat.eyeHeightM is measured from the event/floor datum and is the\n // authoritative chart contract when present. The rendered seat instance has\n // a small visual clearance baked into its Y, so adding the legacy eye offset\n // to that decorated position would silently shift a verified camera.\n const eyeY = Number.isFinite(authoredEyeHeightM)\n ? floorBaseHeightM + authoredEyeHeightM!\n : seatDeckWorld[1] + SEAT_EYE_ABOVE_DECK_M;\n return {\n eye: [seatDeckWorld[0], eyeY, seatDeckWorld[2]],\n focal: [\n focalPoint.x * M,\n floorBaseHeightM + FOCAL_LOOK_HEIGHT_M,\n focalPoint.y * M,\n ],\n };\n}\n","/** Progressive delivery policy shared by the WebGL and DOM panorama viewers. */\n\nexport interface PanoramaDeliverySource {\n url: string;\n previewUrl?: string;\n sourceWidth?: number;\n sourceHeight?: number;\n previewWidth?: number;\n previewHeight?: number;\n}\n\nexport interface PanoramaDeliveryConstraints {\n saveData?: boolean;\n maxTextureSize?: number;\n maxDecodedBytes?: number;\n}\n\nexport interface PanoramaDeliveryPlan {\n initialUrl: string;\n upgradeUrl?: string;\n initialWidth?: number;\n initialHeight?: number;\n reason: 'full-only' | 'progressive' | 'save-data' | 'texture-limit' | 'memory-limit';\n}\n\n/** Approximate GPU allocation including a complete mip chain (4/3 overhead). */\nexport function estimatePanoramaTextureBytes(width: number, height: number): number {\n if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return 0;\n return Math.ceil(width * height * 4 * (4 / 3));\n}\n\n/** Conservative per-panorama budget, raised only on devices reporting ample RAM. */\nexport function panoramaTextureBudgetBytes(deviceMemoryGb?: number): number {\n const mib = deviceMemoryGb !== undefined && deviceMemoryGb <= 2\n ? 32\n : deviceMemoryGb !== undefined && deviceMemoryGb >= 8\n ? 192\n : 96;\n return mib * 1024 * 1024;\n}\n\nexport function browserPanoramaConstraints(maxTextureSize?: number): PanoramaDeliveryConstraints {\n const nav = typeof navigator === 'undefined'\n ? undefined\n : navigator as Navigator & { deviceMemory?: number; connection?: { saveData?: boolean } };\n return {\n saveData: nav?.connection?.saveData === true,\n maxTextureSize,\n maxDecodedBytes: panoramaTextureBudgetBytes(nav?.deviceMemory),\n };\n}\n\n/** Select the first image and whether a later sharp-image upgrade is safe. */\nexport function planPanoramaDelivery(\n source: PanoramaDeliverySource,\n constraints: PanoramaDeliveryConstraints = {},\n): PanoramaDeliveryPlan {\n const previewUrl = source.previewUrl?.trim();\n if (!previewUrl || previewUrl === source.url) {\n return {\n initialUrl: source.url,\n initialWidth: source.sourceWidth,\n initialHeight: source.sourceHeight,\n reason: 'full-only',\n };\n }\n\n const initial = {\n initialUrl: previewUrl,\n initialWidth: source.previewWidth,\n initialHeight: source.previewHeight,\n };\n if (constraints.saveData) return { ...initial, reason: 'save-data' };\n if (constraints.maxTextureSize !== undefined && source.sourceWidth !== undefined && source.sourceHeight !== undefined\n && Math.max(source.sourceWidth, source.sourceHeight) > constraints.maxTextureSize) {\n return { ...initial, reason: 'texture-limit' };\n }\n if (constraints.maxDecodedBytes !== undefined && source.sourceWidth !== undefined && source.sourceHeight !== undefined\n && estimatePanoramaTextureBytes(source.sourceWidth, source.sourceHeight) > constraints.maxDecodedBytes) {\n return { ...initial, reason: 'memory-limit' };\n }\n return { ...initial, upgradeUrl: source.url, reason: 'progressive' };\n}\n\nfunction abortError(): DOMException {\n return new DOMException('Panorama image load aborted', 'AbortError');\n}\n\n/** Decode before resolving so callers never upload or reveal a partial image. */\nexport function loadPanoramaImage(url: string, signal?: AbortSignal): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) { reject(abortError()); return; }\n const image = new Image();\n image.crossOrigin = 'anonymous';\n let settled = false;\n const cleanup = (): void => {\n image.onload = null;\n image.onerror = null;\n signal?.removeEventListener('abort', onAbort);\n };\n const finish = (error?: unknown): void => {\n if (settled) return;\n settled = true;\n cleanup();\n if (error) reject(error);\n else resolve(image);\n };\n const onAbort = (): void => {\n image.src = '';\n finish(abortError());\n };\n image.onload = () => {\n const decoded = typeof image.decode === 'function' ? image.decode() : Promise.resolve();\n void decoded.then(() => finish(), () => finish());\n };\n image.onerror = () => finish(new Error('panorama_image_failed'));\n signal?.addEventListener('abort', onAbort, { once: true });\n image.src = url;\n });\n}\n\n/** Let the preview paint before starting a potentially large decode. */\nexport function schedulePanoramaUpgrade(work: () => void): () => void {\n const host = globalThis as typeof globalThis & {\n requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => number;\n cancelIdleCallback?: (id: number) => void;\n };\n if (host.requestIdleCallback) {\n const id = host.requestIdleCallback(work, { timeout: 1200 });\n return () => host.cancelIdleCallback?.(id);\n }\n const id = globalThis.setTimeout(work, 32);\n return () => globalThis.clearTimeout(id);\n}\n","/**\n * Slice 3 hand-off — the DOM panorama overlay the fly-to-seat cinematic\n * dissolves into. Decoupled by design: the CALLER supplies the equirectangular\n * image (via mountVenue3D's getSeatView), so view3d never imports the app's\n * panorama generator and the chunk stays lean.\n *\n * Technique (mirrors SeatPicker.openSeatView, reimplemented small): an equirect\n * image panned with `repeat-x`; the initial horizontal offset is set so the\n * panorama's bearing matches the final camera yaw — the dissolve reads as the\n * same view sharpening, not a cut. CSS opacity fade is compositor-only.\n */\n\nimport type { SeatViewCoverage } from '../../core/types';\nimport {\n browserPanoramaConstraints,\n loadPanoramaImage,\n planPanoramaDelivery,\n schedulePanoramaUpgrade,\n} from '../../view/panoramaDelivery';\n\nexport interface SeatView {\n url: string;\n /** Lightweight equirect used for first paint before `url` replaces it. */\n previewUrl?: string;\n /**\n * Optional authenticated byte-to-object-URL resolver. The buyer picker uses\n * this for a deferred sharp-image upgrade so progressive view media never\n * needs a bearer in its URL.\n */\n resolveUrl?: (url: string) => Promise<string | null>;\n sourceWidth?: number;\n sourceHeight?: number;\n previewWidth?: number;\n previewHeight?: number;\n /**\n * True when `url` is a SYNTHESISED panorama rather than a real photograph.\n *\n * It decides which of two very different things the buyer gets, so the caller\n * must say rather than the renderer guess. A generated equirect is a picture\n * of a 3D scene the renderer already has: at 2048×1024 it covers 106° of\n * horizontal view in ~600 source pixels, which a 3072-device-pixel viewport\n * magnifies FIVE times, and no amount of art survives that. So a generated\n * view is not drawn as an image at all — the camera simply looks around the\n * real scene, which has no resolution ceiling.\n *\n * A real photo has no such substitute and goes on the sphere, where a 5760 or\n * 8192-wide capture has three to four times the detail to give.\n */\n generated?: boolean;\n /** Explicit media provenance; avoids inferring buyer truth from source copy. */\n mediaKind?: 'photo' | 'demo-render' | 'model';\n /** Bearing (deg, 0 = facing the focal/stage) the panorama should open centred\n * on, to match the camera's final yaw. Default 0 (both face the stage). */\n initialBearingDeg?: number;\n /** Initial vertical orientation. Positive looks up; clamped to the viewer's\n * safe pitch range. Uploaded captures use this to match the live seat pose. */\n initialPitchDeg?: number;\n /** How specifically this image represents the selected inventory unit. */\n coverage?: SeatViewCoverage;\n /** ISO capture date/time when known. Unknown is preferable to invented age. */\n capturedAt?: string;\n /** Optional buyer-safe provenance, for example \"Provided by the venue\". */\n sourceLabel?: string;\n}\n\n/** Honest, compact buyer disclosure for the panorama currently on screen. */\nexport function seatViewDisclosure(view: SeatView): string {\n const demo = view.mediaKind === 'demo-render';\n const coverage = view.generated || view.mediaKind === 'model'\n ? 'Live 3D · chart-derived seat-eye'\n : demo\n ? view.coverage === 'exact-seat'\n ? 'AI-generated exact-seat demo'\n : view.coverage === 'row-representative'\n ? 'AI-generated representative row demo'\n : view.coverage === 'section-representative'\n ? 'AI-generated representative section demo'\n : 'AI-generated illustrative venue demo'\n : view.coverage === 'exact-seat'\n ? 'Exact seat photo'\n : view.coverage === 'row-representative'\n ? 'Representative row view'\n : view.coverage === 'section-representative'\n ? 'Representative section view'\n : view.coverage === 'venue-representative'\n ? 'Representative venue view'\n : 'Venue photo';\n const year = view.capturedAt && /^\\d{4}/.test(view.capturedAt) ? view.capturedAt.slice(0, 4) : '';\n return [coverage, year ? `${demo ? 'created' : 'captured'} ${year}` : '', view.sourceLabel ?? ''].filter(Boolean).join(' · ');\n}\n\n/** True only when the panorama should be presented as organizer-authored media.\n * Representative AI demo assets travel through the same URL field as venue\n * photos, so provenance—not URL presence—must decide the buyer badge. */\nexport function isAuthoredSeatView(view: SeatView): boolean {\n if (view.generated || view.mediaKind === 'model' || view.mediaKind === 'demo-render') return false;\n const source = view.sourceLabel?.trim() ?? '';\n return !/^(?:AI[- ]generated|generated\\s+(?:demo|preview))/i.test(source);\n}\n\nexport interface PanoramaHandle {\n /** Fade out and return to the (frozen) 3D view; calls opts.onClose after. */\n close(): void;\n /** Immediate teardown (dispose) — no fade, no onClose. */\n dispose(): void;\n}\n\n/**\n * Vertical field of view (deg) the windowed panorama shows. The source image is\n * a full 180° equirect sphere; showing it raw wastes ~⅔ of the frame on dead sky\n * and black floor, with the horizon content band squished into the middle. We\n * instead scale the image so only this central slice fills the viewport height,\n * horizon-centred, and let the user drag pitch within ±`MAX_PITCH_DEG`.\n */\nexport const VFOV_DEG = 70;\nexport const MIN_VFOV_DEG = 35;\nexport const MAX_VFOV_DEG = 90;\n\n/** Keep wheel, keyboard and pinch zoom inside a useful, non-disorienting range. */\nexport function clampPanoramaFov(fovDeg: number): number {\n return Math.max(MIN_VFOV_DEG, Math.min(MAX_VFOV_DEG, fovDeg));\n}\n/** Users may look this far up/down from the horizon; well inside the image so\n * the clamp never reveals past its top/bottom edge. */\nexport const MAX_PITCH_DEG = 35;\n\n/**\n * Horizontal background-position (px) that centres `bearingDeg` in the viewport,\n * assuming the equirect image's yaw 0 sits at its horizontal centre. `bgW` is the\n * full scaled image width representing 360° — so this is invariant to the vertical\n * FOV windowing (which scales width and height by the same factor). `repeat-x`\n * handles the wrap, so any real value is valid.\n */\nexport function bearingToOffsetPx(bearingDeg: number, viewportW: number, bgW: number): number {\n const col = (0.5 + bearingDeg / 360) * bgW; // image column (px) for the bearing\n return viewportW / 2 - col;\n}\n\n/**\n * Full scaled image height (px) so that a `vfovDeg`-tall slice fills `viewportH`.\n * The image spans 180° vertically, so height = viewportH · 180/vfov.\n */\nexport function windowedBgHeight(viewportH: number, vfovDeg: number = VFOV_DEG): number {\n return viewportH * (180 / vfovDeg);\n}\n\n/**\n * background-position Y (px) that centres the image's horizon (its vertical\n * centre) in the viewport, offset by `pitchPx` (deviation from the horizon,\n * clamped to ±`MAX_PITCH_DEG`). Positive `pitchPx` looks up.\n */\nexport function horizonOffsetPy(viewportH: number, bgH: number, pitchPx: number): number {\n return (viewportH - bgH) / 2 + clampPitchPx(pitchPx, bgH);\n}\n\n/** Clamp a pitch drag (px) to ±MAX_PITCH_DEG of image travel, and never past the\n * image edge. `bgH` px map the full 180°, so a degree is `bgH/180` px. */\nexport function clampPitchPx(pitchPx: number, bgH: number): number {\n const limit = (MAX_PITCH_DEG / 180) * bgH;\n return Math.max(-limit, Math.min(limit, pitchPx));\n}\n\nexport interface PanoramaOptions {\n fadeMs?: number;\n seatLabel?: string;\n disclosure?: string;\n /** Prefix for the disclosure chip; generated live geometry is not a panorama. */\n disclosurePrefix?: string;\n onClose?: () => void;\n /** Cancels stale preview/full-resolution image work when a flight is retargeted. */\n signal?: AbortSignal;\n}\n\nexport function mountPanorama(container: HTMLElement, view: SeatView, opts: PanoramaOptions = {}): PanoramaHandle {\n const fadeMs = opts.fadeMs ?? 400;\n const bearing = view.initialBearingDeg ?? 0;\n\n const delivery = planPanoramaDelivery(view, browserPanoramaConstraints());\n const loadAbort = new AbortController();\n const abortFromCaller = (): void => loadAbort.abort();\n opts.signal?.addEventListener('abort', abortFromCaller, { once: true });\n const root = document.createElement('div');\n const priorFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-label', opts.seatLabel ? `View from ${opts.seatLabel}` : 'View from seat');\n root.tabIndex = -1;\n Object.assign(root.style, {\n position: 'absolute', inset: '0', zIndex: '10', opacity: '0',\n transition: `opacity ${fadeMs}ms ease`, background: '#05070c',\n overflow: 'hidden', touchAction: 'none',\n } as CSSStyleDeclaration);\n\n const pano = document.createElement('div');\n Object.assign(pano.style, {\n position: 'absolute', inset: '0',\n backgroundImage: `url(\"${delivery.initialUrl}\")`, backgroundRepeat: 'repeat-x',\n cursor: 'grab',\n } as CSSStyleDeclaration);\n root.appendChild(pano);\n\n // Close affordance.\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.textContent = '✕';\n Object.assign(closeBtn.style, {\n position: 'absolute', top: '12px', right: '12px', zIndex: '2',\n width: '44px', height: '44px', borderRadius: '999px', cursor: 'pointer',\n border: '1px solid rgba(255,255,255,0.25)', background: 'rgba(8,12,18,0.6)',\n color: '#e6edf3', fontSize: '15px', lineHeight: '1',\n } as CSSStyleDeclaration);\n root.appendChild(closeBtn);\n\n if (opts.disclosure) {\n const disclosure = document.createElement('div');\n disclosure.textContent = `${opts.disclosurePrefix ?? '360° panorama'} · ${opts.disclosure}`;\n Object.assign(disclosure.style, {\n position: 'absolute', top: '12px', left: '12px', zIndex: '2',\n maxWidth: 'calc(100% - 88px)', padding: '8px 11px', borderRadius: '999px',\n overflow: 'hidden', color: '#dce6f8', background: 'rgba(8,12,18,0.68)',\n font: '600 11px/1.2 ui-sans-serif, system-ui, sans-serif', textOverflow: 'ellipsis',\n whiteSpace: 'nowrap', pointerEvents: 'none', backdropFilter: 'blur(6px)',\n } as CSSStyleDeclaration);\n root.appendChild(disclosure);\n }\n\n const hint = document.createElement('div');\n hint.textContent = 'Drag to look · pinch or scroll to zoom · Esc to close';\n Object.assign(hint.style, {\n position: 'absolute', bottom: '12px', left: '0', right: '0', textAlign: 'center',\n color: 'rgba(230,237,243,0.7)', font: '12px ui-sans-serif, system-ui, sans-serif',\n pointerEvents: 'none',\n } as CSSStyleDeclaration);\n root.appendChild(hint);\n\n container.appendChild(root);\n closeBtn.focus({ preventScroll: true });\n\n // Layout: window a ~70° vertical slice of the sphere (horizon-centred) so the\n // venue fills the frame instead of floating in dead sky + black floor. The\n // image is scaled so that slice is exactly the viewport height; width scales by\n // the same factor, so `bearingToOffsetPx` stays correct. `pitchPx` is the\n // vertical drag deviation from the horizon, clamped to ±35°.\n let bgW = 0;\n let bgH = 0;\n let posX = 0;\n let pitchPx = 0;\n let viewFov = VFOV_DEG;\n const layout = (): void => {\n const vh = root.clientHeight || 1;\n const vw = root.clientWidth || 1;\n const natW = img.naturalWidth || vw * 2;\n const natH = img.naturalHeight || vh;\n const centredImageRatio = bgW > 0 ? (vw / 2 - posX) / bgW : 0;\n const pitchRatio = bgH > 0 ? pitchPx / bgH : 0;\n bgH = windowedBgHeight(vh, viewFov);\n bgW = bgH * (natW / natH);\n pano.style.backgroundSize = `${bgW}px ${bgH}px`;\n if (!posInitialised) {\n posX = bearingToOffsetPx(bearing, vw, bgW);\n pitchPx = clampPitchPx(((view.initialPitchDeg ?? 0) / 180) * bgH, bgH);\n posInitialised = true;\n }\n else posX = vw / 2 - centredImageRatio * bgW;\n pitchPx = clampPitchPx(pitchRatio * bgH, bgH);\n pano.style.backgroundPosition = `${posX}px ${horizonOffsetPy(vh, bgH, pitchPx)}px`;\n };\n let posInitialised = false;\n\n const applyPos = (): void => {\n const vh = root.clientHeight || 1;\n pitchPx = clampPitchPx(pitchPx, bgH);\n pano.style.backgroundPosition = `${posX}px ${horizonOffsetPy(vh, bgH, pitchPx)}px`;\n };\n\n let img = new Image();\n img.onload = layout;\n img.src = delivery.initialUrl;\n let cancelUpgrade = (): void => {};\n if (delivery.upgradeUrl) {\n cancelUpgrade = schedulePanoramaUpgrade(() => {\n const resolved = view.resolveUrl\n ? view.resolveUrl(delivery.upgradeUrl!)\n : Promise.resolve(delivery.upgradeUrl!);\n void resolved.then((url) => {\n if (!url || loadAbort.signal.aborted) return null;\n return loadPanoramaImage(url, loadAbort.signal).then((full) => ({ full, url }));\n }).then((loaded) => {\n if (!loaded) return;\n if (disposed || loadAbort.signal.aborted) return;\n img = loaded.full;\n pano.style.backgroundImage = `url(\"${loaded.url}\")`;\n layout();\n }).catch(() => { /* the preview remains usable */ });\n });\n }\n // If it's already cached, onload may not fire — lay out on next frame too.\n requestAnimationFrame(layout);\n\n // Pan: horizontal (repeat-x wraps seamlessly) + vertical pitch (clamped ±35°).\n let dragging = false;\n let lastX = 0;\n let lastY = 0;\n let pinchDistance = 0;\n const pointers = new Map<number, { x: number; y: number }>();\n const distanceBetweenPointers = (): number => {\n const [a, b] = [...pointers.values()];\n return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;\n };\n const setViewFov = (nextFov: number): void => {\n const clamped = clampPanoramaFov(nextFov);\n if (clamped === viewFov) return;\n viewFov = clamped;\n layout();\n };\n const onDown = (e: PointerEvent): void => {\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n dragging = pointers.size === 1;\n lastX = e.clientX; lastY = e.clientY;\n if (pointers.size === 2) pinchDistance = distanceBetweenPointers();\n pano.style.cursor = 'grabbing';\n try { pano.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onMove = (e: PointerEvent): void => {\n if (!pointers.has(e.pointerId)) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (pointers.size >= 2) {\n const nextDistance = distanceBetweenPointers();\n if (pinchDistance > 0 && nextDistance > 0) setViewFov(viewFov * (pinchDistance / nextDistance));\n pinchDistance = nextDistance;\n dragging = false;\n return;\n }\n if (!dragging) return;\n posX += e.clientX - lastX;\n pitchPx += e.clientY - lastY;\n lastX = e.clientX;\n lastY = e.clientY;\n applyPos();\n };\n const onUp = (e: PointerEvent): void => {\n pointers.delete(e.pointerId);\n pinchDistance = pointers.size === 2 ? distanceBetweenPointers() : 0;\n const remaining = pointers.values().next().value as { x: number; y: number } | undefined;\n dragging = pointers.size === 1;\n if (remaining) { lastX = remaining.x; lastY = remaining.y; }\n else pano.style.cursor = 'grab';\n try { pano.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n setViewFov(viewFov + e.deltaY * 0.04);\n };\n pano.addEventListener('pointerdown', onDown);\n pano.addEventListener('pointermove', onMove);\n pano.addEventListener('pointerup', onUp);\n pano.addEventListener('pointercancel', onUp);\n pano.addEventListener('wheel', onWheel, { passive: false });\n\n let closed = false;\n let disposed = false;\n let fadeTimer = 0;\n const removeListeners = (): void => {\n pano.removeEventListener('pointerdown', onDown);\n pano.removeEventListener('pointermove', onMove);\n pano.removeEventListener('pointerup', onUp);\n pano.removeEventListener('pointercancel', onUp);\n pano.removeEventListener('wheel', onWheel);\n window.removeEventListener('keydown', onKey);\n };\n const teardown = (): void => {\n cancelUpgrade();\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n if (fadeTimer) { window.clearTimeout(fadeTimer); fadeTimer = 0; }\n removeListeners();\n if (root.parentNode) root.parentNode.removeChild(root);\n if (priorFocus?.isConnected) priorFocus.focus({ preventScroll: true });\n };\n const close = (): void => {\n if (closed) return;\n closed = true;\n root.style.opacity = '0';\n // Guard the fade callback: a dispose() (or a retarget that disposes us) inside\n // the fade window clears the timer AND flips `disposed`, so a stray fire can\n // never call onClose into a newer flight/panorama.\n const done = (): void => {\n fadeTimer = 0;\n if (disposed) return;\n teardown();\n opts.onClose?.();\n };\n fadeTimer = window.setTimeout(done, fadeMs);\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === 'Tab') { e.preventDefault(); closeBtn.focus({ preventScroll: true }); return; }\n if (e.key === 'Escape') { e.stopPropagation(); close(); return; }\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n posX += e.key === 'ArrowLeft' ? -32 : 32;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n pitchPx += (e.key === 'ArrowUp' ? 4 : -4) * (bgH / 180);\n } else if (e.key === '+' || e.key === '=') {\n setViewFov(viewFov - 5);\n e.preventDefault();\n return;\n } else if (e.key === '-') {\n setViewFov(viewFov + 5);\n e.preventDefault();\n return;\n } else {\n return;\n }\n e.preventDefault();\n applyPos();\n };\n window.addEventListener('keydown', onKey);\n closeBtn.addEventListener('click', close);\n\n // Fade in on the next frame (0 → 1).\n requestAnimationFrame(() => { root.style.opacity = '1'; });\n\n return {\n close,\n dispose(): void { closed = true; disposed = true; teardown(); },\n };\n}\n","/**\n * The equirectangular panorama, as real geometry inside the venue scene.\n *\n * What this replaces, and why it had to change:\n *\n * `crossfade/panorama.ts` draws the 360 as a DOM div with `background-repeat:\n * repeat-x`, panned by hand and windowed to a 70° vertical slice. To show it,\n * `openPanorama` calls `loop.stop()` — it FREEZES the WebGL venue and fades a\n * flat image over the corpse. So the immersive payoff of the whole 3D flight is\n * the moment the product stops using its renderer.\n *\n * A `repeat-x` background is also not a sphere. It is a cylinder at best: pan is\n * linear in longitude (correct), but pitch is a vertical translation of a flat\n * bitmap, so verticals bow and the image stretches away from the horizon. A real\n * 360 photo looks right in the 2D popover — which uses an actual sphere via\n * Photo Sphere Viewer — and subtly wrong here, on the surface the marketing\n * story leads with.\n *\n * The fix is not to import Photo Sphere Viewer. That is Three.js-based, and this\n * chunk already owns a WebGL stack (OGL) and is deliberately kept at ~71 KB\n * gzipped because it loads only when a buyer opens 3D. Pulling in a second\n * renderer to draw one textured sphere would roughly triple it.\n *\n * So: a unit sphere, inward-facing, sampled equirectangularly, drawn by the\n * scene's own renderer with the scene's own camera. The panorama stops being an\n * overlay and becomes somewhere the camera can be, which is also what makes the\n * fly-to-seat handoff a continuous move rather than a cut between two different\n * rendering technologies.\n *\n * Everything below the `mount` function is pure and unit-tested; the GL parts\n * are a thin wrapper over it.\n */\n\nimport { Geometry, Mesh, Program, Texture } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\n\n/**\n * Longitude/latitude tessellation of the sphere.\n *\n * The sphere is viewed from its exact centre, so silhouette smoothness never\n * matters — only how much the equirect sampling is distorted by interpolating\n * UVs across a triangle. 64×32 keeps the worst-case angular error under a third\n * of a degree at the horizon, which is far below a pixel at any sane FOV, for\n * 4,096 triangles. Going finer costs vertices to fix an error nothing can see.\n */\nexport const PANO_SEGMENTS_LON = 64;\nexport const PANO_SEGMENTS_LAT = 32;\n\n/**\n * Radius in metres. Arbitrary in principle — the camera sits at the centre and\n * an equirect sphere has no parallax — but it must be large enough to enclose\n * the near clip plane and small enough to stay inside the far plane, or the\n * panorama clips against its own geometry. 500 m clears both for every venue in\n * the catalog, including the 50k-seat stadium.\n */\nexport const PANO_RADIUS_M = 500;\n\nexport interface SphereVertexData {\n position: Float32Array;\n uv: Float32Array;\n index: Uint16Array;\n}\n\n/**\n * A UV sphere with INWARD-facing triangles and equirectangular UVs.\n *\n * Inward-facing is done by winding, not by disabling culling: the scene draws\n * with back-face culling on, and turning it off for one mesh would leak that\n * state onto everything drawn after it in the same pass. Reversing the triangle\n * winding makes the inside surface the front face, so the sphere obeys exactly\n * the same pipeline state as the venue.\n *\n * Mapping, matching `generatePanorama.ts` and the 2D viewer so one image reads\n * identically in all three:\n * u = 0.5 + yaw/360 (u 0.5 is yaw 0, the direction the camera faces)\n * v = (90 - pitch)/180 (v 0 is the top of the image)\n *\n * The seam at u=0/1 is duplicated rather than wrapped — a shared vertex would\n * have to carry both u=0 and u=1 and would interpolate the whole image backwards\n * across that one column of triangles.\n */\nexport function buildPanoSphere(\n segmentsLon: number = PANO_SEGMENTS_LON,\n segmentsLat: number = PANO_SEGMENTS_LAT,\n radius: number = PANO_RADIUS_M,\n): SphereVertexData {\n const lonCount = segmentsLon + 1; // +1 duplicates the seam column\n const latCount = segmentsLat + 1; // +1 closes both poles\n const vertexCount = lonCount * latCount;\n const position = new Float32Array(vertexCount * 3);\n const uv = new Float32Array(vertexCount * 2);\n\n for (let iLat = 0; iLat < latCount; iLat++) {\n // v runs 0 (top) → 1 (bottom); polar angle 0 at +Y.\n const v = iLat / segmentsLat;\n const polar = v * Math.PI;\n const sinPolar = Math.sin(polar);\n const cosPolar = Math.cos(polar);\n for (let iLon = 0; iLon < lonCount; iLon++) {\n const u = iLon / segmentsLon;\n // u 0.5 must land on yaw 0. The scene's yaw 0 faces -Z (the camera looks\n // down -Z by default), so offset the azimuth by half a turn.\n const azimuth = (u - 0.5) * Math.PI * 2;\n const i = iLat * lonCount + iLon;\n position[i * 3] = radius * sinPolar * Math.sin(azimuth);\n position[i * 3 + 1] = radius * cosPolar;\n position[i * 3 + 2] = -radius * sinPolar * Math.cos(azimuth);\n uv[i * 2] = u;\n uv[i * 2 + 1] = v;\n }\n }\n\n // Two triangles per quad, wound so the INSIDE is the front face.\n const index = new Uint16Array(segmentsLon * segmentsLat * 6);\n let w = 0;\n for (let iLat = 0; iLat < segmentsLat; iLat++) {\n for (let iLon = 0; iLon < segmentsLon; iLon++) {\n const a = iLat * lonCount + iLon;\n const b = a + lonCount;\n index[w++] = a; index[w++] = b; index[w++] = a + 1;\n index[w++] = a + 1; index[w++] = b; index[w++] = b + 1;\n }\n }\n return { position, uv, index };\n}\n\n/**\n * Direction (unit vector, scene axes) the camera looks along for a given\n * bearing and pitch in degrees. Bearing 0 faces the focal/stage, matching\n * `SeatView.initialBearingDeg`; positive pitch looks up.\n *\n * Exported because the transition from the flight has to hand the sphere the\n * camera's final yaw, and a panorama that opens on a different bearing than the\n * flight ended on reads as a cut.\n */\nexport function bearingPitchToDirection(bearingDeg: number, pitchDeg: number): [number, number, number] {\n const yaw = (bearingDeg * Math.PI) / 180;\n const pitch = (pitchDeg * Math.PI) / 180;\n const cosPitch = Math.cos(pitch);\n return [cosPitch * Math.sin(yaw), Math.sin(pitch), -cosPitch * Math.cos(yaw)];\n}\n\nconst PANO_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nin vec2 uv;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst PANO_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform sampler2D uPano;\nuniform float uOpacity;\nout vec4 fragColor;\nvoid main() {\n vec3 rgb = texture(uPano, vUv).rgb;\n fragColor = vec4(rgb, uOpacity);\n}`;\n\nexport interface PanoSphereHandle {\n mesh: Mesh;\n /** Cross-fade control: 0 hides the sphere, 1 fully replaces the venue. */\n setOpacity(value: number): void;\n /** Replace the decoded source on the existing GPU texture. */\n setImage(image: HTMLImageElement | HTMLCanvasElement): void;\n dispose(): void;\n}\n\n/**\n * Build the textured sphere mesh. The caller owns adding it to a scene graph and\n * driving `setOpacity` — this module deliberately knows nothing about the flight\n * or the overlay chrome.\n *\n * `depthTest: false` and `depthWrite: false`: the sphere encloses the entire\n * venue, so with depth on it would be occluded by every piece of geometry\n * between the camera and its far wall. It is a backdrop that replaces the view,\n * not a solid in it — so it is drawn without consulting depth, and the caller\n * draws it after the venue when fading in.\n */\nexport function createPanoSphere(\n gl: OGLRenderingContext,\n // Narrower than `TexImageSource` on purpose: the panorama always arrives as a\n // decoded <img> (a URL or the generator's data URL) or a canvas. OGL's texture\n // type does not accept ImageBitmap, and widening here would only move the\n // failure to runtime.\n image: HTMLImageElement | HTMLCanvasElement,\n): PanoSphereHandle {\n const { position, uv, index } = buildPanoSphere();\n const geometry = new Geometry(gl, {\n position: { size: 3, data: position },\n uv: { size: 2, data: uv },\n index: { data: index },\n });\n const texture = new Texture(gl, {\n image,\n // The seam column is duplicated in geometry, so CLAMP is correct and avoids\n // a wrapped bilinear tap bleeding the far edge of the image into it.\n wrapS: gl.CLAMP_TO_EDGE,\n wrapT: gl.CLAMP_TO_EDGE,\n generateMipmaps: true,\n // MUST be false, and this is the one line that decides whether the whole\n // panorama is upside down.\n //\n // OGL defaults `flipY` to true for 2D textures, which is right for the usual\n // case: a UV of 0 means the BOTTOM of a quad, an image's first row is its\n // TOP, and flipping on upload reconciles the two. This sphere is the other\n // case. Its `v` is authored in IMAGE space — v=0 is the top of the equirect\n // and maps to the top of the sphere (`buildPanoSphere`, and the mapping\n // `generatePanorama` and the 2D viewer both use). Flipping on upload as well\n // applies the correction twice: the stadium pitch renders on the ceiling and\n // the audience sits overhead.\n //\n // The geometry tests state the v mapping and pass either way — they never\n // touch GL — so this is not something a unit test can defend. It was caught\n // by looking at a stadium.\n flipY: false,\n });\n const program = new Program(gl, {\n vertex: PANO_VERT,\n fragment: PANO_FRAG,\n uniforms: { uPano: { value: texture }, uOpacity: { value: 0 } },\n transparent: true,\n depthTest: false,\n depthWrite: false,\n });\n const mesh = new Mesh(gl, { geometry, program });\n return {\n mesh,\n setOpacity(value: number): void {\n program.uniforms.uOpacity.value = Math.max(0, Math.min(1, value));\n },\n setImage(nextImage: HTMLImageElement | HTMLCanvasElement): void {\n texture.image = nextImage;\n texture.needsUpdate = true;\n },\n dispose(): void {\n mesh.setParent(null);\n geometry.remove();\n if (texture.texture) gl.deleteTexture(texture.texture);\n program.remove();\n },\n };\n}\n","/**\n * The panorama, mounted as scene geometry instead of as a div over a corpse.\n *\n * `panorama.ts` (still here, still used as the fallback) freezes the render loop\n * and fades a `repeat-x` bitmap over the last WebGL frame. This mounts the same\n * image on the inward-facing sphere from `scene/panoSphere.ts`, leaves the loop\n * running, and moves the camera instead of a background-position. Three things\n * follow from that, and they are the reason for the rewrite:\n *\n * - It is a real sphere. Pitch is a camera rotation, not a vertical translation\n * of a flat bitmap, so verticals stay vertical and nothing stretches away\n * from the horizon.\n * - The venue is still being drawn underneath, so the arrival is a CROSS-FADE\n * between two views of the same place from the same camera, not a cut\n * between two rendering technologies.\n * - Escape/close costs nothing to reverse: the camera is already where it needs\n * to be, because it never went anywhere.\n *\n * The DOM chrome (close button, hint, dialog semantics) is kept deliberately —\n * it is the accessible surface, and none of it belongs in GL.\n */\n\nimport { createPanoSphere, bearingPitchToDirection, type PanoSphereHandle } from '../scene/panoSphere';\nimport type { PanoramaHandle, PanoramaOptions, SeatView } from './panorama';\nimport { clampPanoramaFov, MAX_PITCH_DEG, VFOV_DEG } from './panorama';\nimport type { OGLRenderingContext, Transform, Camera } from 'ogl';\nimport {\n browserPanoramaConstraints,\n loadPanoramaImage,\n planPanoramaDelivery,\n schedulePanoramaUpgrade,\n} from '../../view/panoramaDelivery';\n\n/**\n * Degrees of look per pixel dragged. Matched to the DOM viewer's feel: a drag\n * across the full width of a 1200 px viewport turns roughly 180°.\n */\nconst DEG_PER_PX = 0.15;\n\nexport interface PanoramaSceneDeps {\n gl: OGLRenderingContext;\n /** The scene the venue is drawn into; the sphere joins it and leaves on close. */\n scene: Transform;\n camera: Camera;\n /** Ask for another frame — the loop is render-on-demand, not free-running. */\n requestRender(): void;\n /**\n * Exact world-space eye point for scene mode. The camera is moved here while\n * the buyer looks around, then restored to the cinematic arrival on close.\n */\n cameraOriginWorld?: readonly [number, number, number];\n /**\n * The venue focal (stage/pitch centre) in world metres.\n *\n * Only scene mode needs it, and it genuinely cannot do without it. A generated\n * panorama is DRAWN with the stage at bearing 0, so opening at bearing 0 faces\n * the stage for free. The real scene has no such convention — bearing 0 is\n * world −Z, which points wherever the venue happened to be authored — so\n * opening there stares at whatever is behind the buyer. The bearing is derived\n * from the seat's own line to the focal instead.\n */\n focalWorld?: readonly [number, number, number];\n}\n\n/**\n * Mount the panorama sphere. Returns null if the image cannot be decoded, so the\n * caller can fall back to the DOM viewer rather than stranding the buyer on a\n * black screen — a 360 that fails to load must never be worse than no 360.\n */\nexport async function mountPanoramaSphere(\n container: HTMLElement,\n // NULL means \"look around the real scene\": no image, no sphere, just the\n // camera at the seat. That is the right answer for a generated panorama,\n // which is only ever a low-resolution picture of the geometry already on\n // screen — see `SeatView.generated`. Everything else below (framing,\n // look-around, Escape, restoring the camera) is identical, which is why the\n // two modes share one code path rather than diverging into two viewers that\n // drift apart.\n view: SeatView | null,\n deps: PanoramaSceneDeps,\n opts: PanoramaOptions = {},\n): Promise<PanoramaHandle | null> {\n const fadeMs = opts.fadeMs ?? 400;\n let bearing = view?.initialBearingDeg ?? 0;\n let pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, view?.initialPitchDeg ?? 0));\n let viewFov = VFOV_DEG;\n let disposed = false;\n const loadAbort = new AbortController();\n const abortFromCaller = (): void => loadAbort.abort();\n opts.signal?.addEventListener('abort', abortFromCaller, { once: true });\n let cancelUpgrade = (): void => {};\n\n // Scene mode is the real venue viewed from the selected seat, not from the\n // deliberately flattering cinematic arrival behind and above it. Preserve\n // that arrival so closing 360 returns to the exact pre-view camera.\n const priorPosition: [number, number, number] = [\n deps.camera.position.x,\n deps.camera.position.y,\n deps.camera.position.z,\n ];\n if (!view && deps.cameraOriginWorld) {\n deps.camera.position.set(\n deps.cameraOriginWorld[0],\n deps.cameraOriginWorld[1],\n deps.cameraOriginWorld[2],\n );\n }\n\n // Scene mode opens on the stage, derived from where this seat actually is.\n // The pitch matters as much as the yaw: from a balcony the stage is well below\n // eye level, and opening level puts the whole venue in the bottom of the frame\n // with dead air above it.\n if (!view && deps.focalWorld) {\n const p = deps.camera.position;\n const dx = deps.focalWorld[0] - p.x;\n const dy = deps.focalWorld[1] - p.y;\n const dz = deps.focalWorld[2] - p.z;\n const flat = Math.hypot(dx, dz);\n if (flat > 1e-3) {\n bearing = (Math.atan2(dx, -dz) * 180) / Math.PI;\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, (Math.atan2(dy, flat) * 180) / Math.PI));\n }\n }\n\n let sphere: PanoSphereHandle | null = null;\n if (view) {\n const maxTextureSize = deps.gl.getParameter(deps.gl.MAX_TEXTURE_SIZE) as number;\n const delivery = planPanoramaDelivery(view, browserPanoramaConstraints(maxTextureSize));\n let image: HTMLImageElement;\n try {\n const initialUrl = view.resolveUrl\n ? await view.resolveUrl(delivery.initialUrl)\n : delivery.initialUrl;\n if (!initialUrl) throw new Error('panorama_asset_unavailable');\n image = await loadPanoramaImage(initialUrl, loadAbort.signal);\n } catch {\n opts.signal?.removeEventListener('abort', abortFromCaller);\n return null;\n }\n try {\n sphere = createPanoSphere(deps.gl, image);\n } catch {\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n return null;\n }\n // Drawn last and without depth, so it composites over the venue during the\n // fade instead of being occluded by whatever sits between the camera and\n // the far wall of the sphere.\n sphere.mesh.renderOrder = 999;\n sphere.mesh.setParent(deps.scene);\n if (delivery.upgradeUrl) {\n cancelUpgrade = schedulePanoramaUpgrade(() => {\n const resolved = view.resolveUrl\n ? view.resolveUrl(delivery.upgradeUrl!)\n : Promise.resolve(delivery.upgradeUrl!);\n void resolved.then((url) => {\n if (!url || loadAbort.signal.aborted) return null;\n return loadPanoramaImage(url, loadAbort.signal);\n }).then((full) => {\n if (!full) return;\n if (disposed || loadAbort.signal.aborted) return;\n sphere?.setImage(full);\n deps.requestRender();\n }).catch(() => { /* retain the decoded preview */ });\n });\n }\n }\n\n // A cinematic flight ENDS pushed in — it narrows the FOV to close the last of\n // the distance to the seat, and `resumeAfterFlight` is what normally restores\n // it. The DOM viewer never noticed because it never used the camera; drawing\n // the panorama through that same camera inherits the zoom and magnifies the\n // equirect to a few unreadable degrees of arc.\n //\n // So the sphere sets its own field of view — the same 70° the DOM viewer\n // windowed to, which is what makes the two paths frame a panorama alike — and\n // puts back whatever it found on the way out, so closing hands the flight's\n // camera back exactly as it was.\n const priorFov = deps.camera.fov;\n deps.camera.perspective({ fov: viewFov, aspect: deps.camera.aspect });\n\n // Looking around REPLACES the camera's orientation, and orbit only re-aims on\n // a frame it considers \"moving\". So closing the panorama used to hand back a\n // camera still pointing wherever the buyer had dragged, at a venue that was no\n // longer in front of it — a black screen until the first orbit drag snapped it\n // home. Capture the orientation on the way in and put it back on the way out,\n // exactly as the FOV is handled.\n const priorQuat = deps.camera.quaternion.slice() as unknown as number[];\n\n /** Keep the camera at the sphere's exact centre — an equirect has no parallax,\n * and any offset would shear the image. */\n const aim = (): void => {\n const p = deps.camera.position;\n sphere?.mesh.position.set(p.x, p.y, p.z);\n const [dx, dy, dz] = bearingPitchToDirection(bearing, pitch);\n deps.camera.lookAt([p.x + dx, p.y + dy, p.z + dz]);\n deps.requestRender();\n };\n aim();\n\n // --- chrome -------------------------------------------------------------\n const root = document.createElement('div');\n const priorFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-label', opts.seatLabel ? `View from ${opts.seatLabel}` : 'View from seat');\n root.tabIndex = -1;\n Object.assign(root.style, {\n position: 'absolute', inset: '0', zIndex: '10',\n cursor: 'grab', touchAction: 'none',\n } as CSSStyleDeclaration);\n\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.textContent = '✕';\n Object.assign(closeBtn.style, {\n position: 'absolute', top: '12px', right: '12px', zIndex: '2',\n width: '44px', height: '44px', borderRadius: '999px', cursor: 'pointer',\n border: '1px solid rgba(255,255,255,0.25)', background: 'rgba(8,12,18,0.6)',\n color: '#e6edf3', fontSize: '15px', lineHeight: '1',\n } as CSSStyleDeclaration);\n root.appendChild(closeBtn);\n\n if (opts.disclosure) {\n const disclosure = document.createElement('div');\n disclosure.textContent = `${opts.disclosurePrefix ?? '360° panorama'} · ${opts.disclosure}`;\n Object.assign(disclosure.style, {\n position: 'absolute', top: '12px', left: '12px', zIndex: '2',\n maxWidth: 'calc(100% - 88px)', padding: '8px 11px', borderRadius: '999px',\n overflow: 'hidden', color: '#dce6f8', background: 'rgba(8,12,18,0.68)',\n font: '600 11px/1.2 ui-sans-serif, system-ui, sans-serif', textOverflow: 'ellipsis',\n whiteSpace: 'nowrap', pointerEvents: 'none', backdropFilter: 'blur(6px)',\n } as CSSStyleDeclaration);\n root.appendChild(disclosure);\n }\n\n const hint = document.createElement('div');\n hint.textContent = 'Drag to look · pinch or scroll to zoom · Esc to close';\n Object.assign(hint.style, {\n position: 'absolute', bottom: '12px', left: '0', right: '0', textAlign: 'center',\n color: 'rgba(230,237,243,0.7)', font: '12px ui-sans-serif, system-ui, sans-serif',\n pointerEvents: 'none',\n } as CSSStyleDeclaration);\n root.appendChild(hint);\n container.appendChild(root);\n closeBtn.focus({ preventScroll: true });\n\n // --- look-around --------------------------------------------------------\n let dragging = false;\n let lastX = 0;\n let lastY = 0;\n let pinchDistance = 0;\n const pointers = new Map<number, { x: number; y: number }>();\n const distanceBetweenPointers = (): number => {\n const [a, b] = [...pointers.values()];\n return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;\n };\n const setViewFov = (nextFov: number): void => {\n const clamped = clampPanoramaFov(nextFov);\n if (clamped === viewFov) return;\n viewFov = clamped;\n deps.camera.perspective({ fov: viewFov, aspect: deps.camera.aspect });\n aim();\n };\n const onDown = (e: PointerEvent): void => {\n if (e.target === closeBtn) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n dragging = pointers.size === 1;\n lastX = e.clientX; lastY = e.clientY;\n if (pointers.size === 2) pinchDistance = distanceBetweenPointers();\n root.setPointerCapture?.(e.pointerId);\n root.style.cursor = 'grabbing';\n };\n const onMove = (e: PointerEvent): void => {\n if (!pointers.has(e.pointerId)) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (pointers.size >= 2) {\n const nextDistance = distanceBetweenPointers();\n if (pinchDistance > 0 && nextDistance > 0) setViewFov(viewFov * (pinchDistance / nextDistance));\n pinchDistance = nextDistance;\n dragging = false;\n return;\n }\n if (!dragging) return;\n // Horizontal follows the HAND: drag right, turn right. The vertical below\n // stays grab-style (drag down to look up), which is the mix the owner asked\n // for after using it — and worth stating because it looks inconsistent in\n // the source and is not. The DOM viewer this replaces dragged the image on\n // both axes; the yaw was the one that read backwards in a real venue.\n bearing += (e.clientX - lastX) * DEG_PER_PX;\n // Clamped to the same ±35° the DOM viewer allowed. A generated panorama has\n // nothing meaningful at the poles, and letting the buyer tumble to straight\n // up reads as a broken control rather than a feature.\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, pitch + (e.clientY - lastY) * DEG_PER_PX));\n lastX = e.clientX; lastY = e.clientY;\n aim();\n };\n const onUp = (e: PointerEvent): void => {\n pointers.delete(e.pointerId);\n pinchDistance = pointers.size === 2 ? distanceBetweenPointers() : 0;\n const remaining = pointers.values().next().value as { x: number; y: number } | undefined;\n dragging = pointers.size === 1;\n if (remaining) { lastX = remaining.x; lastY = remaining.y; }\n else root.style.cursor = 'grab';\n try { root.releasePointerCapture?.(e.pointerId); } catch { /* pointer already released */ }\n };\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n setViewFov(viewFov + e.deltaY * 0.04);\n };\n root.addEventListener('pointerdown', onDown);\n root.addEventListener('pointermove', onMove);\n root.addEventListener('pointerup', onUp);\n root.addEventListener('pointercancel', onUp);\n root.addEventListener('wheel', onWheel, { passive: false });\n\n // Escape closes. On `window`, not on `root`, because the hint promises it\n // unconditionally and a div only receives keys while focused — the DOM viewer\n // this replaces bound it the same way, and dropping it made the hint lie.\n // `stopPropagation` so the host page's own Escape handling (closing a modal,\n // leaving 3D) does not also fire on the same press.\n const onKey = (e: KeyboardEvent): void => {\n if (disposed) return;\n if (e.key === 'Tab') {\n e.preventDefault();\n closeBtn.focus({ preventScroll: true });\n return;\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n handle.close();\n return;\n }\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n bearing += e.key === 'ArrowLeft' ? -4 : 4;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, pitch + (e.key === 'ArrowUp' ? 4 : -4)));\n } else if (e.key === '+' || e.key === '=') {\n setViewFov(viewFov - 5);\n e.preventDefault();\n return;\n } else if (e.key === '-') {\n setViewFov(viewFov + 5);\n e.preventDefault();\n return;\n } else {\n return;\n }\n e.preventDefault();\n aim();\n };\n window.addEventListener('keydown', onKey);\n\n // --- fade ---------------------------------------------------------------\n let raf = 0;\n const fadeTo = (target: number, done?: () => void): void => {\n // Scene mode has nothing to fade — the buyer is already looking at the\n // venue — so the transition is the FOV widening, not a dissolve.\n if (!sphere) { done?.(); return; }\n const from = target === 1 ? 0 : 1;\n const start = performance.now();\n const step = (): void => {\n if (disposed) return;\n const t = fadeMs <= 0 ? 1 : Math.min(1, (performance.now() - start) / fadeMs);\n sphere?.setOpacity(from + (target - from) * t);\n deps.requestRender();\n if (t < 1) raf = requestAnimationFrame(step);\n else done?.();\n };\n raf = requestAnimationFrame(step);\n };\n fadeTo(1);\n\n const teardown = (): void => {\n if (disposed) return;\n disposed = true;\n cancelUpgrade();\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n cancelAnimationFrame(raf);\n root.removeEventListener('pointerdown', onDown);\n root.removeEventListener('pointermove', onMove);\n root.removeEventListener('pointerup', onUp);\n root.removeEventListener('pointercancel', onUp);\n root.removeEventListener('wheel', onWheel);\n window.removeEventListener('keydown', onKey);\n root.remove();\n if (priorFocus?.isConnected) priorFocus.focus({ preventScroll: true });\n sphere?.dispose();\n deps.camera.position.set(priorPosition[0], priorPosition[1], priorPosition[2]);\n deps.camera.perspective({ fov: priorFov, aspect: deps.camera.aspect });\n deps.camera.quaternion.set(priorQuat[0], priorQuat[1], priorQuat[2], priorQuat[3]);\n deps.requestRender();\n };\n\n const handle: PanoramaHandle = {\n close(): void {\n if (disposed) return;\n const finish = (): void => { teardown(); opts.onClose?.(); };\n cancelAnimationFrame(raf);\n fadeTo(0, finish);\n },\n dispose: teardown,\n };\n closeBtn.addEventListener('click', () => handle.close());\n return handle;\n}\n","/**\n * view3d analytics — a tiny, decoupled event emitter for the venue view. The\n * caller (app/harness) supplies `onAnalytics`; this class owns the per-mount\n * state (first-orbit latch, panorama dwell timing) and, crucially, wraps EVERY\n * callback invocation in try/catch so a throwing analytics sink can never break\n * rendering. No DOM, no GL — unit-testable in isolation.\n */\n\nexport type Analytics3DCallback = (event: string, props?: Record<string, unknown>) => void;\n\n/**\n * Where the CPU scene was compiled, as a closed enum (never free text).\n * - `worker` — built off the UI thread by `prepareVenue3D`.\n * - `main` — `prepareVenue3D` ran but the worker was unavailable or failed\n * (no `Worker`, a host CSP blocking the module worker, a throw, or\n * the 15s timeout) and the same pure compiler ran on the UI thread.\n * - `inline` — the caller never used `prepareVenue3D`; the scene was compiled\n * synchronously inside `mountVenue3D`. Kept distinct from `main`\n * so worker uptake is not diluted by callers that never asked.\n */\nexport type Scene3DPrepSource = 'worker' | 'main' | 'inline';\n\nconst now = (): number =>\n (typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now());\n\nexport class Analytics3D {\n private cb?: Analytics3DCallback;\n private orbitLatched = false;\n private panoramaOpenedAt = 0;\n\n constructor(cb?: Analytics3DCallback) {\n this.cb = cb;\n }\n\n /** The single guarded emit point — analytics must never throw into the loop. */\n private emit(event: string, props?: Record<string, unknown>): void {\n if (!this.cb) return;\n try {\n this.cb(event, props);\n } catch {\n /* analytics sink threw — swallow so rendering is never affected */\n }\n }\n\n opened(\n seats: number,\n hasHeights: boolean,\n buildMs?: number,\n prepSource?: Scene3DPrepSource,\n ): void {\n this.emit('3d_opened', {\n seats,\n hasHeights,\n ...(buildMs === undefined ? {} : { buildMs: Math.round(buildMs) }),\n ...(prepSource === undefined ? {} : { prepSource }),\n });\n }\n\n /** First user-driven orbit/dolly per mount only (the intro ease is not user\n * input, so callers must gate this on real pointer/wheel gestures). */\n orbitEngaged(): void {\n if (this.orbitLatched) return;\n this.orbitLatched = true;\n this.emit('3d_orbit_engaged');\n }\n\n seatPicked(seatId: string, sectionId: string | undefined): void {\n this.emit('3d_seat_picked', { seatId, sectionId });\n }\n\n cinematicPlayed(durationMs: number): void {\n this.emit('3d_cinematic_played', { durationMs, reducedMotion: false });\n }\n\n cinematicSkipped(): void {\n this.emit('3d_cinematic_skipped', { reducedMotion: true });\n }\n\n cinematicCancelled(): void {\n this.emit('3d_cinematic_cancelled');\n }\n\n panoramaOpened(): void {\n this.panoramaOpenedAt = now();\n this.emit('3d_panorama_opened');\n }\n\n panoramaClosed(): void {\n const viewMs = this.panoramaOpenedAt ? Math.round(now() - this.panoramaOpenedAt) : 0;\n this.panoramaOpenedAt = 0;\n this.emit('3d_panorama_closed', { viewMs });\n }\n\n panoramaFailed(seatId: string, phase: 'source' | 'mount'): void {\n this.emit('3d_panorama_failed', { seatId, phase });\n }\n\n panoramaFallback(seatId: string): void {\n this.emit('3d_panorama_fallback', { seatId });\n }\n\n contextLost(): void {\n this.emit('3d_context_lost');\n }\n\n contextRestored(): void {\n this.emit('3d_context_restored');\n }\n}\n","/**\n * Establish a positioning context only when the host is genuinely static.\n *\n * Buyer overlays are positioned by stylesheet rules. Checking only the inline\n * style would overwrite `position:absolute` and collapse the 3D canvas.\n */\nexport function establishPositioningContext(container: HTMLElement): () => void {\n const originalInline = container.style.position;\n if (getComputedStyle(container).position !== 'static') return () => {};\n\n container.style.position = 'relative';\n return () => {\n // Preserve any host change made while the renderer was mounted.\n if (container.style.position === 'relative') container.style.position = originalInline;\n };\n}\n","import type { ChartDoc, ExpandedSeat } from '../core/types';\nimport { buildSceneModel, type SceneModel } from './scene/sceneModel';\nimport type { Scene3DPrepSource } from './analytics';\n\nexport interface PreparedVenue3D {\n model: SceneModel;\n buildMs: number;\n /** Reported on `3d_opened` as `prepSource`. `inline` is not reachable here —\n * it is what `mountVenue3D` reports when no prepared scene was handed in. */\n source: Exclude<Scene3DPrepSource, 'inline'>;\n}\n\nexport interface PrepareVenue3DInput {\n doc: ChartDoc;\n seats: ExpandedSeat[];\n eventConfigurationId?: string;\n}\n\nfunction prepareOnMain(input: PrepareVenue3DInput): PreparedVenue3D {\n const started = performance.now();\n const model = buildSceneModel(input);\n return { model, buildMs: performance.now() - started, source: 'main' };\n}\n\n/**\n * Build the CPU-heavy merged mesh and seat buffers off the UI thread. Worker\n * failure is non-fatal: old browsers and restrictive hosts fall back to the\n * exact same pure compiler on the main thread.\n */\nexport function prepareVenue3D(input: PrepareVenue3DInput): Promise<PreparedVenue3D> {\n if (typeof Worker === 'undefined') return Promise.resolve(prepareOnMain(input));\n\n return new Promise((resolve) => {\n const worker = new Worker(new URL('./scene/scene.worker.ts', import.meta.url), { type: 'module' });\n let settled = false;\n const finish = (result: PreparedVenue3D): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n worker.terminate();\n resolve(result);\n };\n const fallback = (): void => finish(prepareOnMain(input));\n const timeout = window.setTimeout(fallback, 15_000);\n worker.onerror = fallback;\n worker.onmessage = (event: MessageEvent<{ model: SceneModel; buildMs: number }>) => {\n finish({ model: event.data.model, buildMs: event.data.buildMs, source: 'worker' });\n };\n worker.postMessage(input);\n });\n}\n"],"mappings":"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","names":["import_ogl","import_ogl","norm","now","earcut","scale","polygonClipping","scale","import_polygon_clipping","DEG","import_polygon_clipping","import_earcut","out","probesOf","polygonClipping","poly","earcut","belowY","pointInRing","polygonClipping","s","import_ogl","import_ogl","sub","import_ogl","import_ogl","now","id","import_ogl","DEG","moved"]}
|
|
1
|
+
{"version":3,"sources":["../../src/view3d/index.ts","../../src/view3d/gl/context.ts","../../src/view3d/palette.ts","../../src/view3d/camera/orbit.ts","../../src/view3d/loop.ts","../../src/view3d/lod.ts","../../src/view3d/scene/nearField.ts","../../src/core/types.ts","../../src/core/units.ts","../../src/view3d/scene/geometry.ts","../../src/view3d/scene/seatChair.ts","../../src/view3d/scene/seatInstances.ts","../../src/view3d/theme.ts","../../src/view3d/labels.ts","../../src/view3d/scene/sceneModel.ts","../../src/core/venueStructure.ts","../../src/core/layout.ts","../../src/core/rake.ts","../../src/view3d/scene/deckBands.ts","../../src/view3d/scene/surface.ts","../../src/view3d/labelOverlay.ts","../../src/view3d/scene/build.ts","../../src/view3d/scene/materials.ts","../../src/view3d/pick/pickPipeline.ts","../../src/view3d/pick/encode.ts","../../src/view3d/pick/selection.ts","../../src/view3d/camera/cinematic.ts","../../src/view3d/camera/cinematicMath.ts","../../src/view3d/camera/seatViewPose.ts","../../src/view/panoramaDelivery.ts","../../src/view3d/crossfade/panorama.ts","../../src/view3d/scene/panoSphere.ts","../../src/view3d/crossfade/panoramaSphere.ts","../../src/view3d/analytics.ts","../../src/view3d/positioningContext.ts","../../src/view3d/prepareScene.ts"],"sourcesContent":["/**\n * view3d — the sole dynamic-import boundary for the lazy OGL venue-view chunk.\n *\n * const { mountVenue3D } = await import('../view3d');\n * const handle = mountVenue3D(container, { doc, seats }, { onSeatPick, getSeatView });\n * await handle.flyToSeat(seatId);\n *\n * Read-only 3D of any chart, fed entirely from the existing height contract.\n * Slice 1: orbit camera, extruded tiers/stage/GA, instanced seat dots, sub-range\n * availability, dispose + context-loss survival. Slice 2: GPU color-pick. Slice\n * 3: the fly-to-seat cinematic that dissolves into the view-from-seat panorama.\n */\n\nimport { Quat, Vec3 } from 'ogl';\nimport type { ChartDoc, ExpandedSeat } from '../core/types';\nimport { GLContext } from './gl/context';\nimport { OrbitCamera } from './camera/orbit';\nimport { RenderLoop, type RenderLoopStats } from './loop';\nimport { computeSeatLod, CHAIR_GATHER_M, CHAIR_MAX_INSTANCES, CHAIR_REBUILD_M } from './lod';\nimport { NearFieldIndex } from './scene/nearField';\nimport { SEAT_DOT_RADIUS_M } from './scene/seatInstances';\nimport { buildSceneModel, type SceneModel, type SceneZone, type SceneSection, type SceneFloor, type SceneRow } from './scene/sceneModel';\nimport { LabelOverlay } from './labelOverlay';\nimport { projectToScreen } from './labels';\nimport { buildGpuScene, type GpuScene } from './scene/build';\nimport { applySeatStates } from './scene/seatInstances';\nimport { PickPipeline } from './pick/pickPipeline';\nimport { pickPixelCoords } from './pick/encode';\nimport { diffSelection, mergeAvailabilityIntoSelection } from './pick/selection';\nimport { Cinematic, buildWaypoints, lookAtQuat, FLIGHT_DURATION_MS, FOV_END, type Vec3Arr } from './camera/cinematic';\nimport { seatViewPose } from './camera/seatViewPose';\nimport { mountPanorama, seatViewDisclosure, type PanoramaHandle, type SeatView } from './crossfade/panorama';\nimport { mountPanoramaSphere } from './crossfade/panoramaSphere';\nimport { Analytics3D, type Analytics3DCallback } from './analytics';\nimport { SEAT_STATES, type SeatState3D } from './palette';\nimport { establishPositioningContext } from './positioningContext';\nimport type { PreparedVenue3D } from './prepareScene';\n\nexport { prepareVenue3D } from './prepareScene';\n\nexport type { SeatState3D } from './palette';\nexport type { SeatView } from './crossfade/panorama';\nexport type { Analytics3DCallback, Scene3DPrepSource } from './analytics';\nexport { buildSceneModel } from './scene/sceneModel';\n\nexport interface Venue3DInput {\n doc: ChartDoc;\n /** Expanded seats (from `expandChart`) — carry x/y + resolved eyeHeightM. */\n seats: ExpandedSeat[];\n /** Optional initial per-seat state (default all available). */\n initialState?: (seat: ExpandedSeat) => SeatState3D;\n /** CPU scene prepared by `prepareVenue3D`; avoids rebuilding on mount. */\n prepared?: PreparedVenue3D;\n}\n\nexport interface Venue3DOptions {\n /** Fired on a tap that hits a seat (GPU color-pick). Not fired on empty taps. */\n onSeatPick?: (seatId: string) => void;\n /** Fired when the buyer taps a visible seat that cannot currently be selected.\n * The host can explain held/sold/filter state without turning inspection into\n * selection or briefly painting the unavailable seat as the buyer's choice. */\n onSeatInspect?: (seatId: string, state: SeatState3D) => void;\n /** Overview → section drill state. A stand tap reports its owning section;\n * Overview reports null so host navigation can stay in sync. */\n onSectionFocusChange?: (sectionId: string | null) => void;\n /** Camera target changes, including internal next/previous and Overview UI. */\n onViewTargetChange?: (seatId: string | null) => void;\n /**\n * Supplies the view-from-seat panorama for the cinematic hand-off. Decoupled:\n * the caller (app/harness) owns panorama generation; view3d never imports it.\n * Called at PICK time to pre-render, so flyToSeat has zero wait on landing.\n */\n getSeatView?: (seatId: string) => SeatView | Promise<SeatView>;\n /** Buyer-facing action for entering the supplied seat view. The caller knows\n * whether that source is live geometry or an authored panorama. */\n seatViewActionLabel?: (seatId: string) => string;\n /** Land at the modeled seated-eye pose instead of the elevated inspection\n * anchor. Useful when live geometry is the primary buyer view. */\n arriveAtSeatEye?: boolean;\n /**\n * Decoupled analytics sink. Emits the venue-view journey: `3d_opened`,\n * `3d_orbit_engaged` (first user gesture), `3d_seat_picked`,\n * `3d_cinematic_played`/`_skipped`/`_cancelled`, `3d_panorama_opened`/`_closed`.\n * Every invocation is wrapped in try/catch — a throwing sink never breaks\n * rendering. Absent = no events emitted.\n */\n onAnalytics?: Analytics3DCallback;\n /**\n * Let portrait overview framing crop the venue's far edges within a bounded\n * landscape fit so seats stay discoverable instead of shrinking the whole\n * bowl to fit. Opt-in because designer/editor previews still prefer strict\n * contain framing; validated for the buyer across `/dev/3d` fixtures.\n */\n portraitOverviewCrop?: boolean;\n}\n\nexport interface Venue3DStats extends RenderLoopStats {\n drawCalls: number;\n seatCount: number;\n}\n\nexport interface Venue3DHandle {\n dispose(): void;\n setAvailability(updates: { seatId: string; state: SeatState3D }[]): void;\n setSelection(seatIds: string[]): void;\n /** Fly the camera from the overview into `seatId` and dissolve into its\n * view-from-seat panorama. Resolves at flight end; a drag cancels it, a second\n * call retargets, dispose resolves early. Reduced-motion → a short fade. */\n flyToSeat(seatId: string): Promise<void>;\n /** Cancel a seat flight/panorama and frame the complete venue. */\n focusOverview(): void;\n resize(): void;\n stats(): Venue3DStats;\n loseContextForTest(): void;\n /** Test hook: force (or clear) the reduced-motion path. */\n setReducedMotionForTest(value: boolean | null): void;\n /** The venue's zones (id, label, colour, seat count) in authored order. */\n zones(): SceneZone[];\n /**\n * Frame a zone: the camera moves to sit over that zone looking at what the\n * zone faces. Returns false for an unknown or empty zone.\n *\n * This is the navigation the venue's own structure implies — a buyer picks\n * \"Grand Circle\", not a set of coordinates — and it is what the 2D renderer's\n * farthest LOD rung already offers. Approaching from the zone's focal side\n * means the seats face the camera rather than presenting their backs.\n */\n focusZone(zoneId: string): boolean;\n /** The venue's seated sections (id, name, seat count) in authored order. */\n sections(): SceneSection[];\n /**\n * Frame a section — the middle rung of the venue ladder, and the one every\n * chart has. Zones are optional and a chart may author none or one; sections\n * are what a buyer picks between when \"which part of the venue\" has already\n * been answered. Returns false for an unknown or empty section.\n */\n focusSection(sectionId: string): boolean;\n /** Rows in the venue, optionally narrowed to a section. */\n rows(sectionId?: string): SceneRow[];\n /** Seats in one row, in authored order. */\n seatsInRow(rowId: string): Array<{ id: string; label: string }>;\n /** Frame a row closely enough for its on-demand seat numbers to be legible. */\n focusRow(rowId: string): boolean;\n /** The venue's floors (id, name, seat count) in authored order. */\n floors(): SceneFloor[];\n /**\n * Isolate one floor, or pass null to show the whole venue.\n *\n * Every shipped multi-floor chart puts its floors at the same base height and\n * takes relief from the sections, so all three of an opera house draw at once\n * and the balcony sits over the parterre. Unfocused floors are DIMMED rather\n * than hidden, so the buyer keeps the venue as context while looking at the\n * level they are booking. Returns false for an unknown index.\n */\n focusFloor(index: number | null): boolean;\n}\n\nconst DEG = Math.PI / 180;\nconst TAP_SLOP = 6;\nconst TAP_MS = 500;\n\nexport function mountVenue3D(\n container: HTMLElement,\n input: Venue3DInput,\n opts: Venue3DOptions = {},\n): Venue3DHandle {\n const buildStartedAt = performance.now();\n const model: SceneModel = input.prepared?.model ?? buildSceneModel(input);\n const analytics = new Analytics3D(opts.onAnalytics);\n\n const seatIdByIndex: string[] = new Array(model.seats.count);\n for (const [id, idx] of model.seats.idToIndex) seatIdByIndex[idx] = id;\n const expandedSeatById = new Map(input.seats.map((seat) => [seat.id, seat]));\n const seatIdsByRow = new Map<string, string[]>();\n for (const seat of input.seats) {\n if (!seat.rowId) continue;\n const row = seatIdsByRow.get(seat.rowId);\n if (row) row.push(seat.id);\n else seatIdsByRow.set(seat.rowId, [seat.id]);\n }\n\n // Seat → owning section (for the 3d_seat_picked event); resolved from the\n // expanded seats that already carry sectionId.\n const sectionIdBySeatId = new Map<string, string | undefined>();\n for (const s of input.seats) sectionIdBySeatId.set(s.id, s.sectionId);\n\n // Whether the chart carries any real 3D relief (authored heights/rake or\n // elevated floors) vs. degrading to flat slabs — reported with 3d_opened.\n const hasHeights = ((): boolean => {\n if (input.doc.floors?.some((f) => (f.baseHeightM ?? 0) > 0)) return true;\n const objs = input.doc.floors?.flatMap((f) => f.objects) ?? input.doc.objects;\n return objs.some((o) => o.type === 'section'\n && (((o as { height?: number }).height ?? 0) > 0 || ((o as { rake?: number }).rake ?? 0) > 0));\n })();\n\n let gpu: GpuScene | null = null;\n let pick: PickPipeline | null = null;\n let contextLost = false;\n let frozen = false; // GL render paused while the panorama is up\n let disposed = false;\n let selection = new Map<string, number>();\n let panorama: PanoramaHandle | null = null;\n let panoramaLoadAbort: AbortController | null = null;\n let panoramaPriorZIndex: string | null = null;\n const prefetch = new Map<string, Promise<SeatView>>();\n let flightGen = 0;\n let reducedForced: boolean | null = null;\n /** Focused floor, or -1 for the whole venue. Survives a context restore. */\n let focusedFloor = -1;\n\n const raisePanoramaLayer = (): void => {\n if (panoramaPriorZIndex !== null) return;\n panoramaPriorZIndex = container.style.zIndex;\n // Buyer chrome sits outside this stacking context at z-index 5. A panorama\n // is modal, so raise the complete 3D layer while it owns the interaction.\n container.style.zIndex = '20';\n };\n const restorePanoramaLayer = (): void => {\n if (panoramaPriorZIndex === null) return;\n if (container.style.zIndex === '20') container.style.zIndex = panoramaPriorZIndex;\n panoramaPriorZIndex = null;\n };\n\n const rebuildGpu = (): void => {\n gpu = buildGpuScene(glctx.gl, model);\n pick = new PickPipeline(\n glctx.renderer,\n gpu.seatGeometry,\n gpu.chairGeometry,\n gpu.solidGeometry,\n model.seats.count,\n );\n // Apply the chart's authored theme to everything outside the scene graph:\n // the clear colour (visible for a frame before the background draws, and on\n // any frame the scene does not cover) and the pick pass's restore.\n glctx.setClearColor(model.theme.background.top);\n pick.setRestoreClear(model.theme.background.top);\n // Seat size is authored too (`ChartTheme.seatScale`) — bigger seats for\n // charts with longer labels.\n gpu.seatProgram.uniforms.uSeatRadius.value = SEAT_DOT_RADIUS_M * model.theme.seatScale;\n // A context restore rebuilds the GPU scene, so re-apply the focused floor\n // rather than silently reverting the buyer to the whole venue.\n gpu.seatProgram.uniforms.uFocusFloor.value = focusedFloor;\n gpu.solidProgram.uniforms.uFocusFloor.value = focusedFloor;\n gpu.chairProgram.uniforms.uFocusFloor.value = focusedFloor;\n // A rebuilt scene has an empty chair set; force the next frame to re-gather\n // instead of trusting the last camera position.\n lastGatherX = Infinity;\n };\n\n // --- near field ------------------------------------------------------------\n // The spatial index is constructed here but its grid is built lazily, on the\n // first gather — a session that never leaves the overview never pays for it.\n const nearIndex = new NearFieldIndex(model.seats.iPosition, model.seats.count);\n const nearBuf = new Int32Array(CHAIR_MAX_INSTANCES);\n let lastGatherX = Infinity;\n let lastGatherZ = Infinity;\n // Overview is the legible map rung: category/status dots only. Chair/person\n // detail belongs to section, row and exact-seat inspection. Keeping this as\n // explicit journey state also prevents a close row's gathered mesh surviving\n // a soft camera return and streaking across the overview near plane.\n let nearFieldDetailEnabled = false;\n let cameraDetailVisible = false;\n // Real panoramas render on a 500 m in-scene sphere. While it is mounted the\n // tight venue frustum must include that sphere; generated live-scene views do\n // not need the override.\n let panoramaSphereVisible = false;\n const setNearFieldDetailEnabled = (enabled: boolean): void => {\n nearFieldDetailEnabled = enabled;\n if (!enabled) cameraDetailVisible = false;\n lastGatherX = Infinity;\n lastGatherZ = Infinity;\n if (!enabled && gpu?.nearSeatCount()) gpu.setNearSeats(nearBuf, 0);\n };\n\n /**\n * Re-gather the near set when the camera has moved far enough to invalidate\n * it — not every frame.\n *\n * The cheap early-out is the venue bound: if the camera is further from the\n * whole seat cloud than the gather radius, nothing can qualify and the grid is\n * never even touched. That is the state the overview, the intro ease and every\n * frame of a wide orbit are in, so the common case costs one distance test.\n */\n const updateNearField = (detailVisible: boolean): void => {\n if (!gpu) return;\n if (!detailVisible) {\n if (gpu.nearSeatCount()) gpu.setNearSeats(nearBuf, 0);\n return;\n }\n const cam = orbit.camera.position;\n const moved = Math.hypot(cam.x - lastGatherX, cam.z - lastGatherZ);\n if (moved < CHAIR_REBUILD_M) return;\n lastGatherX = cam.x;\n lastGatherZ = cam.z;\n const outside = Math.hypot(cam.x - model.bounds.center[0], cam.z - model.bounds.center[2])\n - model.bounds.radius;\n if (outside > CHAIR_GATHER_M) {\n if (gpu.nearSeatCount()) gpu.setNearSeats(nearBuf, 0);\n return;\n }\n const n = nearIndex.gather(cam.x, cam.z, CHAIR_GATHER_M, nearBuf);\n if (n === 0 && gpu.nearSeatCount() === 0) return;\n gpu.setNearSeats(nearBuf, n);\n };\n\n const glctx = new GLContext(container, {\n onContextLost: () => {\n if (!disposed) analytics.contextLost();\n contextLost = true;\n loop.stop();\n gpu = null;\n pick = null;\n },\n onContextRestored: () => {\n rebuildGpu();\n contextLost = false;\n if (!disposed) analytics.contextRestored();\n loop.requestRender();\n },\n });\n\n const orbit = new OrbitCamera(\n glctx.gl,\n glctx.canvas,\n () => loop.requestRender(),\n () => analytics.orbitEngaged(), // first real drag/wheel/pinch (not the intro ease)\n );\n // setAspect BEFORE frame so the fit clears the horizontal FOV too (centred with\n // margin on a wide designer canvas rather than parked low-left).\n orbit.setAspect(glctx.aspect);\n // Enter from the stage side (camera behind the stage, every tier facing you).\n // When the focal sits at the centre (in-the-round) there is no stage side —\n // fall back to the fixed architectural angle.\n const stageAzimuth = ((): number | undefined => {\n const dx = model.focalWorld[0] - model.bounds.center[0];\n const dz = model.focalWorld[2] - model.bounds.center[2];\n return Math.hypot(dx, dz) > model.bounds.radius * 0.12 ? Math.atan2(dx, dz) : undefined;\n })();\n const reduceMotionOnMount = typeof window !== 'undefined' && !!window.matchMedia\n && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n orbit.frame(model.bounds, !reduceMotionOnMount, stageAzimuth, opts.portraitOverviewCrop === true);\n\n const cinematic = new Cinematic(orbit.camera);\n\n // Labels are DOM, projected from world anchors — see labels.ts for why. The\n // container must establish a positioning context or the overlay would anchor\n // to the page instead of the canvas.\n const restoreContainerPosition = establishPositioningContext(container);\n const labelOverlay = new LabelOverlay(container, {\n fontFamily: input.doc.theme?.fontFamily,\n ink: input.doc.theme?.textColor,\n });\n labelOverlay.setLabels(model.labels);\n const baseLabels = model.labels;\n // While a buyer is inside a section, taps are deliberately scoped to that\n // section. Nearby tiers remain visible as spatial context, but must not steal\n // a tap through the GPU depth buffer when their projected dots overlap.\n let focusedSectionId: string | null = null;\n const showSectionSeatLabels = (sectionId: string | null): void => {\n focusedSectionId = sectionId;\n labelOverlay.setForcedDense(!!sectionId);\n if (!sectionId) {\n labelOverlay.setLabels(baseLabels);\n return;\n }\n const sectionSeats = input.seats.filter((seat) => seat.sectionId === sectionId);\n const seatIds = new Set(sectionSeats.map((seat) => seat.id));\n const rowIds = new Set(sectionSeats.map((seat) => seat.rowId));\n const selectedIds = new Set(selection.keys());\n const selectedInSection = new Set(sectionSeats.filter((seat) => selectedIds.has(seat.id)).map((seat) => seat.id));\n const focusedBase = baseLabels.filter((label) => (\n label.kind !== 'row' && label.kind !== 'seat'\n ) || (\n label.kind === 'row'\n ? rowIds.has(label.id.slice('row:'.length))\n : seatIds.has(label.id.slice('seat:'.length))\n && (selectedInSection.size === 0 || selectedInSection.has(label.id.slice('seat:'.length)))\n ));\n if (model.seatCount <= 6_000 && selectedInSection.size === 0) {\n labelOverlay.setLabels(focusedBase);\n return;\n }\n const labelSeats = selectedInSection.size\n ? sectionSeats.filter((seat) => selectedInSection.has(seat.id))\n : sectionSeats;\n const generatedIds = new Set(labelSeats.map((seat) => `seat:${seat.id}`));\n const labels = labelSeats.flatMap((seat) => {\n const index = model.seats.idToIndex.get(seat.id);\n if (index === undefined) return [];\n const offset = index * 3;\n return [{\n id: `seat:${seat.id}`,\n kind: 'seat' as const,\n text: seat.displayLabel || seat.label,\n anchor: [\n model.seats.iPosition[offset],\n // Put the active identity on the chair back. A deck-level label can\n // sit a full row below a foreground chair in perspective, while DOM\n // labels for rows behind remain visible through the WebGL geometry.\n model.seats.iPosition[offset + 1] + (selectedIds.has(seat.id) ? 1.05 : 0.55),\n model.seats.iPosition[offset + 2],\n ] as [number, number, number],\n }];\n });\n labelOverlay.setLabels([\n ...focusedBase.filter((label) => !generatedIds.has(label.id)),\n ...labels,\n ]);\n };\n\n rebuildGpu();\n\n const loop = new RenderLoop((/* dt */) => {\n if (contextLost || !gpu || frozen) return false;\n const flying = cinematic.active;\n const moving = flying ? cinematic.update(performance.now()) : orbit.update();\n\n const lod = computeSeatLod(orbit.currentDistance, model.bounds.radius);\n // Cinematic and panorama poses move the OGL camera directly, so clipping\n // belongs in the render loop rather than only in OrbitCamera.frame(). The\n // same projection matrix is then used by display, label projection and pick.\n const clippingBounds = panoramaSphereVisible\n ? { center: [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z] as [number, number, number], radius: 520 }\n : model.renderBounds;\n orbit.updateClipping(clippingBounds);\n // Semantic section/row/seat focus permits detail, but camera scale decides\n // whether it is still legible. A buyer can wheel from a section back to a\n // whole-bowl view without pressing Fit venue; leaving thousands of dots and\n // chairs enabled there caused a history-dependent moiré/scattering state.\n // Hysteresis prevents flicker near the handover.\n const cameraDx = orbit.camera.position.x - model.renderBounds.center[0];\n const cameraDy = orbit.camera.position.y - model.renderBounds.center[1];\n const cameraDz = orbit.camera.position.z - model.renderBounds.center[2];\n const cameraVenueDistance = Math.hypot(cameraDx, cameraDy, cameraDz);\n const detailEnter = model.renderBounds.radius * 1.55;\n const detailExit = model.renderBounds.radius * 1.80;\n if (!nearFieldDetailEnabled) cameraDetailVisible = false;\n else if (cameraDetailVisible) cameraDetailVisible = cameraVenueDistance <= detailExit;\n else cameraDetailVisible = cameraVenueDistance <= detailEnter;\n\n const u = gpu.seatProgram.uniforms;\n u.uSeatScale.value = lod.scale;\n u.uSeatFade.value = lod.fade;\n u.uSeatDetail.value = cameraDetailVisible ? 1 : 0;\n gpu.solidProgram.uniforms.uStructureDetail.value = cameraDetailVisible ? 1 : 0;\n // Relax the dot's pixel floor as the venue recedes — see SeatLod.minPixels\n // for why holding 2.5 px all the way out is what welds distant rows into one\n // mass. The pick pass syncs this same value, so the hit mask never drifts\n // off the drawn dot.\n u.uMinPixels.value = lod.minPixels;\n u.uPixelToWorld.value = (2 * Math.tan((orbit.camera.fov * DEG) / 2)) / Math.max(1, glctx.pixelHeight);\n updateNearField(cameraDetailVisible);\n\n glctx.renderer.render({ scene: gpu.background, clear: true });\n glctx.renderer.render({ scene: gpu.main, camera: orbit.camera, clear: false });\n // After the render, so the camera's matrices are the ones just drawn with —\n // projecting from stale matrices makes labels lag the venue by a frame.\n labelOverlay.update(\n orbit.camera.projectionViewMatrix as unknown as ArrayLike<number>,\n glctx.canvas.clientWidth || 1,\n glctx.canvas.clientHeight || 1,\n orbit.currentDistance,\n model.bounds.radius,\n // The dense label rungs rank by real distance from the eye, not by the\n // orbit radius — at the arrival pose those are wildly different numbers.\n [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z],\n );\n return moving;\n });\n\n const ro = typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => handle.resize())\n : null;\n ro?.observe(container);\n\n const setSelection = (ids: string[]): void => {\n const baseStateIndex = (id: string): number | undefined => {\n const idx = model.seats.idToIndex.get(id);\n return idx === undefined ? undefined : model.seats.iState[idx];\n };\n const { updates, next } = diffSelection(selection, ids, baseStateIndex);\n selection = next;\n labelOverlay.setSelectedSeatIds(selection.keys());\n if (focusedSectionId) showSectionSeatLabels(focusedSectionId);\n if (updates.length) {\n const runs = applySeatStates(model.seats, updates);\n if (gpu) gpu.uploadSeatStateRuns(runs);\n loop.requestRender();\n }\n };\n\n // --- cinematic / panorama ---\n const reducedMotion = (): boolean => {\n if (reducedForced !== null) return reducedForced;\n return typeof window !== 'undefined' && !!window.matchMedia\n && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n };\n\n const PREFETCH_CAP = 8;\n const ensureSeatView = (seatId: string): Promise<SeatView> | null => {\n if (!opts.getSeatView) return null;\n let p = prefetch.get(seatId);\n if (!p) {\n p = Promise.resolve(opts.getSeatView(seatId));\n prefetch.set(seatId, p);\n // A network/decode failure must not poison this seat for the lifetime of\n // the 3D mount. Drop only the promise that actually failed; a newer retry\n // may already have replaced it by the time this rejection settles.\n void p.catch(() => {\n if (prefetch.get(seatId) === p) prefetch.delete(seatId);\n });\n // Bound the cache (LRU-ish): drop the oldest inserted entries past the cap.\n while (prefetch.size > PREFETCH_CAP) {\n const oldest = prefetch.keys().next().value as string | undefined;\n if (oldest === undefined) break;\n prefetch.delete(oldest);\n }\n }\n return p;\n };\n\n const resolvedSeatViewPose = (seatId: string, idx: number): { eye: Vec3Arr; focal: Vec3Arr } => {\n const seat = expandedSeatById.get(seatId);\n const floorIndex = Math.max(0, Math.round(model.seats.iFloor[idx] ?? 0));\n const floor = input.doc.floors?.[floorIndex];\n const focalPoint = seat?.focalPoint ?? floor?.focalPoint ?? input.doc.focalPoint;\n const deck: Vec3Arr = [\n model.seats.iPosition[idx * 3],\n model.seats.iPosition[idx * 3 + 1],\n model.seats.iPosition[idx * 3 + 2],\n ];\n return seatViewPose(deck, focalPoint, floor?.baseHeightM ?? 0, seat?.eyeHeightM);\n };\n\n const placeCameraFinal = (finalPos: Vec3Arr, focal: Vec3Arr, fov = FOV_END): void => {\n orbit.camera.position.set(finalPos[0], finalPos[1], finalPos[2]);\n orbit.camera.lookAt(new Vec3(focal[0], focal[1], focal[2]));\n orbit.camera.fov = fov;\n orbit.camera.updateProjectionMatrix();\n };\n\n // --- arrival chip: the flight HOLDS in the live scene; the painted 360 is\n // an explicit tap away. (Owner call 2026-07-24: the real scene at the seat\n // IS the payoff; the generated panorama undersold it as an auto-landing.)\n /** One narrow test for all of this module's chrome, measured off the CONTAINER\n * rather than the window — the 3D view mounts inside the picker, which itself\n * mounts inside a page column, so the window says nothing useful about how\n * much room these chips actually have. */\n const isNarrow = (): boolean => (container.clientWidth || glctx.canvas.clientWidth) <= 520;\n\n let arriveChip: HTMLDivElement | null = null;\n const layoutArriveChip = (): void => {\n if (!arriveChip) return;\n const narrow = isNarrow();\n Object.assign(arriveChip.style, narrow ? {\n left: '12px', right: '12px', bottom: '70px', transform: 'none', justifyContent: 'center',\n } : {\n left: '50%', right: 'auto', bottom: '18px', transform: 'translateX(-50%)', justifyContent: 'initial',\n });\n };\n const removeArriveChip = (): void => {\n arriveChip?.remove();\n arriveChip = null;\n };\n const showArriveChip = (seatId: string, gen: number, retry = false): HTMLDivElement | null => {\n removeArriveChip();\n if (disposed) return null;\n const controls = document.createElement('div');\n controls.setAttribute('role', 'group');\n Object.assign(controls.style, {\n position: 'absolute',\n display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '6px', zIndex: '4',\n } as Partial<CSSStyleDeclaration>);\n\n const seat = expandedSeatById.get(seatId);\n const section = seat?.sectionId ? model.sections.find((candidate) => candidate.id === seat.sectionId) : undefined;\n const seatLabel = seat?.label ?? seatId;\n controls.setAttribute('aria-label', `Explore views near ${section?.label ? `${section.label}, ` : ''}seat ${seatLabel}`);\n const location = document.createElement('span');\n location.textContent = ['Live 3D seat view', section?.label, seatLabel].filter(Boolean).join(' · ');\n Object.assign(location.style, {\n maxWidth: 'min(320px, calc(100vw - 32px))', padding: '5px 10px', borderRadius: '999px',\n overflow: 'hidden', color: '#d9e3f5', background: 'rgba(12,18,32,0.72)',\n font: '600 11px/1.2 inherit', textOverflow: 'ellipsis', whiteSpace: 'nowrap',\n backdropFilter: 'blur(6px)',\n } as Partial<CSSStyleDeclaration>);\n controls.appendChild(location);\n const actions = document.createElement('div');\n Object.assign(actions.style, {\n display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '6px',\n } as Partial<CSSStyleDeclaration>);\n controls.appendChild(actions);\n const row = seat?.rowId ? seatIdsByRow.get(seat.rowId) : undefined;\n const at = row?.indexOf(seatId) ?? -1;\n const neighbours = at >= 0 ? [row?.[at - 1], row?.[at + 1]] : [undefined, undefined];\n const addButton = (label: string, ariaLabel: string, action: () => void, primary = false): void => {\n const button = document.createElement('button');\n button.type = 'button';\n button.textContent = label;\n button.setAttribute('aria-label', ariaLabel);\n Object.assign(button.style, {\n minHeight: '44px', padding: primary ? '10px 16px' : '10px',\n borderRadius: '999px', background: 'rgba(12,18,32,0.78)', color: '#eef1f8',\n border: '1px solid rgba(150,165,205,0.4)', backdropFilter: 'blur(6px)',\n font: '600 13px/1 inherit', cursor: 'pointer', whiteSpace: 'nowrap',\n } as Partial<CSSStyleDeclaration>);\n if (!primary) button.style.minWidth = '44px';\n if (primary) button.dataset.panoramaTrigger = '';\n button.addEventListener('click', action);\n actions.appendChild(button);\n };\n if (neighbours[0]) {\n const label = expandedSeatById.get(neighbours[0])?.label ?? neighbours[0];\n addButton('‹', `View previous seat ${label}`, () => { void flyToSeat(neighbours[0]!); });\n }\n if (opts.getSeatView) {\n const sourceAction = opts.seatViewActionLabel?.(seatId) ?? 'Open 360° panorama';\n addButton(retry ? `↻ Retry ${sourceAction}` : `◉ ${sourceAction}`, `${retry ? 'Retry' : sourceAction} from seat ${seatLabel}`, () => {\n if (disposed || gen !== flightGen) { removeArriveChip(); return; }\n removeArriveChip();\n void openPanorama(seatId, 400, gen);\n }, true);\n }\n if (neighbours[1]) {\n const label = expandedSeatById.get(neighbours[1])?.label ?? neighbours[1];\n addButton('›', `View next seat ${label}`, () => { void flyToSeat(neighbours[1]!); });\n }\n if (!actions.childElementCount) return null;\n container.appendChild(controls);\n arriveChip = controls;\n layoutArriveChip();\n return controls;\n };\n\n // --- 3D control dock --------------------------------------------------------\n // Match the 2D picker's learned control position: one bottom-right home for\n // camera actions. Desktop keeps explicit Rotate/Move discovery; touch already\n // has one-finger orbit plus two-finger pan/zoom, so phones only need a compact\n // \"fit the venue\" escape and do not sacrifice the scene to redundant buttons.\n const navigationModeChip = document.createElement('div');\n let seatViewLocked = false;\n navigationModeChip.className = 'sl-3d-navigation-controls';\n Object.assign(navigationModeChip.style, {\n position: 'absolute', right: '14px', bottom: '14px', display: 'flex', zIndex: '4',\n flexDirection: 'column', alignItems: 'flex-end', gap: '7px',\n } as Partial<CSSStyleDeclaration>);\n const modeControls = document.createElement('div');\n modeControls.setAttribute('role', 'group');\n modeControls.setAttribute('aria-label', '3D drag mode');\n Object.assign(modeControls.style, {\n display: 'flex', flexDirection: 'column', gap: '6px', alignItems: 'flex-end',\n } as Partial<CSSStyleDeclaration>);\n const utilityControls = document.createElement('div');\n utilityControls.setAttribute('role', 'group');\n utilityControls.setAttribute('aria-label', '3D view controls');\n Object.assign(utilityControls.style, {\n display: 'flex', flexDirection: 'column', gap: '6px', alignItems: 'flex-end',\n } as Partial<CSSStyleDeclaration>);\n const rotateModeButton = document.createElement('button');\n rotateModeButton.type = 'button';\n rotateModeButton.textContent = '↻';\n rotateModeButton.setAttribute('aria-label', 'Drag to rotate the 3D venue');\n rotateModeButton.dataset.tooltip = 'Rotate venue';\n const moveModeButton = document.createElement('button');\n moveModeButton.type = 'button';\n moveModeButton.textContent = '✥';\n moveModeButton.setAttribute('aria-label', 'Drag to move the 3D venue left, right, up or down');\n moveModeButton.dataset.tooltip = 'Move venue';\n const zoomOutButton = document.createElement('button');\n zoomOutButton.type = 'button';\n zoomOutButton.textContent = '−';\n zoomOutButton.setAttribute('aria-label', 'Zoom out of the 3D venue');\n zoomOutButton.dataset.tooltip = 'Zoom out';\n const zoomInButton = document.createElement('button');\n zoomInButton.type = 'button';\n zoomInButton.textContent = '+';\n zoomInButton.setAttribute('aria-label', 'Zoom into the 3D venue');\n zoomInButton.dataset.tooltip = 'Zoom in';\n for (const button of [rotateModeButton, moveModeButton, zoomOutButton, zoomInButton]) {\n button.className = 'sl-3d-icon-control';\n Object.assign(button.style, {\n width: '44px', minWidth: '44px', minHeight: '44px', padding: '9px',\n border: '1px solid rgba(150,165,205,0.35)', borderRadius: '999px',\n color: '#c9d4ea', background: 'rgba(12,18,32,0.82)', font: '600 17px/1 inherit',\n backdropFilter: 'blur(6px)',\n cursor: 'pointer', whiteSpace: 'nowrap',\n } as Partial<CSSStyleDeclaration>);\n }\n modeControls.append(rotateModeButton, moveModeButton);\n utilityControls.append(zoomInButton, zoomOutButton);\n const setNavigationMode = (mode: 'orbit' | 'pan'): void => {\n orbit.setPrimaryDragMode(mode);\n const rotateActive = mode === 'orbit';\n rotateModeButton.setAttribute('aria-pressed', String(rotateActive));\n moveModeButton.setAttribute('aria-pressed', String(!rotateActive));\n rotateModeButton.style.background = rotateActive ? 'rgba(96,110,150,0.68)' : 'rgba(12,18,32,0.82)';\n moveModeButton.style.background = rotateActive ? 'rgba(12,18,32,0.82)' : 'rgba(96,110,150,0.68)';\n navigationModeChip.title = rotateActive\n ? 'Drag to rotate · Shift-drag to move'\n : 'Drag to move · Shift-drag to rotate';\n };\n rotateModeButton.addEventListener('click', () => setNavigationMode('orbit'));\n moveModeButton.addEventListener('click', () => setNavigationMode('pan'));\n zoomOutButton.addEventListener('click', () => orbit.zoomBy(1.22));\n zoomInButton.addEventListener('click', () => orbit.zoomBy(0.82));\n navigationModeChip.append(modeControls, utilityControls);\n setNavigationMode('orbit');\n container.appendChild(navigationModeChip);\n\n /* THE GESTURE IS THE CONTROL ON TOUCH.\n Desktop exposes Rotate/Move and +/− because alternate mouse gestures are\n not obvious. On a phone those controls only restate native one-finger orbit\n and two-finger pan/zoom, so the dock collapses to the single recovery action\n buyers still need: fit the whole venue back into view. */\n const layoutNavigationChip = (): void => {\n const narrow = isNarrow();\n navigationModeChip.style.display = seatViewLocked ? 'none' : 'flex';\n navigationModeChip.style.right = narrow ? '12px' : '14px';\n // Clear the standard bottom-right privacy trigger on phone-sized embeds.\n navigationModeChip.style.bottom = narrow\n ? 'calc(env(safe-area-inset-bottom, 0px) + 76px)'\n : '14px';\n modeControls.style.display = narrow ? 'none' : 'flex';\n zoomInButton.style.display = narrow ? 'none' : '';\n zoomOutButton.style.display = narrow ? 'none' : '';\n };\n layoutNavigationChip();\n const setSeatViewLocked = (locked: boolean): void => {\n seatViewLocked = locked;\n orbit.setInteractionEnabled(!locked);\n layoutNavigationChip();\n };\n\n // --- fit venue: the 3D equivalent of the 2D map's \"Fit to screen\". It exits\n // section/seat/panorama context and restores the whole-venue composition.\n const overviewChip = document.createElement('button');\n overviewChip.className = 'sl-3d-overview-control sl-3d-icon-control';\n overviewChip.type = 'button';\n overviewChip.textContent = '⌂';\n overviewChip.setAttribute('aria-label', 'Fit the whole venue in view');\n overviewChip.dataset.tooltip = 'Fit venue';\n Object.assign(overviewChip.style, {\n width: '44px', minWidth: '44px', minHeight: '44px', padding: '9px', borderRadius: '999px',\n background: 'rgba(12,18,32,0.72)', color: '#c9d4ea',\n border: '1px solid rgba(150,165,205,0.35)', backdropFilter: 'blur(6px)',\n font: '600 11px/1 inherit', cursor: 'pointer', whiteSpace: 'nowrap',\n } as Partial<CSSStyleDeclaration>);\n const layoutOverviewChip = (): void => {\n overviewChip.textContent = '⌂';\n };\n layoutOverviewChip();\n const focusOverview = (): void => {\n // The panorama owns the screen whenever it is up. Guarding on `frozen`\n // alone was equivalent only while every panorama froze the loop; the\n // in-scene sphere deliberately does not, so ask about the panorama itself.\n if (disposed) return;\n // Notify the host before any camera/panorama cleanup. A rendering exception\n // must not leave semantic buyer navigation stuck on the previous seat.\n opts.onViewTargetChange?.(null);\n opts.onSectionFocusChange?.(null);\n if (panorama) {\n panorama.dispose();\n panorama = null;\n analytics.panoramaClosed();\n }\n panoramaSphereVisible = false;\n panoramaLoadAbort?.abort();\n panoramaLoadAbort = null;\n restorePanoramaLayer();\n overviewChip.style.display = '';\n labelOverlay.setVisible(true);\n frozen = false;\n setNearFieldDetailEnabled(false);\n setSeatViewLocked(false);\n cancelFlight();\n removeArriveChip();\n setNavigationMode('orbit');\n if (reducedMotion()) orbit.frame(model.bounds, false, stageAzimuth, opts.portraitOverviewCrop === true);\n else orbit.frameSoft(model.bounds, stageAzimuth, opts.portraitOverviewCrop === true);\n showSectionSeatLabels(null);\n loop.requestRender();\n };\n overviewChip.addEventListener('click', focusOverview);\n utilityControls.appendChild(overviewChip);\n\n const openPanorama = async (seatId: string, fadeMs: number, gen: number): Promise<void> => {\n panoramaLoadAbort?.abort();\n const loadAbort = new AbortController();\n panoramaLoadAbort = loadAbort;\n const seatIndex = model.seats.idToIndex.get(seatId);\n if (seatIndex === undefined) { orbit.syncFromCamera(); return; }\n const seatLabel = expandedSeatById.get(seatId)?.label ?? seatId;\n const seatPose = resolvedSeatViewPose(seatId, seatIndex);\n const viewPromise = ensureSeatView(seatId);\n if (!viewPromise) {\n loadAbort.abort();\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n orbit.syncFromCamera();\n return;\n } // no panorama source\n frozen = true;\n loop.stop(); // freeze the GL at the seat pose; panorama fades in over it\n let view: SeatView;\n try {\n view = await viewPromise;\n } catch {\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n analytics.panoramaFailed(seatId, 'source');\n // Only unfreeze if we still own the flight — a retarget during the await\n // has already reset `frozen` and taken over the loop.\n if (!disposed && gen === flightGen) {\n frozen = false;\n orbit.resumeAfterFlight(seatPose.focal);\n loop.requestRender();\n const controls = showArriveChip(seatId, gen, true);\n requestAnimationFrame(() => controls?.querySelector<HTMLButtonElement>('[data-panorama-trigger]')?.focus());\n }\n return;\n }\n // Superseded during the await (retarget/cancel) or disposed: bail WITHOUT\n // touching frozen/loop — the newer flight owns the freeze state now, and\n // mounting this stale seat's panorama would be wrong.\n if (disposed || gen !== flightGen) {\n loadAbort.abort();\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n return;\n }\n removeArriveChip();\n const onClose = (): void => {\n loadAbort.abort();\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n panorama = null;\n panoramaSphereVisible = false;\n restorePanoramaLayer();\n overviewChip.style.display = '';\n labelOverlay.setVisible(!opts.arriveAtSeatEye);\n frozen = false;\n analytics.panoramaClosed();\n orbit.resumeAfterFlight(seatPose.focal);\n loop.requestRender();\n // Back in the live scene at the seat — offer the 360 again.\n const controls = showArriveChip(seatId, flightGen);\n requestAnimationFrame(() => controls?.querySelector<HTMLButtonElement>('[data-panorama-trigger]')?.focus());\n };\n // Prefer the in-scene sphere: it keeps the loop running, so the arrival is a\n // cross-fade between two views of the same place from the same camera\n // rather than a bitmap dropped over a frozen frame. It needs the image to\n // decode and a GL context that still exists, so it can decline — and the\n // DOM viewer stays as the fallback because a 360 that fails to mount must\n // never leave the buyer worse off than no 360 at all.\n // A GENERATED panorama is a 2048×1024 picture of the very scene already on\n // screen. Through a 106° window on a 3072-device-pixel viewport that is a\n // 5× magnification of ~600 source pixels, and it reads soft next to the\n // arrival frame the buyer was just looking at. So it is not drawn: the\n // camera looks around the real geometry instead, which is sharp at any\n // field of view and gets better every time the scene does. Real photographs\n // still go on the sphere, where their 5760–8192 px capture has detail worth\n // showing.\n const sceneMode = view.generated === true;\n const disclosure = seatViewDisclosure(view);\n raisePanoramaLayer();\n panoramaSphereVisible = !sceneMode;\n overviewChip.style.display = 'none';\n const effectiveFadeMs = reducedMotion() ? 0 : fadeMs;\n const spherical = gpu && !contextLost\n ? await mountPanoramaSphere(container, sceneMode ? null : view, {\n gl: glctx.gl,\n scene: gpu.main,\n camera: orbit.camera,\n requestRender: () => loop.requestRender(),\n cameraOriginWorld: sceneMode ? seatPose.eye : undefined,\n focalWorld: seatPose.focal,\n }, {\n fadeMs: effectiveFadeMs,\n seatLabel,\n disclosure,\n disclosurePrefix: sceneMode ? 'Live seat-eye view' : '360° panorama',\n onClose,\n signal: loadAbort.signal,\n })\n : null;\n // Superseded while the image was decoding — the newer flight owns the state.\n if (disposed || gen !== flightGen) {\n loadAbort.abort();\n panoramaSphereVisible = false;\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n spherical?.dispose();\n overviewChip.style.display = '';\n restorePanoramaLayer();\n return;\n }\n if (spherical) {\n // The venue keeps rendering behind the sphere, so the freeze that the DOM\n // path depends on must be lifted or the cross-fade has nothing to fade\n // from. The sphere itself pins the camera.\n //\n // Sync orbit to where the flight actually parked the camera FIRST. Its\n // damped pose is still whatever it was before the flight, so the next\n // `orbit.update()` would read that as movement, call `applyPosition()`\n // and yank the camera off the seat mid-fade. Syncing equalises pose and\n // target so `update()` reports \"not moving\" and leaves the camera alone —\n // which is what makes it safe to run the loop at all here.\n orbit.syncFromCamera();\n // Labels are DOM and the sphere is GL, so every row and seat label would\n // otherwise float on top of the panorama.\n labelOverlay.setVisible(false);\n frozen = false;\n loop.requestRender();\n panorama = spherical;\n } else if (sceneMode) {\n // Live geometry has no bitmap fallback. If the GL context disappeared\n // between the explicit action and mount, return to the live seat controls\n // rather than handing a sentinel URL to the DOM image viewer or silently\n // substituting unrelated media.\n restorePanoramaLayer();\n overviewChip.style.display = '';\n frozen = false;\n orbit.resumeAfterFlight(seatPose.focal);\n loop.requestRender();\n panorama = null;\n panoramaSphereVisible = false;\n analytics.panoramaFailed(seatId, 'mount');\n const controls = showArriveChip(seatId, flightGen, true);\n requestAnimationFrame(() => controls?.querySelector<HTMLButtonElement>('[data-panorama-trigger]')?.focus());\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n return;\n } else {\n // The DOM fallback does not use the GL sphere; return the venue renderer\n // to its tight render bound while the frozen overlay owns presentation.\n panoramaSphereVisible = false;\n analytics.panoramaFallback(seatId);\n panorama = mountPanorama(container, view, {\n fadeMs: effectiveFadeMs, seatLabel, disclosure, onClose, signal: loadAbort.signal,\n });\n }\n analytics.panoramaOpened();\n };\n\n const cancelFlight = (): void => {\n flightGen++; // supersede any pending .then(openPanorama)\n panoramaLoadAbort?.abort();\n panoramaLoadAbort = null;\n if (cinematic.active) {\n cinematic.cancel();\n orbit.resumeAfterFlight(model.focalWorld);\n }\n };\n\n const flyToSeat = (seatId: string): Promise<void> => {\n if (disposed || !gpu) return Promise.resolve();\n const idx = model.seats.idToIndex.get(seatId);\n if (idx === undefined) return Promise.resolve();\n setNearFieldDetailEnabled(true);\n opts.onViewTargetChange?.(seatId);\n // Reset the freeze unconditionally: a previous flight may have set frozen=true\n // inside openPanorama's pre-await window without a panorama ever mounting.\n if (panorama) {\n panorama.dispose();\n panorama = null;\n panoramaSphereVisible = false;\n overviewChip.style.display = '';\n restorePanoramaLayer();\n }\n panoramaLoadAbort?.abort();\n panoramaLoadAbort = null;\n frozen = false;\n setSeatViewLocked(false);\n\n const gen = ++flightGen;\n removeArriveChip();\n const { eye: seatEye, focal } = resolvedSeatViewPose(seatId, idx);\n const start: Vec3Arr = [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z];\n const flight = buildWaypoints(start, seatEye, focal, model.bounds.center, model.bounds.radius);\n // The inspection anchor sits behind the selected seat and can end up under\n // the next tier in a deep covered bowl. Live seat-eye experiences land at\n // the truthful modeled origin; legacy embeds keep the inspection anchor.\n const finalPos = opts.arriveAtSeatEye ? seatEye : flight.finalPos;\n const arrivalFov = opts.arriveAtSeatEye\n ? (container.clientWidth / Math.max(1, container.clientHeight) < 0.70 ? 90 : 65)\n : FOV_END;\n const waypoints: Vec3Arr[] = [flight.waypoints[0], flight.waypoints[1], finalPos];\n labelOverlay.setVisible(false);\n\n if (reducedMotion()) {\n // a11y: no flight — snap straight to the seat pose in the live scene.\n placeCameraFinal(finalPos, focal, arrivalFov);\n loop.requestRender();\n analytics.cinematicSkipped();\n if (!disposed) orbit.syncFromCamera();\n setSeatViewLocked(opts.arriveAtSeatEye === true);\n showArriveChip(seatId, gen);\n return Promise.resolve();\n }\n\n const startQuat = new Quat().copy(orbit.camera.quaternion);\n const endQuat = lookAtQuat(orbit.camera, finalPos, focal);\n loop.requestRender();\n return cinematic.start(waypoints, startQuat, endQuat, FLIGHT_DURATION_MS, arrivalFov).then(() => {\n if (disposed || gen !== flightGen) return; // disposed or superseded (retarget/cancel)\n analytics.cinematicPlayed(FLIGHT_DURATION_MS);\n // Arrive and HOLD in the live 3D scene — sitting in the crowd, looking at\n // the show. Exact seat-eye callers are pinned here; their explicit\n // look-around action rotates at this origin without handing the buyer the\n // venue-orbit controller. The real scene is the payoff moment.\n orbit.resumeAfterFlight(focal);\n setSeatViewLocked(opts.arriveAtSeatEye === true);\n loop.requestRender();\n showArriveChip(seatId, gen);\n });\n };\n\n /** Enter the stand itself. This is intentionally separate from picking a\n * ticket: overview tap → section, focused-section tap → seat. */\n const focusSectionCamera = (sectionId: string): boolean => {\n const sec = model.sections.find((candidate) => candidate.id === sectionId);\n if (!sec || sec.seatCount === 0) return false;\n setNearFieldDetailEnabled(true);\n setSeatViewLocked(false);\n cinematic.cancel();\n removeArriveChip();\n setNavigationMode('pan');\n showSectionSeatLabels(sectionId);\n const dx = sec.focalWorld[0] - sec.center[0];\n const dz = sec.focalWorld[2] - sec.center[2];\n const azimuth = Math.hypot(dx, dz) > sec.radius * 0.12 ? Math.atan2(dx, dz) : undefined;\n // Deliberately crop the section edges: this is an ENTER-section action,\n // not a second overview. Seats become practical tap targets and the row /\n // sampled seat numbers are readable.\n orbit.frame({ center: sec.center, radius: Math.max(5, sec.radius * 0.5) }, false, azimuth);\n opts.onSectionFocusChange?.(sectionId);\n loop.requestRender();\n return true;\n };\n\n // --- Tap → pick / flight-cancel ---\n let downX = 0, downY = 0, downT = 0, downId = -1, moved = false, suppressTap = false;\n const onDown = (e: PointerEvent): void => {\n if (seatViewLocked) return;\n if (downId !== -1) return;\n downId = e.pointerId; downX = e.clientX; downY = e.clientY; downT = performance.now(); moved = false;\n // A press during a flight cancels it (damped stop) instead of picking.\n suppressTap = cinematic.active;\n if (cinematic.active) {\n analytics.cinematicCancelled();\n cancelFlight();\n opts.onViewTargetChange?.(null);\n }\n };\n const onMove = (e: PointerEvent): void => {\n if (e.pointerId !== downId) return;\n if (Math.hypot(e.clientX - downX, e.clientY - downY) > TAP_SLOP) moved = true;\n };\n /**\n * Pick the visible seat target nearest a tap while drilled into a section.\n *\n * The overview uses the O(1) GPU colour pass. In a close raked section the\n * chair/deck depth geometry can win that pass even though the buyer clearly\n * tapped the large seat dot. Projecting just the focused section's seats is a\n * bounded, tap-only fallback (normally a few hundred points) and, critically,\n * prevents the neighbouring tier retained for context from stealing the tap.\n */\n const pickNearestProjectedSeat = (\n clientX: number,\n clientY: number,\n sectionId: string | null,\n maxDistance: number,\n bookableOnly: boolean,\n ): number => {\n const rect = glctx.canvas.getBoundingClientRect();\n const width = glctx.canvas.clientWidth || rect.width || 1;\n const height = glctx.canvas.clientHeight || rect.height || 1;\n const tapX = clientX - rect.left;\n const tapY = clientY - rect.top;\n // The caller chooses a section/overview tolerance. Distance wins; depth\n // only breaks near-ties.\n const maxDistanceSq = maxDistance * maxDistance;\n let bestIndex = -1;\n let bestDistanceSq = maxDistanceSq;\n let bestDepth = Infinity;\n for (const seat of input.seats) {\n if (sectionId && seat.sectionId !== sectionId) continue;\n const index = model.seats.idToIndex.get(seat.id);\n if (index === undefined) continue;\n // The focused view is a buying surface, not an inventory inspector. Snap\n // the finger target to the nearest bookable (or already-selected) seat;\n // held, sold and filtered seats stay visible but cannot produce a dead\n // optimistic pick that the authoritative picker immediately rejects.\n if (bookableOnly) {\n const state = Math.round(model.seats.iState[index] ?? 0);\n if (state !== 0 && state !== 3) continue;\n }\n const offset = index * 3;\n const screen = projectToScreen(\n orbit.camera.projectionViewMatrix as unknown as ArrayLike<number>,\n [\n model.seats.iPosition[offset],\n // Match the visible chair/number rather than the deck-level instance\n // origin. At strong zoom the vertical difference is dozens of pixels\n // and a perfectly reasonable click on the chair otherwise misses.\n model.seats.iPosition[offset + 1] + 0.55,\n model.seats.iPosition[offset + 2],\n ],\n width,\n height,\n );\n if (!screen.visible) continue;\n const dx = screen.x - tapX;\n const dy = screen.y - tapY;\n const distanceSq = dx * dx + dy * dy;\n if (\n distanceSq < bestDistanceSq\n || (Math.abs(distanceSq - bestDistanceSq) < 1 && screen.depth < bestDepth)\n ) {\n bestIndex = index;\n bestDistanceSq = distanceSq;\n bestDepth = screen.depth;\n }\n }\n return bestIndex;\n };\n const pickVisibleGeometry = (clientX: number, clientY: number): number => {\n if (!gpu || !pick) return -1;\n pick.syncFromSeatProgram(gpu.seatProgram);\n const rect = glctx.canvas.getBoundingClientRect();\n const dpr = glctx.renderer.dpr;\n const { x, y } = pickPixelCoords(\n clientX,\n clientY,\n rect,\n dpr,\n glctx.gl.drawingBufferWidth,\n glctx.gl.drawingBufferHeight,\n );\n const radius = Math.max(2, Math.round(8 * dpr));\n return pick.pick(orbit.camera, x, y, radius);\n };\n const onUp = (e: PointerEvent): void => {\n if (e.pointerId !== downId) return;\n const isTap = !moved && performance.now() - downT < TAP_MS;\n downId = -1;\n if (suppressTap) { suppressTap = false; return; }\n if (!isTap || !gpu || !pick) return;\n // Exact visible geometry first. The pick pass contains the same chairs and\n // people as the buyer view, with global seat IDs, so a foreground occupant\n // cannot be pierced by a projected anchor from a row behind.\n let idx = pickVisibleGeometry(e.clientX, e.clientY);\n if (focusedSectionId && idx >= 0) {\n const visibleSeatId = seatIdByIndex[idx];\n const visibleSectionId = visibleSeatId ? sectionIdBySeatId.get(visibleSeatId) : undefined;\n if (visibleSectionId && visibleSectionId !== focusedSectionId && focusSectionCamera(visibleSectionId)) return;\n }\n // Enlarged screen-space tolerance is only a fallback for the gaps between\n // exact geometry, preserving a forgiving touch target on mobile.\n if (focusedSectionId && idx < 0) {\n idx = pickNearestProjectedSeat(e.clientX, e.clientY, focusedSectionId, 44, false);\n }\n if (focusedSectionId && idx < 0) {\n // A buyer may wheel back out instead of pressing Overview. In that case\n // the first section is still the active seat-picking scope, but other\n // stands are visible and should remain valid navigation targets. If the\n // tap misses a bookable seat in the active section, transfer focus to the\n // visible stand under the pointer rather than silently retaining the old\n // section forever.\n // This is a stand-navigation target, not an exact ticket target: allow a\n // little space around dots so the tier/deck itself still feels clickable.\n const sectionIndex = pickNearestProjectedSeat(e.clientX, e.clientY, null, 72, false);\n const sectionSeatId = sectionIndex >= 0 ? seatIdByIndex[sectionIndex] : undefined;\n const nextSectionId = sectionSeatId ? sectionIdBySeatId.get(sectionSeatId) : undefined;\n if (nextSectionId && nextSectionId !== focusedSectionId && focusSectionCamera(nextSectionId)) return;\n }\n if (!focusedSectionId) {\n // Labels and the gaps between overview dots are still visibly part of a\n // stand. If the exact GPU mask misses, use a larger section-level target.\n if (idx < 0) idx = pickNearestProjectedSeat(e.clientX, e.clientY, null, 42, false);\n const overviewSeatId = idx >= 0 ? seatIdByIndex[idx] : undefined;\n const sectionId = overviewSeatId ? sectionIdBySeatId.get(overviewSeatId) : undefined;\n if (sectionId && focusSectionCamera(sectionId)) return;\n }\n if (idx < 0 || idx >= seatIdByIndex.length) {\n if (selection.size) {\n setSelection([]);\n opts.onViewTargetChange?.(null);\n }\n return;\n }\n const seatId = seatIdByIndex[idx];\n const seatState = SEAT_STATES[Math.round(model.seats.iState[idx] ?? 0)] ?? 'available';\n if (seatState !== 'available' && seatState !== 'selected') {\n // Held, sold and filter-dimmed inventory remains visible because it is\n // useful venue context. It must also be explainable when tapped, but it\n // must never receive the optimistic selection highlight or enter the\n // host's booking path.\n opts.onSeatInspect?.(seatId, seatState);\n return;\n }\n if (selection.has(seatId) && selection.size === 1) {\n setSelection([]);\n opts.onViewTargetChange?.(null);\n return;\n }\n setSelection([seatId]);\n ensureSeatView(seatId); // pre-render the panorama the moment the seat is picked\n analytics.seatPicked(seatId, sectionIdBySeatId.get(seatId));\n opts.onSeatPick?.(seatId);\n };\n glctx.canvas.addEventListener('pointerdown', onDown);\n glctx.canvas.addEventListener('pointermove', onMove);\n glctx.canvas.addEventListener('pointerup', onUp);\n glctx.canvas.addEventListener('pointercancel', onUp);\n\n loop.requestRender();\n\n const handle: Venue3DHandle = {\n setAvailability(updates) {\n const passthrough = mergeAvailabilityIntoSelection(selection, updates);\n const runs = applySeatStates(model.seats, passthrough);\n if (runs.length && gpu) gpu.uploadSeatStateRuns(runs);\n loop.requestRender();\n },\n setSelection,\n flyToSeat,\n focusOverview,\n resize() {\n const { width, height } = glctx.resize();\n orbit.setAspect(width / Math.max(1, height));\n layoutArriveChip();\n layoutNavigationChip();\n layoutOverviewChip();\n loop.requestRender();\n },\n stats() {\n return {\n ...loop.stats(),\n drawCalls: gpu ? gpu.drawCalls : 0,\n seatCount: model.seatCount,\n };\n },\n loseContextForTest() {\n glctx.simulateContextLossCycle();\n },\n floors(): SceneFloor[] {\n return model.floors;\n },\n focusFloor(index: number | null): boolean {\n if (index !== null && !model.floors.some((f) => f.index === index)) return false;\n const value = index ?? -1;\n // Floor/zone are overview rungs. Their result must not depend on whether\n // the buyer arrived from a detailed section (chairs already gathered) or\n // from venue overview (no detail gathered).\n setNearFieldDetailEnabled(false);\n setSeatViewLocked(false);\n if (gpu) {\n gpu.seatProgram.uniforms.uFocusFloor.value = value;\n gpu.solidProgram.uniforms.uFocusFloor.value = value;\n gpu.chairProgram.uniforms.uFocusFloor.value = value;\n }\n focusedFloor = value;\n setNavigationMode('orbit');\n if (index !== null) {\n const f = model.floors[index];\n if (f.seatCount > 0) {\n cinematic.cancel();\n orbit.frame({ center: f.center, radius: f.radius * 1.25 });\n }\n }\n loop.requestRender();\n return true;\n },\n zones(): SceneZone[] {\n return model.zones;\n },\n focusZone(zoneId: string): boolean {\n const zone = model.zones.find((z) => z.id === zoneId);\n if (!zone || zone.seatCount === 0) return false;\n setNearFieldDetailEnabled(false);\n setSeatViewLocked(false);\n cinematic.cancel();\n setNavigationMode('orbit');\n showSectionSeatLabels(null);\n opts.onSectionFocusChange?.(null);\n // Approach from the side the zone faces, so its seats present their fronts\n // rather than their backs — the same reasoning as the venue's intro shot.\n const dx = zone.focalWorld[0] - zone.center[0];\n const dz = zone.focalWorld[2] - zone.center[2];\n const azimuth = Math.hypot(dx, dz) > zone.radius * 0.12 ? Math.atan2(dx, dz) : undefined;\n // Padded past the zone's own radius: framing exactly to its edge reads as\n // cropped, and a buyer needs the neighbouring geometry to know where in the\n // venue they have landed.\n orbit.frame({ center: zone.center, radius: zone.radius * 1.25 }, false, azimuth);\n loop.requestRender();\n return true;\n },\n sections(): SceneSection[] {\n return model.sections;\n },\n focusSection(sectionId: string): boolean {\n return focusSectionCamera(sectionId);\n },\n rows(sectionId?: string): SceneRow[] {\n return sectionId ? model.rows.filter((row) => row.sectionId === sectionId) : model.rows;\n },\n seatsInRow(rowId: string): Array<{ id: string; label: string }> {\n return input.seats\n .filter((seat) => seat.rowId === rowId)\n .map((seat) => ({ id: seat.id, label: seat.displayLabel || seat.label }));\n },\n focusRow(rowId: string): boolean {\n const row = model.rows.find((candidate) => candidate.id === rowId);\n if (!row || row.seatCount === 0) return false;\n setNearFieldDetailEnabled(true);\n setSeatViewLocked(false);\n cinematic.cancel();\n setNavigationMode('pan');\n if (row.sectionId) {\n showSectionSeatLabels(row.sectionId);\n opts.onSectionFocusChange?.(row.sectionId);\n }\n orbit.frame({ center: row.center, radius: Math.max(2.5, row.radius * 0.65) });\n loop.requestRender();\n return true;\n },\n setReducedMotionForTest(value) {\n reducedForced = value;\n },\n dispose() {\n disposed = true;\n removeArriveChip();\n overviewChip.remove();\n navigationModeChip.remove();\n cancelFlight();\n loop.stop();\n labelOverlay.dispose();\n ro?.disconnect();\n if (panorama) { panorama.dispose(); panorama = null; }\n restorePanoramaLayer();\n restoreContainerPosition();\n glctx.canvas.removeEventListener('pointerdown', onDown);\n glctx.canvas.removeEventListener('pointermove', onMove);\n glctx.canvas.removeEventListener('pointerup', onUp);\n glctx.canvas.removeEventListener('pointercancel', onUp);\n orbit.dispose();\n if (pick) pick.dispose();\n if (gpu) gpu.dispose();\n gpu = null;\n pick = null;\n glctx.dispose();\n },\n };\n\n analytics.opened(\n model.seatCount,\n hasHeights,\n input.prepared?.buildMs ?? performance.now() - buildStartedAt,\n // Known synchronously at mount: either the caller handed us a scene\n // prepared by `prepareVenue3D` (which reports worker vs. main-thread\n // fallback) or we compiled it inline here. Never delays the event.\n input.prepared?.source ?? 'inline',\n );\n return handle;\n}\n","/**\n * OGL renderer + canvas lifecycle. Owns the single WebGL2 context (reused across\n * open/close so an embed never exhausts the browser's ~16-context cap), DPR\n * capping, resize, and WebGL context-loss survival.\n *\n * Context loss is handled by preventing the default (so the browser will restore)\n * and delegating rebuild to the caller: the JS-side SceneModel is the source of\n * truth, so `onContextRestored` re-uploads all GPU resources from it.\n */\n\nimport { Renderer } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\nimport { BACKGROUND } from '../palette';\n\nexport interface GLContextOptions {\n onContextLost: () => void;\n onContextRestored: () => void;\n}\n\n/** DPR ceiling: 2.0, or 1.5 on low-memory devices (fragment cost is DPR²). */\nfunction computeDpr(): number {\n const raw = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n const mem = (navigator as unknown as { deviceMemory?: number }).deviceMemory;\n const cap = typeof mem === 'number' && mem <= 4 ? 1.5 : 2.0;\n return Math.min(raw, cap);\n}\n\nexport class GLContext {\n readonly renderer: Renderer;\n readonly gl: OGLRenderingContext;\n readonly canvas: HTMLCanvasElement;\n private container: HTMLElement;\n /** Current clear colour, so a context restore repaints the themed background. */\n private clearRgb: [number, number, number] = [0, 0, 0];\n private lostHandler: (e: Event) => void;\n private restoredHandler: () => void;\n\n /**\n * Repaint the clear colour from the chart's theme.\n *\n * The clear shows for one frame before the background triangle draws, and on\n * any frame the scene does not cover — so leaving it at the library default\n * flashes SeatLayer grey into a white-labelled venue.\n */\n setClearColor(rgb: readonly number[]): void {\n this.clearRgb = [rgb[0], rgb[1], rgb[2]];\n this.gl.clearColor(rgb[0], rgb[1], rgb[2], 1);\n }\n\n /** The clear colour currently set (the pick pass restores through this). */\n get clearColor(): readonly number[] {\n return this.clearRgb;\n }\n\n constructor(container: HTMLElement, opts: GLContextOptions) {\n this.container = container;\n this.canvas = document.createElement('canvas');\n this.canvas.style.display = 'block';\n this.canvas.style.width = '100%';\n this.canvas.style.height = '100%';\n this.canvas.style.touchAction = 'none';\n this.canvas.tabIndex = 0;\n this.canvas.setAttribute('role', 'application');\n this.canvas.setAttribute(\n 'aria-label',\n 'Interactive 3D venue. Drag or use arrow keys to look around; pinch, scroll, plus or minus to zoom.',\n );\n\n this.renderer = new Renderer({\n canvas: this.canvas,\n dpr: computeDpr(),\n alpha: false,\n antialias: false,\n depth: true,\n stencil: false,\n powerPreference: 'high-performance',\n webgl: 2,\n });\n this.gl = this.renderer.gl;\n this.clearRgb = [BACKGROUND.top[0], BACKGROUND.top[1], BACKGROUND.top[2]];\n this.gl.clearColor(this.clearRgb[0], this.clearRgb[1], this.clearRgb[2], 1);\n\n container.appendChild(this.canvas);\n\n\n\n this.lostHandler = (e: Event) => {\n e.preventDefault();\n opts.onContextLost();\n };\n this.restoredHandler = () => opts.onContextRestored();\n this.canvas.addEventListener('webglcontextlost', this.lostHandler, false);\n this.canvas.addEventListener('webglcontextrestored', this.restoredHandler, false);\n\n this.resize();\n }\n\n /** Match the drawing buffer to the container's CSS box. */\n resize(): { width: number; height: number } {\n const w = Math.max(1, this.container.clientWidth || this.canvas.clientWidth || 1);\n const h = Math.max(1, this.container.clientHeight || this.canvas.clientHeight || 1);\n this.renderer.setSize(w, h);\n return { width: w, height: h };\n }\n\n get pixelHeight(): number {\n return this.renderer.height * this.renderer.dpr;\n }\n\n get aspect(): number {\n return this.renderer.width / Math.max(1, this.renderer.height);\n }\n\n dispose(): void {\n this.canvas.removeEventListener('webglcontextlost', this.lostHandler, false);\n this.canvas.removeEventListener('webglcontextrestored', this.restoredHandler, false);\n const ext = this.gl.getExtension('WEBGL_lose_context');\n if (ext) ext.loseContext();\n if (this.canvas.parentNode) this.canvas.parentNode.removeChild(this.canvas);\n }\n\n /**\n * Test hook: force a full loss→restore cycle. `restoreContext()` must be called\n * only AFTER the browser has dispatched `webglcontextlost` (calling it too soon\n * makes the browser drop the restore request), so we sequence it off a one-shot\n * listener rather than a fixed timeout.\n */\n simulateContextLossCycle(): void {\n const ext = this.gl.getExtension('WEBGL_lose_context') as\n | { loseContext(): void; restoreContext?: () => void }\n | null;\n if (!ext) return;\n ext.loseContext();\n // Chrome drops a restore requested too soon after loseContext(); a short\n // delay lets the loss settle before we ask for the context back.\n setTimeout(() => { if (ext.restoreContext) ext.restoreContext(); }, 300);\n }\n}\n","/**\n * view3d palette + seat-state model — the single source of colour truth for the\n * OGL venue view. Pure data (no GPU, no DOM) so the scene builder and the unit\n * tests share one definition. Colours are linear-ish RGB triplets in 0..1.\n *\n * Look brief (docs/3d-usp-strategy §3): desaturated cool greys for structure,\n * one warm accent for the stage, availability colours only on seats.\n */\n\nexport type SeatState3D = 'available' | 'held' | 'sold' | 'selected' | 'dimmed';\n\n/** Fixed LUT order — the per-instance `iState` float indexes this array, and the\n * fragment shader's `uStateColors` uniform is uploaded in exactly this order. */\nexport const SEAT_STATES: SeatState3D[] = ['available', 'held', 'sold', 'selected', 'dimmed'];\n\nexport function seatStateIndex(state: SeatState3D): number {\n const i = SEAT_STATES.indexOf(state);\n return i < 0 ? 0 : i;\n}\n\nexport type RGB = [number, number, number];\n\n/** Availability colours — the only saturated colours in the scene. */\nexport const SEAT_STATE_COLORS: Record<SeatState3D, RGB> = {\n available: [0.24, 0.82, 0.52],\n held: [0.95, 0.66, 0.22],\n sold: [0.34, 0.39, 0.45],\n selected: [0.24, 0.74, 1.0],\n dimmed: [0.28, 0.32, 0.37],\n};\n\n/**\n * A category colour as UPHOLSTERY — what that seat looks like from inside the\n * room rather than from above it.\n *\n * The same colour has two jobs at two ranges. On the map it is a price tier and\n * must be legible at a glance across a whole venue, so it is saturated. At eye\n * level it is a fabric-covered chair a metre away, and the saturated version\n * reads as moulded plastic: an amphitheatre whose tiers are pink, gold and blue\n * arrives at the seat looking like a toy.\n *\n * So the near-field chair wears a muted version — desaturated toward its own\n * luminance and darkened. Hue survives, which is the point: a buyer sitting in\n * the Terrace can still see where the Orchestra ends, they just are not sitting\n * in a bag of sweets. The DOT keeps the full colour, so nothing changes about\n * reading the map.\n */\nexport function upholsteryTone(rgb: RGB): RGB {\n const luma = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];\n const SAT = 0.42; // how much original hue survives\n const VALUE = 0.62; // and how much of its brightness\n return [\n (luma + (rgb[0] - luma) * SAT) * VALUE,\n (luma + (rgb[1] - luma) * SAT) * VALUE,\n (luma + (rgb[2] - luma) * SAT) * VALUE,\n ];\n}\n\n/** Flat LUT (5 × vec3) for the seat fragment shader uniform. */\nexport function seatStateColorLUT(): number[] {\n const out: number[] = [];\n for (const s of SEAT_STATES) out.push(...SEAT_STATE_COLORS[s]);\n return out;\n}\n\n/** Colour for a state index (from `iState`) — used to fill the per-instance\n * `iColor` attribute CPU-side, avoiding a dynamically-indexed array uniform. */\nexport function seatStateColorByIndex(index: number): RGB {\n const state = SEAT_STATES[index] ?? 'available';\n return SEAT_STATE_COLORS[state];\n}\n\n/** Structure palette — cool desaturated greys + one warm stage accent. */\nexport const STRUCTURE = {\n ground: [0.07, 0.085, 0.11] as RGB,\n tierTop: [0.24, 0.28, 0.34] as RGB,\n tierWall: [0.17, 0.20, 0.25] as RGB,\n /**\n * The lit performance surface.\n *\n * This was [0.42, 0.36, 0.26] and commented \"slightly emissive read\", which it\n * was not — under the scene's rig it resolved to a mid-brown slab, and from a\n * seat the stage was the DIMMEST large surface in a dark hall. That is exactly\n * backwards: the stage is the one place in a venue with light pointed at it,\n * and it is what a buyer's eye should land on when they arrive at their seat.\n *\n * Bright and warm enough to hold that role against the surrounding structure,\n * which sits around 0.24. An authored `fill` still tints it (see `buildShape`),\n * so an organizer's own stage colour survives — it is just no longer lit like\n * a basement.\n */\n stageTop: [0.94, 0.70, 0.38] as RGB,\n stageWall: [0.48, 0.33, 0.18] as RGB,\n decorTop: [0.22, 0.25, 0.29] as RGB,\n decorWall: [0.15, 0.17, 0.20] as RGB,\n gaTop: [0.24, 0.28, 0.33] as RGB,\n gaWall: [0.16, 0.19, 0.23] as RGB,\n /** Exhibition / trade-show booth stand. */\n boothTop: [0.30, 0.33, 0.38] as RGB,\n boothWall: [0.20, 0.23, 0.27] as RGB,\n /** Banquet table top — warmer, so a laid table reads apart from structure. */\n tableTop: [0.38, 0.34, 0.29] as RGB,\n tableWall: [0.24, 0.21, 0.18] as RGB,\n} as const;\n\n/** Background vertical gradient (matches the app's dark UI). */\nexport const BACKGROUND = {\n top: [0.05, 0.06, 0.08] as RGB,\n bottom: [0.10, 0.12, 0.15] as RGB,\n};\n\n/** Parse `#rrggbb` (or `#rgb`) to linear-ish 0..1 RGB; null on anything else. */\nexport function hexToRgb(hex: string | undefined): RGB | null {\n if (!hex) return null;\n let h = hex.trim();\n if (h[0] === '#') h = h.slice(1);\n if (h.length === 3) h = h.split('').map((c) => c + c).join('');\n if (h.length !== 6 || /[^0-9a-fA-F]/.test(h)) return null;\n const n = parseInt(h, 16);\n return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];\n}\n\n/** Mix two colours (a*(1-t) + b*t). */\nexport function mix(a: RGB, b: RGB, t: number): RGB {\n return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];\n}\n\n/** Desaturate toward its own luma by `amount` (0 = unchanged, 1 = grey). */\nexport function desaturate(c: RGB, amount: number): RGB {\n const l = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];\n return mix(c, [l, l, l], amount);\n}\n\n/** Scale a colour by a scalar (baked vertex AO), clamped to [0,1]. */\nexport function scaleRgb(c: RGB, k: number): RGB {\n return [Math.min(1, c[0] * k), Math.min(1, c[1] * k), Math.min(1, c[2] * k)];\n}\n","/**\n * Orbit + dolly + pan camera, damped. Drag = azimuth/polar; wheel/pinch = dolly;\n * right-drag, middle-drag or shift-drag = pan, as does a two-finger drag on\n * touch. Polar clamped [15°,80°]; distance clamped to bounds-derived limits; pan\n * clamped to a bounds-derived box; initial framing = a 3/4 view fitted to bounds.\n *\n * Pan was deliberately omitted in v1 (\"always centred on the venue focal point\"),\n * on the reasoning that a venue is a single object you orbit. That is true only\n * while the whole venue is in frame. Dolly in far enough to read seat labels — the\n * thing the low `minDist` exists to allow — and the pivot stays pinned to the\n * venue centre, so the upper tiers sit off-screen with no way to bring them back\n * except zooming out again. Reported from the marketing hero: \"if I want to move\n * this 3D down to see the upper part it is not possible.\"\n */\n\nimport { Camera, Vec3 } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\n\nconst DEG = Math.PI / 180;\nconst POLAR_MIN = 15 * DEG;\nconst POLAR_MAX = 80 * DEG;\nconst DAMP = 0.12;\nconst FOV = 35;\n/** Fit multiplier past a tight bounds-sphere fit. The 3/4 tilt makes the near\n * ground edge overhang below the fitted sphere, so a wide-shallow layout needs\n * more than a nominal 10% or its front row clips — this clears it while keeping\n * the venue centred with a comfortable margin. */\nconst FRAME_MARGIN = 1.25;\n\nexport interface OrbitBounds {\n center: [number, number, number];\n radius: number;\n /**\n * Optional world-metre axis-aligned extents of the venue. When present,\n * `frame()` fits the PROJECTED box at the target pose instead of the\n * enclosing sphere. The sphere is exact for chunky targets (sections, rows)\n * but a flat, wide floor's sphere is nearly all horizontal extent, and\n * sphere-fitting it backs the camera off until the venue is a postage stamp\n * floating in the canvas. Radius-only callers keep the sphere path.\n */\n box?: { min: [number, number, number]; max: [number, number, number] };\n}\n\nexport type PrimaryDragMode = 'orbit' | 'pan';\n\nexport class OrbitCamera {\n readonly camera: Camera;\n readonly fovY = FOV;\n private target = new Vec3();\n private azimuth = -30 * DEG;\n private polar = 55 * DEG;\n private distance = 10;\n private azT = -30 * DEG;\n private polT = 55 * DEG;\n private distT = 10;\n private minDist = 1;\n private maxDist = 100;\n private canvas: HTMLElement;\n private requestRender: () => void;\n /** Fired on the FIRST real user-driven orbit/dolly gesture (drag/wheel/pinch),\n * latched so it can drive a one-shot analytics event. Not the intro ease. */\n private onGesture?: () => void;\n private gestureFired = false;\n /** Seat-eye arrival is a fixed physical origin. The buyer may opt into the\n * dedicated look-around viewer, but venue orbit/dolly/pan must not pull the\n * camera outside the selected seat. */\n private interactionEnabled = true;\n\n private dragging = false;\n private panning = false;\n private lastX = 0;\n private lastY = 0;\n private activePointers = new Map<number, { x: number; y: number }>();\n private pinchDist = 0;\n private pinchCx = 0;\n private pinchCy = 0;\n /** Damped pan pivot. `target` chases this the way azimuth chases `azT`. */\n private targetT = new Vec3();\n /** Venue centre + radius, so pan can be clamped to somewhere still useful. */\n private panAnchor = new Vec3();\n private panLimit = 0;\n /** What an unmodified primary-button / one-finger drag does. Section drill-in\n * switches this to pan so a buyer can slide hidden seats into view without\n * discovering a modifier gesture. Shift temporarily inverts the mode. */\n private primaryDragMode: PrimaryDragMode = 'orbit';\n\n private onPointerDown: (e: PointerEvent) => void;\n private onPointerMove: (e: PointerEvent) => void;\n private onPointerUp: (e: PointerEvent) => void;\n private onWheel: (e: WheelEvent) => void;\n private onKeyDown: (e: KeyboardEvent) => void;\n private onContextMenu: (e: Event) => void;\n\n constructor(gl: OGLRenderingContext, canvas: HTMLElement, requestRender: () => void, onGesture?: () => void) {\n this.camera = new Camera(gl, { fov: FOV, near: 0.1, far: 5000, aspect: 1 });\n this.canvas = canvas;\n this.requestRender = requestRender;\n this.onGesture = onGesture;\n\n this.onPointerDown = (e) => {\n if (!this.interactionEnabled) return;\n // Guard: a synthetic/stale pointer id has no active pointer to capture.\n try { this.canvas.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (this.activePointers.size === 1) {\n // Secondary/middle always pans. For the primary button, Shift inverts\n // the visible Rotate/Move mode so both operations remain one gesture\n // away without a toolbar round-trip.\n const secondaryPan = e.button === 2 || e.button === 1;\n const primaryPan = e.button === 0\n && (e.shiftKey ? this.primaryDragMode === 'orbit' : this.primaryDragMode === 'pan');\n this.panning = secondaryPan || primaryPan;\n this.dragging = !this.panning;\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n } else if (this.activePointers.size === 2) {\n this.dragging = false;\n this.panning = false;\n this.pinchDist = this.currentPinchDistance();\n const c = this.pinchCentroid();\n this.pinchCx = c.x;\n this.pinchCy = c.y;\n }\n };\n this.onPointerMove = (e) => {\n if (!this.interactionEnabled) return;\n if (!this.activePointers.has(e.pointerId)) return;\n this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (this.activePointers.size >= 2) {\n const d = this.currentPinchDistance();\n // Fingers apart (d grows) → zoom in (distance shrinks).\n if (this.pinchDist > 0) { this.dollyBy(Math.exp((this.pinchDist - d) * 0.005)); this.fireGesture(); }\n this.pinchDist = d;\n // Two fingers moving TOGETHER pan; the pinch term above already took the\n // spreading component, so the centroid carries the translation.\n const c = this.pinchCentroid();\n this.panBy(c.x - this.pinchCx, c.y - this.pinchCy);\n this.pinchCx = c.x;\n this.pinchCy = c.y;\n return;\n }\n const dx = e.clientX - this.lastX;\n const dy = e.clientY - this.lastY;\n if (this.panning) {\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n if (dx !== 0 || dy !== 0) { this.panBy(dx, dy); this.fireGesture(); }\n return;\n }\n if (!this.dragging) return;\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n if (dx !== 0 || dy !== 0) this.fireGesture();\n this.azT -= dx * 0.006;\n this.polT = Math.max(POLAR_MIN, Math.min(POLAR_MAX, this.polT - dy * 0.006));\n this.requestRender();\n };\n this.onPointerUp = (e) => {\n this.activePointers.delete(e.pointerId);\n try { this.canvas.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n if (this.activePointers.size < 2) this.pinchDist = 0;\n if (this.activePointers.size === 0) { this.dragging = false; this.panning = false; }\n };\n // Right-drag pans, so the context menu must not interrupt the gesture.\n this.onContextMenu = (e) => { e.preventDefault(); };\n this.onWheel = (e) => {\n e.preventDefault();\n if (!this.interactionEnabled) return;\n // Normalise wheel delta across px / line / page modes, then clamp large\n // mouse-wheel bursts. Trackpads stay smooth while one wheel notch no\n // longer launches the camera through (or far away from) the venue.\n const unit = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? 100 : 1;\n const norm = Math.max(-2, Math.min(2, (e.deltaY * unit) / 100));\n this.dollyBy(Math.exp(norm * 0.22));\n this.fireGesture();\n };\n this.onKeyDown = (e) => {\n if (!this.interactionEnabled) return;\n const step = 7 * DEG;\n if (e.shiftKey && (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown')) {\n this.panBy(\n e.key === 'ArrowLeft' ? -32 : e.key === 'ArrowRight' ? 32 : 0,\n e.key === 'ArrowUp' ? -32 : e.key === 'ArrowDown' ? 32 : 0,\n );\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n this.azT += e.key === 'ArrowLeft' ? -step : step;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n this.polT = Math.max(POLAR_MIN, Math.min(POLAR_MAX, this.polT + (e.key === 'ArrowUp' ? -step : step)));\n } else if (e.key === '+' || e.key === '=') {\n this.dollyBy(0.82);\n } else if (e.key === '-' || e.key === '_') {\n this.dollyBy(1.22);\n } else {\n return;\n }\n e.preventDefault();\n this.fireGesture();\n this.requestRender();\n };\n\n canvas.addEventListener('pointerdown', this.onPointerDown);\n canvas.addEventListener('pointermove', this.onPointerMove);\n canvas.addEventListener('pointerup', this.onPointerUp);\n canvas.addEventListener('pointercancel', this.onPointerUp);\n canvas.addEventListener('wheel', this.onWheel, { passive: false });\n canvas.addEventListener('keydown', this.onKeyDown);\n canvas.addEventListener('contextmenu', this.onContextMenu);\n }\n\n private pinchCentroid(): { x: number; y: number } {\n const pts = [...this.activePointers.values()];\n if (!pts.length) return { x: 0, y: 0 };\n let x = 0;\n let y = 0;\n for (const p of pts) { x += p.x; y += p.y; }\n return { x: x / pts.length, y: y / pts.length };\n }\n\n /**\n * Slide the orbit pivot across the camera's own screen plane.\n *\n * Scaled by distance and FOV so a pixel of drag moves the same amount of VENUE\n * under the cursor whatever the zoom: at the overview a drag sweeps the whole\n * bowl, and pushed in among the seats it nudges. A fixed world-units-per-pixel\n * would be unusable at one end or the other.\n */\n private panBy(dxPx: number, dyPx: number): void {\n const h = (this.canvas as HTMLElement).clientHeight || 1;\n // World units spanned by one pixel at the pivot's depth.\n const perPx = (2 * this.distance * Math.tan((this.fovY * DEG) / 2)) / h;\n // Camera basis on the ground-plane projection: right is perpendicular to the\n // view azimuth; \"up\" on screen maps to the horizontal forward direction,\n // damped by the tilt so a top-down view pans in the plane you can see.\n const sinA = Math.sin(this.azimuth);\n const cosA = Math.cos(this.azimuth);\n const rightX = cosA;\n const rightZ = -sinA;\n const cp = Math.cos(this.polar);\n const sp = Math.sin(this.polar);\n const fwdX = -sinA * cp;\n const fwdZ = -cosA * cp;\n this.targetT.x += (-dxPx * rightX + dyPx * fwdX) * perPx;\n this.targetT.z += (-dxPx * rightZ + dyPx * fwdZ) * perPx;\n this.targetT.y += dyPx * sp * perPx;\n this.clampPan();\n this.requestRender();\n }\n\n /** Keep the pivot within a bounds-derived box so a stray drag cannot lose the\n * venue entirely — the Overview chip should never be the only way back. */\n private clampPan(): void {\n if (this.panLimit <= 0) return;\n const lim = this.panLimit;\n const cx = this.panAnchor.x;\n const cy = this.panAnchor.y;\n const cz = this.panAnchor.z;\n this.targetT.x = Math.max(cx - lim, Math.min(cx + lim, this.targetT.x));\n this.targetT.z = Math.max(cz - lim, Math.min(cz + lim, this.targetT.z));\n this.targetT.y = Math.max(cy - lim * 0.5, Math.min(cy + lim, this.targetT.y));\n }\n\n /** One-shot: notify the first real user gesture (drives 3d_orbit_engaged). */\n private fireGesture(): void {\n if (this.gestureFired) return;\n this.gestureFired = true;\n this.onGesture?.();\n }\n\n private currentPinchDistance(): number {\n const pts = [...this.activePointers.values()];\n if (pts.length < 2) return 0;\n return Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);\n }\n\n /** Multiply target distance by `factor` (proportional zoom — same feel near\n * and far), clamped so you can swoop right down among the seats. */\n private dollyBy(factor: number): void {\n this.distT = Math.max(this.minDist, Math.min(this.maxDist, this.distT * factor));\n this.requestRender();\n }\n\n /** Programmatic zoom used by the visible camera controls. Keeping it on the\n * same dolly path as wheel, pinch and keyboard preserves all distance limits. */\n zoomBy(factor: number): void {\n if (!this.interactionEnabled) return;\n this.dollyBy(factor);\n this.fireGesture();\n }\n\n /** Choose what a normal left/one-finger drag does. Shift temporarily performs\n * the other action. This is intentionally public so the view can make the\n * currently active gesture visible rather than hiding it in documentation. */\n setPrimaryDragMode(mode: PrimaryDragMode): void {\n this.primaryDragMode = mode;\n }\n\n /** Enable venue navigation or pin the current camera pose for seat-eye mode. */\n setInteractionEnabled(enabled: boolean): void {\n this.interactionEnabled = enabled;\n if (enabled) return;\n this.activePointers.clear();\n this.dragging = false;\n this.panning = false;\n this.pinchDist = 0;\n }\n\n /**\n * Fit a flattering 3/4 view to the bounds sphere. With `intro`, the camera\n * STARTS nearly top-down (matching the 2D map's orientation) and further out,\n * then the damped `update()` eases it up into the 3/4 architectural angle and\n * dollies in — the venue \"stands up\" instead of teleporting (~600ms).\n */\n frame(bounds: OrbitBounds, intro = false, stageAzimuth?: number, portraitCrop = false): void {\n this.target.set(bounds.center[0], bounds.center[1], bounds.center[2]);\n this.targetT.copy(this.target);\n this.panAnchor.copy(this.target);\n this.panLimit = Math.max(1, bounds.radius) * 1.5;\n const r = Math.max(1, bounds.radius);\n // Aspect-aware fit: the venue must clear BOTH the vertical and the\n // (aspect-narrowed) horizontal FOV, so a wide designer canvas frames the\n // chart centred with margin instead of parking it low-left. `setAspect` must\n // run before `frame` for the horizontal term to be correct.\n const halfV = (this.fovY * DEG) / 2;\n // A strict horizontal contain fit makes a wide bowl postage-stamp small in\n // portrait. UX prototypes may opt into a bounded crop: treat very narrow\n // screens as 0.82 aspect for OVERVIEW distance only, allowing the venue's\n // outer edges to sit just beyond the screen while its seats remain useful.\n const aspect = portraitCrop ? Math.max(0.82, this.camera.aspect || 1) : (this.camera.aspect || 1);\n const halfH = Math.atan(Math.tan(halfV) * aspect);\n // Enter from the STAGE side when the caller knows where the stage is: the\n // camera stands behind the stage looking into the bowl, so every tier faces\n // you on arrival. A fixed azimuth made the entry view a coin flip — charts\n // oriented the other way opened staring at the back of the shell.\n // (The pose is chosen BEFORE the fit: the projected-box fit below needs\n // the actual arrival angles to know how tall the venue really is on screen.)\n this.azT = stageAzimuth ?? -30 * DEG;\n this.polT = 55 * DEG;\n const fit = this.fitDistance(bounds, halfV, halfH, this.azT, this.polT);\n this.distT = fit * FRAME_MARGIN;\n // Low min so you can swoop down close enough that seat dots are big, tappable\n // targets (\"into your section\"); generous max to pull right back out.\n this.minDist = Math.max(2, r * 0.12);\n this.maxDist = fit * 4;\n if (intro) {\n this.azimuth = this.azT; // no spin — just tilt up + dolly in\n this.polar = 12 * DEG; // near top-down, like the flat 2D view\n this.distance = this.distT * 1.7;\n } else {\n this.azimuth = this.azT;\n this.polar = this.polT;\n this.distance = this.distT;\n }\n this.applyPosition();\n }\n\n setAspect(aspect: number): void {\n this.camera.perspective({ aspect });\n }\n\n /**\n * Damped return to the framed overview from wherever the camera is now (a\n * seat, behind the shell, anywhere): re-pivot on the venue centre without\n * moving the camera, then glide targets back to the 3/4 architectural pose.\n */\n frameSoft(bounds: OrbitBounds, stageAzimuth?: number, portraitCrop = false): void {\n this.target.set(bounds.center[0], bounds.center[1], bounds.center[2]);\n this.targetT.copy(this.target);\n this.panAnchor.copy(this.target);\n this.panLimit = Math.max(1, bounds.radius) * 1.5;\n this.syncFromCamera(); // re-derive pose around the new pivot — no snap\n this.camera.perspective({ fov: this.fovY, aspect: this.camera.aspect });\n const halfV = (this.fovY * DEG) / 2;\n const aspect = portraitCrop ? Math.max(0.82, this.camera.aspect || 1) : (this.camera.aspect || 1);\n const halfH = Math.atan(Math.tan(halfV) * aspect);\n this.azT = stageAzimuth ?? this.azimuth;\n this.polT = 55 * DEG;\n const fit = this.fitDistance(bounds, halfV, halfH, this.azT, this.polT);\n this.distT = fit * FRAME_MARGIN;\n }\n\n /**\n * Distance from the bounds centre at which the venue fits the frustum at\n * the given pose.\n *\n * Radius-only callers (section / zone / row focus) get the sphere fit —\n * exact enough for chunky targets, and their radii are hand-tuned to it.\n * When the caller supplies the venue's axis-aligned box, fit the box's\n * PROJECTED corners instead: a flat banquet floor's enclosing sphere is\n * nearly all horizontal extent, and sphere-fitting it backed the camera off\n * until the venue used a fraction of the canvas. Each corner demands\n * `lateral/tan(half-angle) − depth` of camera distance; the venue fits when\n * every corner does. Clamped to the sphere fit (which always contains the\n * box) and floored so a degenerate box cannot glue the camera to the floor.\n */\n private fitDistance(\n bounds: OrbitBounds,\n halfV: number,\n halfH: number,\n azimuth: number,\n polar: number,\n ): number {\n const r = Math.max(1, bounds.radius);\n const sphereFit = Math.max(r / Math.tan(halfV), r / Math.tan(halfH));\n const box = bounds.box;\n if (!box) return sphereFit;\n // View basis at the target pose: f points camera → target (the inverse of\n // applyPosition's offset); world +Y up, matching lookAt.\n const sp = Math.sin(polar);\n const fx = -sp * Math.sin(azimuth);\n const fy = -Math.cos(polar);\n const fz = -sp * Math.cos(azimuth);\n if (Math.abs(fy) > 0.999) return sphereFit; // top-down: right axis degenerates\n // right = normalize(f × up); trueUp = right × f.\n let rx = -fz, rz = fx;\n const rl = Math.hypot(rx, rz);\n rx /= rl; rz /= rl;\n const ux = -rz * fy;\n const uy = rz * fx - rx * fz;\n const uz = rx * fy;\n const tanH = Math.tan(halfH);\n const tanV = Math.tan(halfV);\n let fit = 1;\n for (let i = 0; i < 8; i++) {\n const px = (i & 1 ? box.max[0] : box.min[0]) - bounds.center[0];\n const py = (i & 2 ? box.max[1] : box.min[1]) - bounds.center[1];\n const pz = (i & 4 ? box.max[2] : box.min[2]) - bounds.center[2];\n const depth = px * fx + py * fy + pz * fz;\n const lx = Math.abs(px * rx + pz * rz);\n const ly = Math.abs(px * ux + py * uy + pz * uz);\n fit = Math.max(fit, lx / tanH - depth, ly / tanV - depth);\n }\n return Math.min(sphereFit, Math.max(fit, r * 0.35));\n }\n\n /**\n * Tighten the depth range around the actual venue bounds for the current pose.\n *\n * A fixed 0.1..5000 m frustum gives an ordinary arena centimetre-scale depth\n * precision at overview distance—coarser than the separation between a row\n * tread and its structural cap. The resulting z quantisation is the black\n * radial comb previously seen while zooming. A padded bounds sphere contains\n * every venue solid while preserving useful precision outside the bowl; when\n * the camera enters that sphere, the near plane safely returns to 5 cm.\n */\n updateClipping(bounds: OrbitBounds): void {\n const dx = this.camera.position.x - bounds.center[0];\n const dy = this.camera.position.y - bounds.center[1];\n const dz = this.camera.position.z - bounds.center[2];\n const distance = Math.hypot(dx, dy, dz);\n const paddedRadius = Math.max(1, bounds.radius) * 1.18;\n const near = Math.max(0.05, distance - paddedRadius);\n const far = Math.max(near + 10, distance + paddedRadius);\n this.camera.perspective({ near, far, aspect: this.camera.aspect });\n }\n\n /** Damp toward targets; returns true while still moving. */\n update(): boolean {\n const da = this.azT - this.azimuth;\n const dp = this.polT - this.polar;\n const dd = this.distT - this.distance;\n const tx = this.targetT.x - this.target.x;\n const ty = this.targetT.y - this.target.y;\n const tz = this.targetT.z - this.target.z;\n // Pan is damped on the same curve as the rest of the pose, and its epsilon is\n // scaled by distance: 1e-4 world units is imperceptible at the overview but\n // would keep a pushed-in camera redrawing forever.\n const panEps = Math.max(1e-4, this.distance * 1e-4);\n const moving = Math.abs(da) > 1e-4 || Math.abs(dp) > 1e-4 || Math.abs(dd) > 1e-4\n || Math.abs(tx) > panEps || Math.abs(ty) > panEps || Math.abs(tz) > panEps;\n this.azimuth += da * DAMP;\n this.polar += dp * DAMP;\n this.distance += dd * DAMP;\n this.target.x += tx * DAMP;\n this.target.y += ty * DAMP;\n this.target.z += tz * DAMP;\n if (moving) this.applyPosition();\n return moving;\n }\n\n /** Distance from camera to target (for LOD). */\n get currentDistance(): number {\n return this.distance;\n }\n\n /**\n * Re-derive the orbit's spherical state from the camera's CURRENT pose (after a\n * cinematic flight leaves it somewhere arbitrary), so a subsequent drag damps\n * from where it actually is with no snap. Does not move the camera.\n */\n syncFromCamera(): void {\n const dx = this.camera.position.x - this.target.x;\n const dy = this.camera.position.y - this.target.y;\n const dz = this.camera.position.z - this.target.z;\n const dist = Math.hypot(dx, dy, dz) || 1;\n const polar = Math.max(POLAR_MIN, Math.min(POLAR_MAX, Math.acos(Math.max(-1, Math.min(1, dy / dist)))));\n // Distance is NOT clamped here: a flight can park closer than minDist, and\n // clamping would jump the camera radially on the very first drag. The clamp\n // applies lazily from the next user-driven dolly (see dollyBy).\n this.distance = this.distT = dist;\n this.polar = this.polT = polar;\n this.azimuth = this.azT = Math.atan2(dx, dz);\n }\n\n /** Point the orbit pivot at a new world target without moving the camera. */\n setTarget(target: [number, number, number]): void {\n this.target.set(target[0], target[1], target[2]);\n this.targetT.copy(this.target);\n }\n\n /** Restore the base FOV (a flight ends pushed-in) and re-sync orbit state. A\n * flight ends looking at `target` (the venue focal), so re-pivot there first —\n * otherwise the first drag would `lookAt(bounds.center)` and pop the aim. */\n resumeAfterFlight(target?: [number, number, number]): void {\n this.camera.perspective({ fov: this.fovY, aspect: this.camera.aspect });\n if (target) this.target.set(target[0], target[1], target[2]);\n this.targetT.copy(this.target);\n this.syncFromCamera();\n }\n\n private applyPosition(): void {\n const sp = Math.sin(this.polar);\n const x = this.target.x + this.distance * sp * Math.sin(this.azimuth);\n const y = this.target.y + this.distance * Math.cos(this.polar);\n const z = this.target.z + this.distance * sp * Math.cos(this.azimuth);\n this.camera.position.set(x, y, z);\n this.camera.lookAt(this.target);\n }\n\n dispose(): void {\n this.canvas.removeEventListener('pointerdown', this.onPointerDown);\n this.canvas.removeEventListener('pointermove', this.onPointerMove);\n this.canvas.removeEventListener('pointerup', this.onPointerUp);\n this.canvas.removeEventListener('pointercancel', this.onPointerUp);\n this.canvas.removeEventListener('wheel', this.onWheel);\n this.canvas.removeEventListener('keydown', this.onKeyDown);\n this.canvas.removeEventListener('contextmenu', this.onContextMenu);\n this.activePointers.clear();\n }\n}\n","/**\n * Dirty-flag render loop. A frame is drawn only while the camera is moving /\n * damping or an availability update arrived; when idle, no rAF is scheduled at\n * all (zero CPU/GPU when parked). Tracks an FPS EMA over frames actually\n * rendered — it reads as \"idle\" when nothing is scheduled.\n */\n\nexport interface RenderLoopStats {\n fps: number;\n rendered: number;\n idle: boolean;\n}\n\nexport class RenderLoop {\n private frame: (dt: number) => boolean;\n private rafId = 0;\n private running = false;\n private lastTime = 0;\n private fpsEma = 0;\n private rendered = 0;\n\n /** `frame(dt)` renders one frame and returns true if another is needed. */\n constructor(frame: (dt: number) => boolean) {\n this.frame = frame;\n }\n\n requestRender(): void {\n if (this.running) return;\n this.running = true;\n this.lastTime = 0;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n private tick = (now: number): void => {\n const dt = this.lastTime ? (now - this.lastTime) / 1000 : 1 / 60;\n this.lastTime = now;\n if (dt > 0) {\n const instFps = 1 / dt;\n this.fpsEma = this.fpsEma ? this.fpsEma * 0.9 + instFps * 0.1 : instFps;\n }\n this.rendered++;\n const again = this.frame(dt);\n if (again) {\n this.rafId = requestAnimationFrame(this.tick);\n } else {\n this.running = false;\n this.rafId = 0;\n }\n };\n\n stats(): RenderLoopStats {\n return { fps: this.running ? Math.round(this.fpsEma) : 0, rendered: this.rendered, idle: !this.running };\n }\n\n stop(): void {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.running = false;\n }\n}\n","/**\n * The seat level-of-detail ladder.\n *\n * Three rungs, all driven by how far a seat is from the CAMERA rather than by a\n * global switch, so one venue can be showing all three at once — chairs in the\n * row you are sitting in, dots across the bowl, a dissolving tint on the far\n * side:\n *\n * near (≤ CHAIR_FULL_M) extruded chair silhouettes (scene/seatChair.ts)\n * band (…CHAIR_NONE_M) chair scales in / dot fades out — no pop\n * far the instanced billboard dot, as before\n *\n * Everything below is pure so the thresholds can be reasoned about (and tested)\n * without a GL context. The shaders re-derive the same weight per instance from\n * view depth; these constants are the single source both sides read.\n */\n\nexport interface SeatLod {\n /** Multiplier on the seat world radius (1 = full). */\n scale: number;\n /** Fade toward the tier/fade colour (0 = pure state colour). */\n fade: number;\n /**\n * Minimum screen size a dot is allowed to demand, in pixels.\n *\n * The shader holds this floor by GROWING a dot's world radius with depth, and\n * that growth is what smears neighbouring rows into one mass at range: measured\n * mean seat spacing is 0.53–0.58 m against a 0.44 m dot diameter, so a dot only\n * has to grow ~20 % before it touches its neighbour. `iMaxRadius` caps each dot\n * at its own pitch, but a capped dot then sits at its ceiling across the whole\n * far half of the venue, which is exactly the solid-mass look.\n *\n * So the DEMAND is tapered as well as the growth capped. Past the far knee the\n * requested floor relaxes toward SEAT_MIN_PIXELS_FAR, the dots stop pressing\n * against their ceilings, and the gap between rows survives to the back wall.\n * Legibility past that point is the tier tint's job, not the dot's — which is\n * what `fade` above is for.\n */\n minPixels: number;\n}\n\n/** Pixel floor a dot demands in the near/mid field — unchanged from v1. */\nexport const SEAT_MIN_PIXELS_NEAR = 2.5;\n/**\n * Pixel floor a dot demands at full distance.\n *\n * Not zero: a dot that demands nothing shrinks below a pixel, aliases, and\n * shimmers as the camera moves. 1.15 keeps a sub-pixel dot anchored while\n * leaving it far enough under its pitch ceiling that its neighbour stays\n * separate. The fragment stage already dissolves anything that cannot afford\n * even this (`vBudget`).\n */\nexport const SEAT_MIN_PIXELS_FAR = 1.15;\n\n/**\n * View depth, in metres, at or below which a seat draws as a full chair.\n *\n * Six metres is close enough for the chair silhouette to be readable. The old\n * 12 m threshold turned aligned chair edges into black moiré streaks while a\n * buyer was still looking at an entire bowl.\n */\nexport const CHAIR_FULL_M = 6;\n\n/**\n * View depth at and beyond which a seat is a dot again.\n *\n * Beyond ten metres the dot is the clearer representation. A smooth four-metre\n * handover still avoids popping during flight without introducing tiny chair\n * geometry at overview or ordinary section distance.\n */\nexport const CHAIR_NONE_M = 10;\n\n/**\n * Occupants are a close-range detail, not an overview availability marker.\n *\n * At the chair handover distance a person is only a few pixels tall. Thousands\n * of those tiny dark silhouettes alias into radial streaks across a bowl, so\n * people use a tighter band than the furniture they sit on. Seat dots continue\n * to carry availability/state at every distance.\n */\nexport const OCCUPANT_FULL_M = 6;\nexport const OCCUPANT_NONE_M = 9.5;\n/** Camera comfort bubble: do not let a foreground head swallow a seat-eye view. */\nexport const OCCUPANT_NEAR_NONE_M = 0.9;\nexport const OCCUPANT_NEAR_FULL_M = 2.2;\n\n/**\n * Radius, in metres, of the CPU gather that decides which seats are handed to\n * the chair mesh at all.\n *\n * Strictly greater than CHAIR_NONE_M + CHAIR_REBUILD_M. That inequality is the\n * whole no-pop argument: a seat always enters the instanced set while it is\n * still beyond the fade band (weight 0, drawn as nothing), so by the time it is\n * close enough to be visible it has already been in the set for a while. A seat\n * can never blink into existence mid-band, whatever the camera does between two\n * gathers. `chairGatherCoversFadeBand()` asserts it.\n */\nexport const CHAIR_GATHER_M = 30;\n\n/**\n * How far the camera may travel before the gathered set is rebuilt.\n *\n * The gather is the only per-frame CPU cost the near field adds, so it is not\n * run per frame. Four metres against the large gather/fade slack leaves ample\n * room for a seat to enter invisibly before its chair begins to grow.\n */\nexport const CHAIR_REBUILD_M = 4;\n\n/**\n * Hard ceiling on instances handed to the chair mesh.\n *\n * The cap is what makes the near field's cost bounded rather than proportional\n * to venue size, and seats are gathered nearest-first, so it only ever drops the\n * OUTER edge of the disc.\n *\n * Its value is not a round number picked for comfort — it is sized against\n * measured venue density, because a cap that bites inside the fade band would\n * silently reintroduce popping on exactly the biggest venues. Worst-case seats\n * within a radius, measured by standing on 60 sampled seats of each shipped\n * chart:\n *\n * 21 m 26 m 30 m\n * megaStadium 4,278 5,905 7,297\n * uberArena 2,670 3,690 4,585\n * amphitheatre 2,326 2,873 3,211\n *\n * megaStadium's figures fell slightly when its ROW_GAP went 28 → 30 to clear the\n * corner-row spacing floor (see `src/core/megaStadium.ts`): rows further apart\n * is strictly less dense, so the headroom below only grew.\n *\n * The original, more distant chair band required coverage through 26 m, which\n * megaStadium met at 5,905 instances. The tighter readable-detail band now only\n * requires CHAIR_NONE_M + CHAIR_REBUILD_M; retaining the measured 8,192 cap\n * therefore adds substantial safety margin for denser charts.\n * `chairCapCoversFadeBand()` is the\n * executable form of this.\n */\nexport const CHAIR_MAX_INSTANCES = 8192;\n\n/**\n * The near/far ladder invariant, as a value rather than a comment.\n *\n * Exported so the test suite asserts the actual constants instead of restating\n * them: if someone widens the fade band or slows the rebuild without widening\n * the gather, seats start popping in mid-band and this goes false.\n */\nexport function chairGatherCoversFadeBand(): boolean {\n return CHAIR_GATHER_M > CHAIR_NONE_M + CHAIR_REBUILD_M;\n}\n\n/** The radius the gathered set must still reach after the cap has bitten. */\nexport const CHAIR_REQUIRED_COVER_M = CHAIR_NONE_M + CHAIR_REBUILD_M;\n\n/**\n * Does a venue holding `seatsWithinRequiredCover` seats inside\n * CHAIR_REQUIRED_COVER_M still get every one of them into the capped set?\n *\n * The companion to `chairGatherCoversFadeBand`. That one checks the RADIUS is\n * generous enough; this checks the CAP is, which is the failure mode that only\n * appears on the densest chart and is therefore the one most likely to ship.\n */\nexport function chairCapCoversFadeBand(seatsWithinRequiredCover: number): boolean {\n return seatsWithinRequiredCover <= CHAIR_MAX_INSTANCES;\n}\n\n/** GLSL `smoothstep`, so the JS side and the shaders agree exactly. */\nfunction smoothstep(edge0: number, edge1: number, x: number): number {\n const t = Math.min(1, Math.max(0, (x - edge0) / Math.max(1e-6, edge1 - edge0)));\n return t * t * (3 - 2 * t);\n}\n\n/**\n * How much chair a seat at `depth` metres of view depth shows: 1 = full\n * silhouette, 0 = nothing drawn (the dot carries it).\n *\n * The chair SCALES IN rather than fading in. A cross-fade would need the chairs\n * to be transparent through the whole band, and a transparent chair either\n * writes depth (and punches holes in the chairs behind it) or does not (and\n * shows its own back panel through its own seat pad). Growing an OPAQUE chair\n * out of the seat point has neither problem, and it lands where the dot already\n * is: at weight 0 the chair is a zero-size nub exactly at the seat centre, under\n * a dot at full opacity.\n *\n * Mirrored in CHAIR_VERT / SEAT_VERT — keep the three in step.\n */\nexport function chairWeight(depth: number): number {\n return 1 - smoothstep(CHAIR_FULL_M, CHAIR_NONE_M, depth);\n}\n\n/**\n * Display weight for signed view depth. An anchor behind/on the camera must be\n * collapsed, not treated as a zero-distance chair: a near-plane-crossing chair\n * expands into long black triangles during overview/zoom transitions.\n */\nexport function chairViewWeight(viewDepth: number): number {\n return viewDepth > 0.05 ? chairWeight(viewDepth) : 0;\n}\n\n/** How much readable occupant detail is shown at `depth`: 1 = full, 0 = none. */\nexport function occupantWeight(depth: number): number {\n const near = smoothstep(OCCUPANT_NEAR_NONE_M, OCCUPANT_NEAR_FULL_M, depth);\n const far = 1 - smoothstep(OCCUPANT_FULL_M, OCCUPANT_NONE_M, depth);\n return near * far;\n}\n\n/** The dot's share of a seat: the exact complement of `chairWeight`. */\nexport function dotWeight(depth: number): number {\n return 1 - chairWeight(depth);\n}\n\n/**\n * The chair's linear size at `depth`, as a fraction of full size.\n *\n * NOT the same curve as `chairWeight`, and the difference is what the middle of\n * the band actually looks like. Scaling linearly with the weight put a\n * quarter-size chair inside a full-opacity dot for most of the crossover: the\n * dot is ~0.44 m across and holds its size until it is handed over, so a chair\n * at 25 % read as a speck floating inside a blob rather than as a seat.\n *\n * The square root front-loads the growth — half a weight is 71 % of full size —\n * so by the time the dot has faded to half, the chair underneath it is nearly\n * the same size and the two genuinely overlay each other. It still passes\n * through exactly 0 at CHAIR_NONE_M, so nothing pops into existence.\n */\nexport function chairScale(depth: number): number {\n return Math.sqrt(chairWeight(depth));\n}\n\nexport function computeSeatLod(distance: number, radius: number): SeatLod {\n const near = radius * 1.4;\n const far = radius * 3.2;\n if (distance <= near) return { scale: 1, fade: 0, minPixels: SEAT_MIN_PIXELS_NEAR };\n const t = Math.min(1, (distance - near) / Math.max(1e-3, far - near));\n return {\n scale: 1 - t * 0.4,\n fade: t * 0.55,\n // Tapered on the same ramp as the fade: as the block starts reading by its\n // tier tint, the dots stop fighting each other for pixels.\n minPixels: SEAT_MIN_PIXELS_NEAR + t * (SEAT_MIN_PIXELS_FAR - SEAT_MIN_PIXELS_NEAR),\n };\n}\n","/**\n * \"Which seats are close enough to be worth drawing as chairs?\" — a uniform XZ\n * grid over the seat cloud, queried nearest-cell-first.\n *\n * This is the piece that makes the near field affordable. The chair mesh could\n * simply carry every seat and let the vertex stage collapse the far ones to\n * zero size, but that is 52,000 × 72 vertices of shading per frame to draw a few\n * hundred chairs — the whole 60 fps claim, spent on geometry that is discarded.\n * Instead the CPU hands the mesh a COMPACT set bounded by CHAIR_MAX_INSTANCES,\n * so near-field cost is a function of the camera's neighbourhood and not of\n * venue size.\n *\n * The grid is built lazily on the first gather: a camera that never comes down\n * to the deck never pays for it, and the intro pose is always far out.\n */\n\n/** Target seats per cell. Small enough to prune hard, large enough that the\n * bucket arrays stay short and cache-friendly. */\nconst SEATS_PER_CELL = 4;\n\nexport class NearFieldIndex {\n private readonly iPosition: Float32Array;\n private readonly count: number;\n private minX = 0;\n private minZ = 0;\n private cell = 1;\n private cols = 1;\n private rows = 1;\n /** CSR-style buckets: `cellStart[c]…cellStart[c+1]` indexes into `cellItems`. */\n private cellStart: Int32Array | null = null;\n private cellItems: Int32Array | null = null;\n\n constructor(iPosition: Float32Array, count: number) {\n this.iPosition = iPosition;\n this.count = count;\n }\n\n /** Built on demand; safe to call repeatedly. */\n private ensureGrid(): void {\n if (this.cellStart || this.count === 0) return;\n const p = this.iPosition;\n let minX = Infinity, minZ = Infinity, maxX = -Infinity, maxZ = -Infinity;\n for (let i = 0; i < this.count; i++) {\n const x = p[i * 3], z = p[i * 3 + 2];\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (z < minZ) minZ = z;\n if (z > maxZ) maxZ = z;\n }\n const w = Math.max(maxX - minX, 1e-3);\n const h = Math.max(maxZ - minZ, 1e-3);\n this.cell = Math.max(Math.sqrt((w * h * SEATS_PER_CELL) / this.count), 0.25);\n this.cols = Math.max(1, Math.ceil(w / this.cell) + 1);\n this.rows = Math.max(1, Math.ceil(h / this.cell) + 1);\n this.minX = minX;\n this.minZ = minZ;\n // Counting sort into CSR — no per-cell arrays, one pass to count and one to\n // place. At 52k seats this is two linear scans and two typed allocations.\n const nCells = this.cols * this.rows;\n const start = new Int32Array(nCells + 1);\n const cellOf = (i: number): number => {\n const cx = Math.min(this.cols - 1, Math.max(0, Math.floor((p[i * 3] - minX) / this.cell)));\n const cz = Math.min(this.rows - 1, Math.max(0, Math.floor((p[i * 3 + 2] - minZ) / this.cell)));\n return cz * this.cols + cx;\n };\n for (let i = 0; i < this.count; i++) start[cellOf(i) + 1]++;\n for (let c = 0; c < nCells; c++) start[c + 1] += start[c];\n const items = new Int32Array(this.count);\n const cursor = start.slice(0, nCells);\n for (let i = 0; i < this.count; i++) items[cursor[cellOf(i)]++] = i;\n this.cellStart = start;\n this.cellItems = items;\n }\n\n /**\n * Fill `out` with the indices of seats within `radius` metres of (camX, camZ),\n * nearest cell-ring first, and return how many were written.\n *\n * Ring order is what makes the CHAIR_MAX_INSTANCES cap harmless: when the cap\n * bites it drops the OUTERMOST seats, which are the ones already past the fade\n * band and drawing nothing. A cap that truncated in index order would instead\n * punch holes in the row you are sitting in.\n *\n * Note this is a horizontal (XZ) query and ignores height. A stacked venue's\n * upper tier is therefore gathered along with the stalls beneath it — which is\n * correct, because the fade weight is re-derived from true view depth in the\n * shader anyway. The grid's only job is to bound the candidate set.\n */\n gather(camX: number, camZ: number, radius: number, out: Int32Array): number {\n this.ensureGrid();\n const start = this.cellStart;\n const items = this.cellItems;\n if (!start || !items) return 0;\n const cap = out.length;\n const r2 = radius * radius;\n const cx = Math.floor((camX - this.minX) / this.cell);\n const cz = Math.floor((camZ - this.minZ) / this.cell);\n const maxRing = Math.ceil(radius / this.cell) + 1;\n const p = this.iPosition;\n let n = 0;\n for (let ring = 0; ring <= maxRing && n < cap; ring++) {\n const z0 = cz - ring, z1 = cz + ring;\n const x0 = cx - ring, x1 = cx + ring;\n for (let gz = z0; gz <= z1 && n < cap; gz++) {\n if (gz < 0 || gz >= this.rows) continue;\n // Only the newly-exposed cells of this ring: the full row on the top and\n // bottom edges, just the two end cells in between.\n const edge = (gz === z0 || gz === z1);\n for (let gx = x0; gx <= x1 && n < cap; gx++) {\n if (!edge && gx !== x0 && gx !== x1) { gx = x1 - 1; continue; }\n if (gx < 0 || gx >= this.cols) continue;\n const c = gz * this.cols + gx;\n for (let k = start[c], e = start[c + 1]; k < e && n < cap; k++) {\n const i = items[k];\n const dx = p[i * 3] - camX;\n const dz = p[i * 3 + 2] - camZ;\n if (dx * dx + dz * dz <= r2) out[n++] = i;\n }\n }\n }\n }\n return n;\n }\n}\n","/**\n * SeatMap chart document — the single source of truth shared by the\n * Designer (authoring) and the Renderer (buyer picker).\n *\n * Coordinates are abstract units (roughly \"pixels at scale 1\"); the renderer\n * fits the chart to its container.\n * Rows are PARAMETRIC (origin + count + spacing + curve + rotation) — never\n * store per-seat coordinates in the document; expansion happens in layout.ts.\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface CubicPath {\n start: Point;\n control1: Point;\n control2: Point;\n end: Point;\n}\n\n/** A real closed section boundary. `outline` remains its sampled collision and\n * persistence fallback; this path is the smooth authoring/buyer paint source. */\nexport type SectionPathSegment =\n | { kind: 'line'; end: Point }\n | { kind: 'arc'; center: Point; radius: number; clockwise: boolean; end: Point }\n | { kind: 'bezier'; control1: Point; control2: Point; end: Point };\n\nexport interface SectionOutlinePath {\n version: 1;\n closed: true;\n start: Point;\n segments: SectionPathSegment[];\n}\n\nexport interface Category {\n key: string;\n label: string;\n color: string;\n /** Base price — used when the category has no explicit tiers. */\n price?: number;\n /** Durable evidence for semantic/display facts proposed while converting a\n * private reference into sellable inventory. */\n referenceCategorySource?: ReferenceCategorySource;\n /**\n * Ticket tiers (Adult / Child / Senior…). When present, a buyer picks a tier\n * per seat in this category and the tier's price applies; the first tier is the\n * default. Per-category (not per-seat) pricing — see Batch 3.5. Empty/absent =\n * a single price (the `price` above).\n */\n tiers?: CategoryTier[];\n}\n\nexport interface ReferenceCategorySource {\n assetId: string;\n /** Original sampled section color. `Category.color` is the approved output\n * color and may differ only with separately recorded evidence. */\n sourceColor: string;\n /** Every exact sampled color normalized into the same original 4-bit/channel\n * segmentation class. Older documents may contain only `sourceColor`. */\n sourceColors?: string[];\n /** Logical sections whose generated inventory uses this category. */\n logicalSectionIds?: string[];\n /** Source-color grouping is deterministic; a semantic regrouping needs its\n * own confirmed assignment evidence. */\n assignmentDerivation?: 'source-color-class' | 'confirmed-logical-sections';\n assignmentEvidence?: 'user-confirmed' | 'authoritative-source';\n assignmentSourceDescription?: string;\n /** Optional legend/commercial swatch stated by the source. This remains\n * immutable provenance when the approved accessible output color differs. */\n sourcePaletteColor?: string;\n sourcePaletteColorEvidence?: 'user-confirmed' | 'authoritative-source';\n sourcePaletteColorSourceDescription?: string;\n labelEvidence: 'user-confirmed' | 'authoritative-source';\n labelSourceDescription: string;\n priceEvidence: 'user-confirmed' | 'authoritative-source';\n priceSourceDescription: string;\n outputColorEvidence?: 'user-confirmed' | 'authoritative-source';\n outputColorSourceDescription?: string;\n}\n\n/** One ticket tier within a category: a named price (Adult, Child, Senior…). */\nexport interface CategoryTier {\n id: string;\n name: string;\n price: number;\n}\n\n/**\n * Accessibility accommodations a seat can carry. Mirrors the taxonomy real\n * venues (and seats.io) expose so buyers can filter for exactly what they need\n * and organizers can mark seats precisely. `wheelchair` is the legacy default.\n */\nexport type AccessibilityType =\n | 'wheelchair'\n | 'companion'\n | 'semi-ambulatory'\n | 'hearing'\n | 'cart'\n | 'sign-language'\n | 'plus-size'\n | 'lift-armrest';\n\nexport interface AccessibilityMeta {\n key: AccessibilityType;\n /** Full descriptive label (designer checkbox, picker legend). */\n label: string;\n /** Compact label for chips/badges. */\n short: string;\n /** Single-glyph badge shown on seat chips + filter chips. */\n icon: string;\n}\n\n/** Ordered taxonomy — drives the designer seat panel and the picker filters. */\nexport const ACCESSIBILITY_TYPES: AccessibilityMeta[] = [\n { key: 'wheelchair', label: 'Wheelchair space', short: 'Wheelchair', icon: '♿' },\n { key: 'companion', label: 'Companion seat', short: 'Companion', icon: '🧑🤝🧑' },\n { key: 'semi-ambulatory', label: 'Semi-ambulatory (limited mobility)', short: 'Limited mobility', icon: '🦯' },\n { key: 'hearing', label: 'Assistive listening', short: 'Hearing', icon: '🦻' },\n { key: 'cart', label: 'CART live-caption view', short: 'CART captions', icon: 'CC' },\n { key: 'sign-language', label: 'Sign-language view', short: 'Sign language', icon: '🤟' },\n { key: 'plus-size', label: 'Plus-size seat', short: 'Plus-size', icon: '💺' },\n { key: 'lift-armrest', label: 'Lift-up armrest', short: 'Lift armrest', icon: '↕️' },\n];\n\nconst ACCESSIBILITY_LABEL = new Map(ACCESSIBILITY_TYPES.map((a) => [a.key, a]));\n\n/** Metadata for one accessibility key (undefined for unknown keys). */\nexport function accessibilityMeta(key: AccessibilityType): AccessibilityMeta | undefined {\n return ACCESSIBILITY_LABEL.get(key);\n}\n\n/**\n * Outer-ring colour per accommodation. Shared by the buyer picker and the\n * designer canvas so an accessible seat reads with the same hue in both — the\n * seat's first-listed type wins. `wheelchair` blue is the default fallback.\n */\nexport const ACCESSIBILITY_RING_COLOR: Record<AccessibilityType, string> = {\n wheelchair: '#3b82f6',\n companion: '#8b5cf6',\n 'semi-ambulatory': '#0ea5e9',\n hearing: '#14b8a6',\n cart: '#7c3aed',\n 'sign-language': '#f59e0b',\n 'plus-size': '#ec4899',\n 'lift-armrest': '#22c55e',\n};\n\n/** Ring colour for a seat's accessibility set (first-listed type wins). */\nexport function accessibilityRingColor(types: AccessibilityType[] | undefined): string {\n const primary = types?.[0];\n return (primary && ACCESSIBILITY_RING_COLOR[primary]) || '#3b82f6';\n}\n\nexport type SeatViewCoverage =\n | 'exact-seat'\n | 'row-representative'\n | 'section-representative'\n | 'venue-representative';\n\n/** Calibration and truth metadata that travels with an organizer panorama.\n * The URL remains separate for backward compatibility with existing charts. */\nexport interface SeatViewMetadata {\n projection?: 'equirectangular';\n coverage?: SeatViewCoverage;\n /** Optional lightweight first paint. `viewFromSeatUrl` remains the sharp source. */\n previewUrl?: string;\n previewWidth?: number;\n previewHeight?: number;\n /** Degrees relative to the authored focal/stage direction. */\n initialBearingDeg?: number;\n /** Positive looks up. The viewer clamps to its safe pitch range. */\n initialPitchDeg?: number;\n capturedAt?: string;\n /** Buyer-safe provenance, e.g. \"Provided by the venue\". */\n sourceLabel?: string;\n sourceWidth?: number;\n sourceHeight?: number;\n captureEyeHeightM?: number;\n eventConfigurationId?: string;\n}\n\n/** How broadly a confidence record applies to the selected inventory unit.\n * This is resolved by the layout engine from the authoring scope; authors do\n * not get to label venue-wide evidence as exact-seat evidence. */\nexport type SeatConfidenceCoverage =\n | 'exact-seat'\n | 'row-representative'\n | 'section-representative'\n | 'floor-representative'\n | 'venue-representative';\n\nexport type SeatConfidenceModelLevel =\n | 'generated'\n | 'geometry-verified'\n | 'configuration-verified';\n\nexport type SeatConfidenceRealityLevel =\n | 'none'\n | 'comparable-build-reference'\n | 'actual-build-checked';\n\n/** A bounded, explainable visibility result. It is deliberately a modeled\n * target score—not a promise that this percentage of a show will be visible. */\nexport interface SeatConfidenceModeledTarget {\n label: string;\n clearRayCount: number;\n totalRayCount: number;\n classification: 'clear' | 'partial' | 'obstructed';\n /** Buyer-safe method name, e.g. \"45-ray screen target model\". */\n methodLabel: string;\n occluderLabels?: string[];\n}\n\n/** Versioned source evidence for a seat-confidence passport.\n *\n * Model verification and reality evidence are independent dimensions. A\n * comparable photograph must never outrank a venue-approved current event\n * model, and a strong model must never silently become real-world evidence.\n * `coverage` is added only after scope resolution on ExpandedSeat. */\nexport interface SeatConfidenceEvidence {\n schema: 'seatlayer.seat-confidence/1';\n evidenceId: string;\n modelVersion: string;\n evidenceKind: 'synthetic-model' | 'organizer-approved' | 'calibrated-provider';\n modelLevel: SeatConfidenceModelLevel;\n realityLevel?: SeatConfidenceRealityLevel;\n /** Required for configuration verification and actual-build checking. */\n eventConfigurationId?: string;\n assessedAt?: string;\n validUntil?: string;\n /** Buyer-safe approving organization or role; never a named individual. */\n approvedByRole?: string;\n /** Buyer-safe evidence provenance, not an asset URL or private filename. */\n sourceLabel?: string;\n limitations?: string[];\n modeledTarget?: SeatConfidenceModeledTarget;\n}\n\nexport interface ResolvedSeatConfidenceEvidence extends SeatConfidenceEvidence {\n coverage: SeatConfidenceCoverage;\n}\n\nexport interface SeatOverride {\n /** 0-based seat index within the row. */\n index: number;\n /** Physical seat absent (pillar, sound desk) — numbering gap preserved. */\n skip?: boolean;\n /** Position nudge in chart units. */\n dx?: number;\n dy?: number;\n /** Replace the computed label entirely. */\n label?: string;\n /** Buyer-facing copy only. Booking/API identity remains row id + slot index\n * internally and the legacy `label` externally for backwards compatibility. */\n displayLabel?: string;\n categoryKey?: string;\n /** @deprecated legacy flag — read as `['wheelchair']`; write `accessibility`. */\n accessible?: boolean;\n /** Accessibility accommodations of this seat (empty/absent = none). */\n accessibility?: AccessibilityType[];\n /**\n * Physical wheelchair provision. Absent keeps legacy seat rendering;\n * `seat-present` is an explicit removable/fixed accessible chair, while\n * `no-seat` is an empty wheelchair bay that remains one sellable inventory\n * unit. This is deliberately distinct from `skip`, which removes inventory.\n */\n wheelchairSpaceType?: 'seat-present' | 'no-seat';\n /** Commercial selling/view attributes are deliberately not accessibility. */\n commercial?: SeatCommercialAttributes;\n /** Seat-specific view photo; falls back to the row photo. */\n viewFromSeatUrl?: string;\n /** Calibration/truth for `viewFromSeatUrl`; ignored when the URL is absent. */\n viewFromSeatMeta?: SeatViewMetadata;\n /** Seat-scoped confidence evidence, independent of photo availability. */\n confidenceEvidence?: SeatConfidenceEvidence;\n /** Per-seat label size/color override; falls back to the row/theme default.\n * Size is clamped to LABEL_STYLE_MIN_SIZE..MAX_SIZE; color is passed through\n * the shared auto-contrast rule at paint time (see {@link LabelStyle}). */\n labelStyle?: LabelStyle;\n}\n\nexport interface SeatCommercialAttributes {\n restrictedView?: boolean;\n obstructedView?: boolean;\n premium?: boolean;\n note?: string;\n}\n\n/** The selling attributes that can be marked on a seat one at a time, the same\n * way an accommodation can. `note` is free text and belongs to one seat, so it\n * is deliberately not here. */\nexport type SeatCommercialMark = 'obstructedView' | 'restrictedView' | 'premium';\n\nexport interface SeatCommercialMeta {\n key: SeatCommercialMark;\n /** Full descriptive label (designer panel, tooltips). */\n label: string;\n /** Compact label for chips. */\n short: string;\n /** Single-glyph badge, matching the accessibility chips. */\n icon: string;\n}\n\n/**\n * Ordered taxonomy of sellable view attributes.\n *\n * Kept beside {@link ACCESSIBILITY_TYPES} and shaped identically because the\n * designer paints them with the same tool: an author marking the back two rows\n * of a stand as obstructed is doing the same gesture as marking a wheelchair\n * space, and at a thousand seats it has to cost the same. They stay separate\n * TYPES because they mean different things — an accommodation is a buyer's\n * access need, a selling attribute is what the seat is worth.\n */\nexport const SEAT_COMMERCIAL_MARKS: SeatCommercialMeta[] = [\n { key: 'obstructedView', label: 'Obstructed view', short: 'Obstructed', icon: '⛔' },\n { key: 'restrictedView', label: 'Restricted view', short: 'Restricted', icon: '👁' },\n { key: 'premium', label: 'Premium seat', short: 'Premium', icon: '★' },\n];\n\nconst SEAT_COMMERCIAL_LABEL = new Map(SEAT_COMMERCIAL_MARKS.map((mark) => [mark.key, mark]));\n\n/** Metadata for one selling attribute (undefined for unknown keys). */\nexport function seatCommercialMeta(key: SeatCommercialMark): SeatCommercialMeta | undefined {\n return SEAT_COMMERCIAL_LABEL.get(key);\n}\n\n/**\n * Per-object label ink + size overrides layered on top of the chart-wide Theme\n * defaults (rowLabelColor / textColor for rows, the section-name ink for\n * sections). Both fields are optional: an absent field means \"inherit the theme\n * default\". `color` is a preferred hex — renderers still pass it through the\n * shared auto-contrast rule (`stateAwareBookableLabelInk`), so a choice that\n * would be illegible over the seat/section background is switched to black or\n * white at paint time. `size` is a font size in chart units, clamped to\n * LABEL_STYLE_MIN_SIZE..LABEL_STYLE_MAX_SIZE by the shared ops.\n */\nexport interface LabelStyle {\n size?: number;\n color?: string;\n}\n\n/** Clamp bounds for a per-object label `size`, shared by ops, MCP, and UI. */\nexport const LABEL_STYLE_MIN_SIZE = 8;\nexport const LABEL_STYLE_MAX_SIZE = 24;\n\nexport interface LabelPresentation {\n visible?: boolean;\n /** Exact Designer-owned label anchor; public semantic MCP schemas omit it. */\n position?: Point;\n rotation?: number;\n style?: 'plain' | 'pill';\n /** Per-object size/color override for this row's or section's label. */\n labelStyle?: LabelStyle;\n /**\n * End-position preset for a ROW label — which end(s) of the row show it:\n * - `start` (default/undefined) — the row's numbering-start end (legacy behaviour).\n * - `end` — the far end of the row.\n * - `both` — a label at BOTH ends.\n * - `none` — hidden (kept coherent with `visible: false`).\n * A free-drag `position` overrides the preset (the designer shows a 'custom'\n * state). Ignored for sections (they use `position`/`visible` only).\n */\n positionPreset?: 'start' | 'end' | 'both' | 'none';\n}\n\n/** Persisted authoring ergonomics that must not change buyer inventory or\n * rendering. Buyer projections strip this object before a chart is served. */\nexport interface ObjectEditorState {\n /** Objects sharing this id select and transform as one authoring unit. */\n groupId?: string;\n /** Protect canvas geometry while keeping ordinary Inspector properties editable. */\n locked?: boolean;\n /** Remove the object from the authoring scene until Reveal hidden is used. */\n hidden?: boolean;\n}\n\nexport interface AuthoringObjectState {\n editor?: ObjectEditorState;\n}\n\n/** Brand/venue theming — applied by the renderer in both designer and picker. */\nexport interface ChartTheme {\n /** Canvas background color (default dark: #0e1117-ish radial). */\n background?: string;\n /** Preferred text color for the numbers inside seat markers. */\n seatLabelColor?: string;\n /** Preferred color for row identifiers such as A, B, C. Falls back to textColor. */\n rowLabelColor?: string;\n /** Selection ring / accent color (default white ring + brand accent). */\n selectionColor?: string;\n /** Décor (stage/shape) default fill. */\n decorFill?: string;\n /** Free-text color default. */\n textColor?: string;\n /** Font family (CSS stack) for all rendered text — row labels, seat numbers, sections, décor text. */\n fontFamily?: string;\n /** Seat size multiplier on the base radius (0.7–1.6, default 1) — bigger seats fit longer labels. */\n seatScale?: number;\n // ---- White-label branding (applied by the buyer picker chrome) ----\n /** Brand accent color — recolors buttons, links, the hold pill, selection UI. */\n accent?: string;\n /** Ink color for text on the accent (e.g. button labels). Default light. */\n accentInk?: string;\n /** Organizer logo shown in the picker header (data/R2 URL). Falls back to the name. */\n logoUrl?: string;\n /** Brand/venue name shown in the picker header when no event name is set. */\n brandName?: string;\n /** Paid-tier flag: hide the \"Powered by SeatMap\" badge. */\n hideBadge?: boolean;\n}\n\nexport interface RowObject extends AuthoringObjectState {\n type: 'row';\n id: string;\n /** Present when this row was materialized by the in-canvas reference scan.\n * A re-scan of the same asset replaces rows carrying this marker and NEVER\n * touches hand-authored rows — the same replace-generated-only invariant as\n * applyReferenceInventory. */\n referenceScan?: { assetId: string };\n /** Row label, e.g. \"A\". Seat labels are `${label}-${n}`. */\n label: string;\n /** Buyer-facing row name. `label` remains the legacy inventory prefix. */\n displayLabel?: string;\n /**\n * Buyer-facing type word override (seats.io \"Displayed type\"). Replaces the\n * hardcoded \"Row\" in the picker tooltip/confirm/cart, e.g. \"Table\", \"Bench\",\n * \"Aisle\". ≤24 chars; absent = the default \"Row\". Pure presentation.\n */\n displayType?: string;\n labelPresentation?: LabelPresentation;\n /** Position of the FIRST seat. */\n origin: Point;\n /** Degrees, clockwise. 0 = seats laid out along +x. */\n rotation: number;\n /**\n * Total arc sweep in degrees across the whole row. 0 = straight.\n * Positive bends away from +y (concave toward the focal point when the\n * row faces it). Typical theatre rows: 10–40.\n */\n curve: number;\n seatCount: number;\n /** Distance between adjacent seat centers, in chart units. */\n seatSpacing: number;\n /** Optional exact cubic centreline. Normal code owns these coordinates and\n * distributes seats by arc length; MCP/model inputs never submit them. */\n path?: CubicPath;\n categoryKey: string;\n /** Deterministic provenance for rows fitted from confirmed reference\n * inventory. It enables revision-safe replacement without touching manually\n * authored rows or accepting client coordinates. */\n referenceInventorySource?: ReferenceInventorySource;\n /** Semantic parameters for a row produced by the shared Arc/Fan operation.\n * Designer can reopen these parameters while every segment in the group\n * still carries the same generation signature. Public MCP tools never accept\n * the stored center/angles as arbitrary model-authored coordinates. */\n arcFanGeneration?: {\n kind: 'arc-fan-v1';\n groupId: string;\n center: Point;\n innerRadius: number;\n rowCount: number;\n rowGap: number;\n startAngle: number;\n endAngle: number;\n seatPitch: number;\n fit: 'seat-pitch' | 'fixed-count';\n seatsPerRow?: number;\n facing: 'inward' | 'outward';\n taperDegrees: number;\n skewDegrees: number;\n aisleGaps: { left: number; center: number; right: number };\n rowLabelStart: number;\n seatLabelStart: number;\n rowIndex: number;\n segmentIndex: number;\n };\n /**\n * Membership in one buyer-facing segmented row. Physical component rows and\n * their `${rowId}:${slotIndex}` inventory ids remain authoritative; this\n * metadata only supplies logical ordering/presentation and explicit aisle\n * continuity. The descriptor is repeated on every component so selecting any\n * one can resolve the complete logical row without a chart-level side table.\n */\n segmentedRow?: {\n kind: 'segmented-row-v1';\n groupId: string;\n componentIndex: number;\n componentCount: number;\n /** The first component must use `start`; later boundaries are explicit. */\n boundaryBefore: 'start' | 'continuous' | 'break';\n /** Buyer-facing row name; technical component `label` values never change. */\n displayLabel: string;\n displayType?: string;\n labelPresentation?: LabelPresentation;\n viewFromSeatUrl?: string;\n viewFromSeatMeta?: SeatViewMetadata;\n confidenceEvidence?: SeatConfidenceEvidence;\n /** Presentation intent for a continuous node-defined centreline. */\n smoothing?: boolean;\n };\n /**\n * Versioned provenance for rows created by the multiple/intertwined block\n * generator. Manual geometry edits remove this marker rather than allowing a\n * later regeneration to overwrite hand-authored work.\n */\n rowBlockGeneration?: {\n kind: 'row-block-v1';\n groupId: string;\n style: 'multiple' | 'intertwined';\n rowIndex: number;\n rowCount: number;\n seatsPerRow: number;\n origin: Point;\n rotation: number;\n rowGap: number;\n seatSpacing: number;\n curve: number;\n /** Stable canonical generator signature shared by every intact member. */\n signature: string;\n };\n /** First seat number (default 1). Roman/letters read it as a 1-based ordinal\n * (start 1 → I / A). */\n seatLabelStart?: number;\n /** Seat numbering within the row (default decimal, ltr, step 1). */\n seatNumbering?: {\n /** ltr / rtl number from an end; `center` numbers outward from the middle\n * (centre seat lowest — the premium-centre theatre convention). */\n direction: 'ltr' | 'rtl' | 'center';\n /** 2 = odd/even numbering (1,3,5… — start at 2 for evens). */\n step?: 1 | 2;\n /**\n * Label scheme for the seat NUMBER part (the row prefix is separate).\n * Default `decimal`. Composition with `direction`/`step`/`seatLabelStart`:\n * - `decimal` 1,2,3 — honours direction + step + start.\n * - `odd` 1,3,5 — odd numbers from the first odd ≥ start.\n * - `even` 2,4,6 — even numbers from the first even ≥ start.\n * - `updown` 1,3,5,…,6,4,2 — odd-up-even-back; REPLACES direction (uses\n * physical left→right order); start shifts.\n * - `updown-descending` …5,3,1,2,4,6 — odd-back-even-up; the distinct\n * reverse up/down sequence. Also replaces\n * direction and uses physical order.\n * - `roman` I,II,III — honours direction + step + start (uppercase).\n * - `letters-upper` A,B,C…Z,AA — honours direction + step + start.\n * - `letters-lower` a,b,c…z,aa — honours direction + step + start.\n * Like `step`/`direction` today, the scheme changes the seat's inventory\n * label (its booking identity), by design.\n */\n scheme?: 'decimal' | 'odd' | 'even' | 'updown' | 'updown-descending' | 'roman' | 'letters-upper' | 'letters-lower';\n /** Optional prefix prepended to every seat number, e.g. 'R' → 'R1', 'R2'. */\n prefix?: string;\n /**\n * End-at preset (\"useEndAt\"): the row's numbering ENDS at this value instead\n * of starting at `seatLabelStart`. The start is derived so the last-numbered\n * seat (highest position rank) lands on `endAt`, respecting the scheme's step\n * (odd/even = 2). When set it WINS over the stored `seatLabelStart` (which is\n * left untouched). For letters it is a 1-based number index (26 → last seat\n * 'Z'); `updown` owns its own sequence and ignores `endAt`.\n */\n endAt?: number;\n };\n /**\n * Per-seat exceptions, keyed by seat index (0-based position in the row).\n * `skip` removes the physical seat but keeps the numbering gap (theatre\n * convention: a pillar eats A-3; A-4 stays A-4).\n */\n overrides?: SeatOverride[];\n /**\n * Organizer-supplied equirectangular 360 (or wide photo) shown as the\n * view-from-seat for every seat in this row. When absent, the picker\n * generates a synthetic panorama from chart geometry.\n */\n viewFromSeatUrl?: string;\n viewFromSeatMeta?: SeatViewMetadata;\n /** Row-scoped confidence evidence inherited by seats without a closer record. */\n confidenceEvidence?: SeatConfidenceEvidence;\n /** Default commercial attributes inherited by seats without an override. */\n commercial?: SeatCommercialAttributes;\n}\n\nexport interface GAAreaObject extends AuthoringObjectState {\n type: 'gaArea';\n id: string;\n /** Stable technical/inventory label. */\n label: string;\n /** Buyer-facing area name; technical `label` and GA unit ids stay stable. */\n displayLabel?: string;\n /** Buyer-facing type word override (seats.io \"Displayed type\"), ≤24 chars.\n * Absent = the default type word. Pure presentation. */\n displayType?: string;\n /** Closed polygon, in chart units. */\n points: Point[];\n /** Explicit aisles/pillars/cutouts excluded from the sellable GA surface. */\n holes?: Point[][];\n capacity: number;\n categoryKey: string;\n /** Corner-rounding radius in chart units (default 0 = sharp corners). Pure\n * presentation — softens the polygon's corners in every renderer without\n * touching capacity, unit identities, or the stored points. Clamped per\n * corner to half the shorter adjacent edge at draw time. */\n cornerRadius?: number;\n /**\n * Durable inventory provenance for a surface produced by Join Areas.\n *\n * A GA unit is identified by the id and zero-based range of the area that\n * originally authored it, not by the current polygon which happens to own\n * it. Keeping those source ranges means a geometric join never renumbers an\n * already published/booked unit. Ordinary (never-joined) areas omit this\n * field and implicitly own `[0, capacity)` under their own id.\n *\n * The ranges must be non-overlapping, contain positive whole counts, and sum\n * exactly to `capacity`; validation rejects malformed metadata. Capacity\n * growth appends a new range under the surviving area id, while shrinking a\n * joined area is deliberately refused because it would silently destroy\n * stable inventory identities.\n */\n inventorySegments?: GAInventorySegment[];\n referenceInventorySource?: ReferenceInventorySource;\n}\n\nexport interface GAInventorySegment {\n sourceAreaId: string;\n startIndex: number;\n count: number;\n}\n\n/**\n * Durable evidence link for sellable objects generated from a private reference.\n * The client supplies facts and stable logical-section ids, never coordinates.\n */\nexport interface ReferenceAccessibilitySource {\n placementDerivation: 'server-synthesized-row-edges';\n groupLogicalSectionIds: string[];\n assignmentEvidence: 'user-confirmed' | 'authoritative-source';\n assignmentSourceDescription: string;\n counts: Array<{\n type: AccessibilityType;\n count: number;\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n }>;\n}\n\nexport interface ReferenceInventorySource {\n assetId: string;\n logicalSectionId: string;\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n /** Distinguishes directly supplied inventory from a user-approved server\n * distribution based only on an aggregate capacity. */\n derivation?: 'explicit-inventory' | 'server-synthesized-from-aggregate';\n /** Evidence for the aggregate figure; synthesized rows remain\n * `user-confirmed` and are never mislabeled as source-extracted. */\n aggregateEvidence?: 'user-confirmed' | 'authoritative-source';\n /** Separate evidence for assigning standing inventory to this logical\n * section. Aggregate evidence alone cannot prove section placement. */\n sectionAssignmentEvidence?: 'user-confirmed' | 'authoritative-source';\n sectionAssignmentSourceDescription?: string;\n /** Aggregate row synthesis may propose numbering, but persistence requires\n * the applying user/agent to confirm that policy explicitly. */\n numberingEvidence?: 'user-confirmed';\n numberingSourceDescription?: string;\n /** Evidence and deterministic placement contract for synthesized accessible\n * units in this logical section. */\n accessibility?: ReferenceAccessibilitySource;\n}\n\n/** Durable evidence that a visible source-backed section shell intentionally\n * carries no generated sellable inventory in this reference configuration. */\nexport interface ReferenceInventoryExclusionSource {\n assetId: string;\n logicalSectionId: string;\n reason: string;\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n}\n\n/** Open-path stroke semantics. Optional ShapeObject fields retain the legacy\n * round/round/no-ending rendering when absent. */\nexport type ShapeLineCap = 'butt' | 'round' | 'square';\nexport type ShapeLineJoin = 'miter' | 'round' | 'bevel';\nexport type ShapeLineEnding = 'none' | 'arrow';\n\n/** Non-bookable décor: stage, walls, exits. */\nexport interface ShapeObject extends AuthoringObjectState {\n type: 'shape';\n id: string;\n /**\n * Closed area shapes (`rect`/`ellipse`/`polygon`) take a `fill`; open path\n * primitives (`line` = two points, `polyline` = n points) are stroke-only and\n * never filled. All kinds honour the optional `stroke`.\n */\n kind: 'rect' | 'ellipse' | 'polygon' | 'line' | 'polyline';\n label?: string;\n /** For rect/ellipse: bounding box. For a stage polygon: the base (pre-shape) box, so its kind can be regenerated. */\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n /** For polygon/line/polyline. */\n points?: Point[];\n fill?: string;\n /** Optional outline. `width` is in chart units; both fields are required together. */\n stroke?: { color: string; width: number };\n /** Open line/polyline only. Absent fields preserve round/round/no-ending legacy rendering. */\n lineCap?: ShapeLineCap;\n /** Controls corners between open-path segments. */\n lineJoin?: ShapeLineJoin;\n /** Independent open-path start/end decorations. Closed outlines never use these fields. */\n startEnding?: ShapeLineEnding;\n endEnding?: ShapeLineEnding;\n /** Rect only — corner rounding radius in chart units, clamped to half the short side at edit time. */\n cornerRadius?: number;\n /** Whole-shape opacity 0.1–1 (default 1). */\n opacity?: number;\n /** Optional physical extrusion height used by the 3D venue renderer. */\n heightM?: number;\n /**\n * Event configurations in which this architecture is present. Absent means\n * the shape belongs to every configuration; a scoped shape is hidden until\n * one of these ids is active on the chart or 3D scene input.\n */\n eventConfigurationIds?: string[];\n /** Degrees clockwise about the shape's center (default 0). Applied at render time. */\n rotation?: number;\n /**\n * Semantic tag driving special rendering. `'stage'` gets the gradient +\n * prominent uppercase label treatment; a décor landmark role (bar, exit…)\n * gets a quieter label. Loose string to avoid a circular import with\n * stage.ts / decor.ts (see StageKind / DecorRole there).\n */\n role?: string;\n /** For a stage: which `StageKind` its polygon was generated from. */\n stageKind?: string;\n}\n\nexport type RectTableSide = 'top' | 'bottom' | 'left' | 'right';\n\n/** Exact rectangular-table chair distribution. The four keys are deliberately\n * required: zero means that edge has no chair, while the sum is the authored\n * `seatCount`. Numeric chair identity remains `${table.id}:${index}` in the\n * canonical top, bottom, left, right expansion order. */\nexport interface RectTableSeatCounts {\n top: number;\n bottom: number;\n left: number;\n right: number;\n}\n\n/** Seats arranged around a table. Grouped selling is activated only by an\n * event's explicit inventory-model-2 snapshot; model-1 events continue to\n * treat every authored chair as an independent unit. */\nexport interface TableObject extends AuthoringObjectState {\n type: 'table';\n id: string;\n /** e.g. \"T1\" — seat labels are `${label}-${n}`. */\n label: string;\n /** Buyer-facing table name; technical chair/group labels stay stable. */\n displayLabel?: string;\n /** Buyer-facing type word override (seats.io \"Displayed type\"), ≤24 chars.\n * Absent = the default \"Row\"/\"Table\" word. Pure presentation. */\n displayType?: string;\n center: Point;\n shape: 'round' | 'rect';\n /** Seats around the perimeter (round) or along the enabled edges (rect). */\n seatCount: number;\n /** Rect tables: which edges get seats (default ['top','bottom']). */\n sides?: RectTableSide[];\n /**\n * Rect tables only: exact chairs on every edge. Absent preserves the legacy\n * `seatCount` + `sides` round-robin distribution byte-for-byte. When present,\n * all four values are whole numbers >= 0 and their sum equals `seatCount`.\n */\n seatCountsBySide?: RectTableSeatCounts;\n /** Individual-chair semantic overrides. Grouped whole/variable tables cannot\n * author these because their only sellable identity is the table itself. */\n overrides?: SeatOverride[];\n rotation: number;\n /** Round tables. */\n radius?: number;\n /**\n * Round tables: the arc (in degrees) the seats occupy, default 360 (full\n * ring). Below 360 leaves an open side — e.g. a service gap for waiters, a\n * head table facing the room, or clearance against a wall. The opening is\n * centred on the `rotation` direction; seats spread across the rest.\n */\n seatArc?: number;\n /** Rect tables. */\n width?: number;\n height?: number;\n categoryKey: string;\n /** One buyer owns the complete table at exactly `seatCount` guests. */\n bookAsWhole?: boolean;\n /** One buyer owns the complete table and chooses a bounded guest quantity. */\n variableOccupancy?: boolean;\n /** Required inclusive guest bounds when `variableOccupancy` is true. */\n minOccupancy?: number;\n maxOccupancy?: number;\n referenceInventorySource?: ReferenceInventorySource;\n}\n\n/** A booth: one bookable unit rendered as a block (trade shows, VIP boxes). */\nexport interface BoothObject extends AuthoringObjectState {\n type: 'booth';\n id: string;\n /** Stable technical/inventory label. */\n label: string;\n /** Buyer-facing booth name; technical `label` stays stable. */\n displayLabel?: string;\n /** Buyer-facing type word override (seats.io \"Displayed type\"), ≤24 chars.\n * Absent = the default type word. Pure presentation. */\n displayType?: string;\n center: Point;\n width: number;\n height: number;\n rotation: number;\n /**\n * Optional custom outline (closed polygon, absolute chart coordinates) for\n * non-rectangular booths — L-shaped, corner, or island units on expo floors.\n * Absent = the default axis-aligned rectangle described by `width`/`height`/\n * `rotation`. A booth stays exactly ONE atomic sellable unit whatever its\n * outline; `points` is purely geometric. `width`/`height` are retained as the\n * last rectangular size so \"Back to rectangle\" can restore it. When `points`\n * is present, renderers draw the polygon and ignore `rotation`.\n */\n points?: Point[];\n categoryKey: string;\n referenceInventorySource?: ReferenceInventorySource;\n}\n\n/**\n * A named region of the venue (Balcony Left, Floor B…). Sections are outlines\n * only — objects belong to a section spatially (center inside the outline),\n * keeping the document flat (no nesting; simpler for tools and for AI edits).\n * Renderer: far zoom shows section shapes/labels instead of seats; clicking\n * a section zooms into it.\n */\nexport interface SectionObject extends AuthoringObjectState {\n type: 'section';\n id: string;\n label: string;\n /** Buyer-facing section name; logical/id fields remain stable. */\n displayLabel?: string;\n /**\n * Buyer-facing entrance/door hint shown in the picker section card\n * (\"Entrance X\"). ≤40 chars; absent = no entrance line. Pure presentation.\n */\n entrance?: string;\n /**\n * Organizer-supplied view image inherited by buyer inventory whose owning\n * row/table/booth sits in this logical section. Seat and row photos take\n * precedence; multipart components are kept in sync by the shared section\n * metadata operation.\n */\n viewFromSeatUrl?: string;\n viewFromSeatMeta?: SeatViewMetadata;\n /** Section-scoped confidence evidence inherited after seat and row evidence. */\n confidenceEvidence?: SeatConfidenceEvidence;\n labelPresentation?: LabelPresentation;\n /**\n * Stable management/inventory identity shared by disconnected visual\n * components of one logical section. When absent, `id` is the logical id.\n * Each component keeps its own `id` and reference provenance so rendering,\n * editing, measured diffs, and source restoration remain exact.\n */\n logicalSectionId?: string;\n /** Shared semantic Arc/Fan group wrapped by this section. The section id is\n * preserved when the fan parameters are reopened and regenerated. */\n arcFanGroupId?: string;\n /** Closed polygon, chart units. */\n outline: Point[];\n /** Optional true line/arc/cubic boundary. `outline` is a deterministic sample\n * of this path and remains authoritative for membership, validation, and\n * clients that predate curved section rendering. */\n outlinePath?: SectionOutlinePath;\n /** Explicit aisle/cutout polygons excluded from rendering, hit-testing, and membership. */\n holes?: Point[][];\n /** Durable opaque link to the private reference component. Unlike generator\n * provenance, this survives manual geometry edits so a measured diff can\n * report drift and server-owned code can restore the source contour. */\n referenceSource?: {\n assetId: string;\n regionId: string;\n };\n /** Evidence-backed reason this source section remains a visible shell without\n * synthesized sellable inventory (press, closed technical zone, etc.). */\n referenceInventoryExclusion?: ReferenceInventoryExclusionSource;\n /** Deterministic generator provenance for editable reference/parametric shells. */\n geometry?: {\n kind: 'rectangle' | 'tapered' | 'bezier' | 'contour';\n sourceRegionId?: string;\n contourMethod?: 'pixel-edge-loops-rdp' | 'shared-edge-vector-fit-v1';\n simplificationTolerancePx?: number;\n vectorFitErrorPx?: number;\n sharedEdgeCount?: number;\n };\n /** Optional tint override (defaults to a neutral fill / dominant category mix). */\n color?: string;\n /** Zone this section belongs to (id into `ChartDoc.zones`). Far-zoom nav + pricing group. */\n zone?: string;\n /**\n * Tier height. 0 = floor (default). Higher values lift the section in the\n * picker's isometric (\"3D\") view, drawn on extruded side faces. Same field a\n * future multi-floor mode reuses — authored in 2D, never drawn by the user.\n *\n * This is the coarse, back-compat source for {@link height}/{@link rake}: when\n * those are absent, {@link sectionGeometry} derives real geometry from this\n * tier so legacy charts render pixel-identical.\n */\n elevation?: number;\n /**\n * 3D foundations (Phase A, additive — no migration; charts are JSON blobs).\n * Metres the section's **front edge** sits above floor 0 (a balcony/tier floor\n * height). Absent ⇒ derived from the coarse {@link elevation} tier via\n * {@link sectionGeometry}. Deliberately two scalars, not a foundation polygon:\n * front-height + {@link rake} fully determine a rectangular tier's back-height.\n *\n * NOTE: no consumer reads this raw field directly — all callers go through\n * {@link sectionGeometry}. Phase B consumers (iso view lift in\n * `SeatmapRenderer`, per-seat eye-height in the `generatePanorama` 360°\n * generator) are intentionally NOT wired in Phase A. Range 0–120 m.\n */\n height?: number;\n /**\n * Degrees of seating incline within the section (0 = flat; typical stalls\n * 5–15°, steep tiers 25–35°). Absent ⇒ 0. Consumed alongside {@link height}\n * by the future Phase B iso-lift shear and 360° sightline math — never in\n * Phase A. Range 0–45°.\n */\n rake?: number;\n /**\n * Override how 3D builds this section's seating surface. Absent = inferred.\n *\n * The renderer infers structure from the authored geometry — which rows form a\n * stand, where its front is, whether the rows are concentric arcs or straight\n * blocks — and that inference is correct across every shipped template. But\n * inference cannot be right on every chart a customer will ever draw, and when\n * it is wrong the author needs a way to say so without us inventing a second\n * source of truth for the geometry itself.\n *\n * - `flat`: a level deck, whatever the rake says. Terraces, boxes, standing.\n * - `rakedRows`: level ribbons stepping back, the default for a raked stand.\n *\n * Deliberately NOT a \"venue type\" picker: the chart is authored once in 2D,\n * and every 3D-only property that is not derived from that authoring is a\n * source of drift. This is a per-section correction, in the same place and of\n * the same kind as {@link height} and {@link rake}.\n */\n surfaceKind?: 'flat' | 'rakedRows';\n /** Uniform scale about the outline centroid (1 = as drawn). Scales members too. */\n scale?: number;\n /** 0–100: reviewed strength last used to bend member rows toward a common fitted arc. */\n smoothing?: number;\n /**\n * Corner smoothing: the raw clicked polygon this section was drawn from, kept\n * verbatim so {@link cornerSmoothing} stays re-derivable and fully reversible.\n * When present, {@link outlinePath} is a curve computed from THIS polygon (not\n * a reference/blueprint contour); {@link outline} is its deterministic sample.\n * Additive and optional — legacy charts and reference-derived curves omit it.\n */\n sourceOutline?: Point[];\n /**\n * 0–100 corner-smoothing strength applied to {@link sourceOutline} to produce\n * the curved {@link outlinePath}. 0/absent = exact clicked corners. Higher\n * rounds the wide (gently-angled) corners more; sharp corners stay crisp.\n * Coordinate-free, so it round-trips over MCP (`update_sections`).\n */\n cornerSmoothing?: number;\n /**\n * Per-edge curvature, one entry per edge of {@link sourceOutline}, -100..100,\n * 0 = straight. Edge `i` runs from clicked vertex `i` to vertex `i+1`.\n *\n * Positive bows to the LEFT of that edge's own direction. The sign is relative\n * to the edge, never to world axes, which is what lets curvature survive\n * rotate / flip / mirror / radial repeat with no per-operation handling.\n *\n * Coordinate-free like {@link cornerSmoothing}, so it round-trips over MCP and\n * the server never accepts client geometry. Absent/all-zero = straight edges;\n * zeroing it restores the exact clicked polygon.\n */\n edgeCurvature?: number[];\n /** Degrees clockwise about the outline centroid (default 0). Rotates members too. */\n rotation?: number;\n}\n\n/**\n * A group of sections (Lower Bowl, Upper Bowl, Floor…). One concept, three jobs:\n * the farthest-zoom navigation unit, a pricing group, and (Batch 3) a timed-\n * release unit. Kept as a flat list on the doc; sections point back by `zone` id.\n */\nexport interface ZoneDef {\n id: string;\n label: string;\n color?: string;\n /**\n * Authored point this zone faces. Optional only for legacy documents: runtime\n * consumers fall back to the active floor/chart focal, while publication of\n * a zone-mode draft requires every used zone to carry an explicit point.\n */\n focalPoint?: Point;\n}\n\n/**\n * Selection layer — a hit-test/dim filter in the designer, NOT z-order management.\n * Fixed set of four; derived from object type via `layerOf()` (no per-object field yet).\n */\nexport type SelectionLayer = 'interactive' | 'background' | 'foreground' | 'surroundings';\n\n/** Shape roles emitted by the curated venue-landmark palette. Keep this list in\n * lockstep with `DECOR_PRESETS`; the selection-layer unit test fails if either\n * vocabulary changes without an explicit routing decision. `reference-focal`\n * is source-backed venue context rather than an authoring-palette preset. */\nexport const SURROUNDINGS_SHAPE_ROLES = [\n 'reference-focal',\n 'bar',\n 'entrance',\n 'exit',\n 'restroom',\n 'screen',\n 'sound',\n 'concession',\n 'coat',\n 'wall',\n 'rail',\n 'suite',\n 'obstruction',\n] as const;\n\nconst SURROUNDINGS_SHAPE_ROLE_SET: ReadonlySet<string> = new Set(SURROUNDINGS_SHAPE_ROLES);\n\n/** Derive an object's selection layer from its type. */\nexport function layerOf(obj: ChartObject): SelectionLayer {\n switch (obj.type) {\n case 'row':\n case 'table':\n case 'gaArea':\n case 'booth':\n case 'section':\n return 'interactive';\n // Raster décor follows its explicit authored z-layer. Absence retains the\n // legacy Background default; bitmap content is never guessed semantically.\n case 'decorImage':\n return obj.layer === 'foreground' ? 'foreground' : 'background';\n // Source-backed focal geometry and curated venue landmarks help an author\n // orient around the sellable plan. A stage and an ordinary authored shape\n // remain Background even when they carry an arbitrary/custom role.\n case 'shape':\n return obj.role && SURROUNDINGS_SHAPE_ROLE_SET.has(obj.role)\n ? 'surroundings'\n : 'background';\n // Free text — including semantic icon text — is background furniture.\n case 'text':\n return 'background';\n default:\n return 'interactive';\n }\n}\n\n/** Free-standing text on the chart (aisle names, door labels…). */\nexport type TextAlignment = 'left' | 'center' | 'right';\n\nexport interface TextBackground {\n color: string;\n /** Space inside the background in chart units; default 6. */\n padding?: number;\n /** Rounded background corners in chart units; default 4. */\n cornerRadius?: number;\n /** Background-only opacity multiplied by the text object's overall opacity. */\n opacity?: number;\n}\n\nexport interface TextObject extends AuthoringObjectState {\n type: 'text';\n id: string;\n /** Persisted provenance for objects created from the venue-icon palette. */\n semanticKind?: 'icon';\n /**\n * Registry key for a vector wayfinding icon (see src/core/icons.ts). Present\n * on modern icon placements; the object then renders as a single-color vector\n * Path instead of `text`. Absent on legacy emoji icons, which keep rendering\n * `text` through the shared glyph path — old charts are never rewritten.\n */\n iconKey?: string;\n text: string;\n position: Point;\n fontSize: number;\n /** Optional CSS family stack for this annotation; absent inherits ChartTheme.fontFamily. */\n fontFamily?: string;\n rotation: number;\n color?: string;\n /** Render weight (default false). Maps to Konva fontStyle bold. */\n bold?: boolean;\n /** Render slant (default false). Maps to Konva fontStyle italic. */\n italic?: boolean;\n /** Fixed text-box width in chart units. Absent keeps legacy auto width. */\n width?: number;\n /** Horizontal alignment inside a fixed-width text box. */\n align?: TextAlignment;\n /** Multiplier over the font's natural line height; default 1.2. */\n lineHeight?: number;\n /** Added chart units between glyphs; default 0. */\n letterSpacing?: number;\n /** Underline decoration. Absent is off. */\n underline?: boolean;\n /** Whole text-object opacity; default 1. */\n opacity?: number;\n /** Optional padded background painted behind the exact text box. */\n background?: TextBackground;\n}\n\n/**\n * A raster/vector decor graphic drawn IN the chart, beneath the seats and\n * sections (ice rink, basketball court, stage art, pitch markings). Purely\n * visual venue context — never bookable, never hit-tested, so it never steals a\n * seat click. `href` is a self-contained data URL (image or SVG) produced by the\n * same client-side downscale used for row photos, so it travels with the doc and\n * caches as a single bitmap blit (zero per-frame cost). Placed by top-left\n * (x,y) + size, rotated about its centre — the same handles a shape rect uses.\n */\nexport interface DecorImageObject extends AuthoringObjectState {\n type: 'decorImage';\n id: string;\n /** Image or SVG data URL. */\n href: string;\n x: number;\n y: number;\n width: number;\n height: number;\n /** Degrees clockwise about the image centre (default 0). */\n rotation?: number;\n /** 0–1 (default 1). Multiplied by any chart-theme dimming at render time. */\n opacity?: number;\n /**\n * Z-layer relative to the interactive seat layer. `background` (default)\n * draws beneath the seats/sections; `foreground` draws above them (a roof\n * canopy, an overlay graphic). Absent = background — no migration needed.\n */\n layer?: 'background' | 'foreground';\n /** Optional caption for the designer inspector / accessibility (not drawn). */\n label?: string;\n}\n\nexport type ChartObject =\n | RowObject\n | GAAreaObject\n | ShapeObject\n | TableObject\n | BoothObject\n | TextObject\n | SectionObject\n | DecorImageObject;\n\n/**\n * One floor / level of a multi-floor venue (Batch 5). Each floor owns its own\n * geometry, stage focal point, and trace image; categories/zones/tiers stay\n * chart-global (one event, one inventory). A single-floor chart has NO `floors`\n * — its `objects[]` is the whole venue — so all existing charts are untouched.\n */\nexport interface Floor {\n id: string;\n name: string;\n /**\n * Absolute physical deck height in metres above the venue/stage datum.\n * Optional for backwards compatibility; an absent value resolves to ground\n * level (0 m). Section tiers and rakes are separate, section-local metadata.\n * Range 0–120 m.\n */\n baseHeightM?: number;\n objects: ChartObject[];\n focalPoint: Point;\n /**\n * Private organizer trace/calibration layer. This is authoring evidence and\n * must never be served to, or rendered by, a buyer surface.\n */\n referenceImage?: ChartReferenceImage;\n /**\n * Buyer-visible aesthetic background. Canonical documents store URL-only\n * images here. Historical `assetId` values are interpreted as a trace layer\n * by the background compatibility helpers.\n */\n backgroundImage?: ChartDoc['backgroundImage'];\n /**\n * Per-floor override of the venue-wide view-from-seat fallback: a single\n * organizer 360° (or wide photo) inherited by every seat on THIS floor that\n * has no closer row/section photo. Falls below section photos and above the\n * chart-level `viewFromSeatUrl`. Absent ⇒ the venue default (then a synthetic\n * panorama) applies.\n */\n viewFromSeatUrl?: string;\n viewFromSeatMeta?: SeatViewMetadata;\n /** Floor-scoped confidence evidence inherited before the venue default. */\n confidenceEvidence?: SeatConfidenceEvidence;\n}\n\nexport interface ReferenceCalibration {\n type: 'two-point';\n /** Points in immutable source-image pixels, selected by a human or trusted detector. */\n sourceA: Point;\n sourceB: Point;\n /** Verified real-world distance between the two source points. */\n distance: number;\n unit: 'm' | 'ft' | 'chart-unit';\n /** Derived and stored for deterministic geometry compilers. */\n pixelsPerUnit: number;\n}\n\n/**\n * A single human-placed seat probe: the author clicks one seat on the reference\n * image and the server reads the surrounding seat lattice from it.\n *\n * COORDINATE-POLICY CARVE-OUT (owner decision 2026-07-21). The reference\n * blueprint pipeline runs `coordinatePolicy: 'opaque-region-ids-only'` — the\n * server is the sole source of chart coordinates and MCP clients select opaque\n * `reg_*` ids, never points. This type is a deliberate, narrow exception on the\n * same grounds as `ReferenceCalibration`: the point is placed by a human in the\n * designer canvas, not proposed by a model.\n *\n * Therefore this is DESIGNER-ONLY and is intentionally NOT exposed over MCP.\n * That is an accepted, documented exception to the MCP-parity rule — the\n * server-is-sole-source guarantee for model-driven edits is worth more than\n * parity here. Do not \"fix\" it by adding a seed point to an MCP tool schema.\n */\nexport interface ReferenceSeatSeed {\n /** Seed centre in immutable source-image pixels (never chart coordinates). */\n source: Point;\n /** Half-width of the author's sizing ring, in source pixels — the \"this is how\n * big one seat is\" hint that replaces seats.io's zoom-until-it-matches step. */\n radius: number;\n /** Whether `radius` was fitted from image pixels or set by hand. Detection\n * weights an author-set radius more heavily than one we guessed. */\n origin: 'auto-fit' | 'manual';\n}\n\n/** One detected row in a scan proposal — a straight seat run in CHART\n * coordinates (the server maps source pixels through referencePixelToChart;\n * clients never see source-pixel geometry back). */\nexport interface ReferenceScanRowProposal {\n start: Point;\n end: Point;\n seatCount: number;\n}\n\n/** Detected rows attributed to one compiled section (or unattributed when the\n * lattice extends outside every compiled polygon). */\nexport interface ReferenceScanSectionProposal {\n /** Id of the compiled SectionObject the rows landed in; null = unattributed. */\n sectionId: string | null;\n name: string;\n rows: ReferenceScanRowProposal[];\n seatCount: number;\n /** 0..1 — how well this section's lattice agreed with the probe's pitch. */\n confidence: number;\n /** Index of the seed (multi-probe) whose pitch produced these rows. */\n seedIndex: number;\n}\n\n/** Server response for an in-canvas reference scan. A PROPOSAL — nothing is\n * committed until the author applies it in the designer (chartOps + undo). */\nexport interface ReferenceScanProposal {\n assetId: string;\n /** Measured seat diameter / centre-to-centre pitch, in chart units. */\n seatDiameter: number;\n seatPitch: number;\n totalSeats: number;\n totalRows: number;\n sections: ReferenceScanSectionProposal[];\n}\n\n/**\n * Human-only Magic Trace request. `source` is one click in immutable\n * source-image pixels; it is accepted only by the browser Designer HTTP\n * surfaces and must never be added to an MCP schema.\n */\nexport interface ReferenceSectionTraceInput {\n assetId: string;\n floorId?: string;\n expectedUpdatedAt: number;\n source: Point;\n}\n\n/**\n * Whole-reference Magic Trace request. Unlike the one-region request this\n * carries no source coordinates: the server returns every persisted closed\n * region as an ID-free proposal for explicit browser review.\n */\nexport interface ReferenceSectionTraceBatchInput {\n assetId: string;\n floorId?: string;\n expectedUpdatedAt: number;\n}\n\n/**\n * Read-only Magic Trace result. This is deliberately not a SectionObject:\n * there is no object id or label until the author accepts the proposal through\n * the normal Designer command/undo boundary.\n *\n * All geometry is in chart coordinates. Source pixels, private contour\n * vertices and reference bounds are never returned.\n */\nexport interface ReferenceSectionTraceProposal {\n assetId: string;\n floorId: string;\n outline: Point[];\n /** Required exact fitted line/arc/cubic boundary. */\n outlinePath: SectionOutlinePath;\n /** Only structural source voids; printed labels/icons are filtered out. */\n holes: Point[][];\n /**\n * Optional, and the tracer deliberately omits it. The colour a section is\n * drawn in belongs to the CHART, not to the screenshot it was traced from —\n * see the note in `referenceSectionTrace.ts`. The sampled value lives on the\n * region row as `sampled_color`. Consumers fall back to zone/theme colour.\n */\n color?: string;\n referenceSource: {\n assetId: string;\n regionId: string;\n };\n geometry: {\n kind: 'contour';\n sourceRegionId: string;\n contourMethod: 'shared-edge-vector-fit-v1';\n simplificationTolerancePx: number;\n vectorFitErrorPx: number;\n sharedEdgeCount: number;\n };\n provenance: {\n regionId: string;\n analysisVersion: number;\n vectorTopologyVersion: number;\n selection: 'human-source-pixel-seed' | 'human-bulk-reference-review';\n registration: 'persisted-reference-registration-v1';\n };\n /** Coordinate-free evidence suitable for an author-facing confirmation. */\n diagnostics: {\n regionMarker: string;\n structuralHoleCount: number;\n fittedComponentCount: number;\n sharedBoundaryCount: number;\n maximumAllowedVectorErrorPx: number;\n measuredVectorErrorPx: number;\n /** Source component footprint, as a percentage of the analyzed image. */\n sourceAreaPercent?: number;\n /**\n * The name already printed on the plan for this region, when one has been\n * read. The review proposes it instead of a generated number — a venue plan\n * names its own sections, and retyping forty of them is work the author\n * should never have to do. Absent means fall back to numbering.\n */\n visibleLabel?: string;\n };\n}\n\n/** Read-only whole-reference proposal. The author may approve each proposal or\n * all proposals; the Designer then materializes the accepted set atomically. */\nexport interface ReferenceSectionTraceBatchProposal {\n assetId: string;\n floorId: string;\n proposals: ReferenceSectionTraceProposal[];\n diagnostics: {\n analyzedRegionCount: number;\n proposalCount: number;\n alreadyTracedCount: number;\n withinToleranceCount: number;\n /** Regions that could not be traced into a usable boundary and were left\n * out. One unusable region must not cost the author the rest of the plan. */\n unusableRegionCount?: number;\n };\n}\n\n/** Coordinate-free physical scale derived by server code from a confirmed\n * semantic feature. Unlike manual two-point calibration, no source points pass\n * through an MCP client or language model. */\nexport interface ReferenceDerivedScale {\n method: 'confirmed-focal-axis-v1';\n feature: 'focal-long-axis' | 'focal-short-axis';\n distance: number;\n unit: 'm' | 'ft';\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n chartUnitsPerUnit: number;\n}\n\nexport interface ChartReferenceImage {\n /** Stable private reference asset. New cloud-authored charts use this. */\n assetId?: string;\n /** Legacy/self-contained source. Optional when assetId is present. */\n url?: string;\n center: Point;\n /** Rendered width in chart units (height follows the cropped image aspect). */\n width: number;\n opacity: number;\n rotation?: number;\n visible?: boolean;\n layer?: 'below' | 'above';\n locked?: boolean;\n /** Normalized source crop; defaults to the full image. */\n crop?: { x: number; y: number; width: number; height: number };\n calibration?: ReferenceCalibration;\n /** Server-derived semantic calibration without source-image coordinates. */\n derivedScale?: ReferenceDerivedScale;\n}\n\nexport interface ChartDoc {\n version: 1;\n name: string;\n venueType: 'SIMPLE' | 'MIXED';\n /** The stage / point every seat looks at. Anchors seat-view + sightlines.\n * Multi-floor: mirrors floor 0; each floor also carries its own focalPoint. */\n focalPoint: Point;\n /** Active event layout used by configuration-scoped 3D architecture. */\n eventConfigurationId?: string;\n categories: Category[];\n /** Section groupings for far-zoom navigation + pricing (optional; sections reference by id). */\n zones?: ZoneDef[];\n /** Multi-floor venues (Batch 5): present ⇒ floors[] is the source of truth;\n * absent ⇒ single-floor and `objects` below is the whole chart. `objects`\n * is kept mirroring floor 0 so single-floor readers never branch. */\n floors?: Floor[];\n objects: ChartObject[];\n /**\n * Private floor-plan source used for tracing, calibration, scanning and\n * reference-backed generation. Buyer projections always remove this field.\n */\n referenceImage?: ChartReferenceImage;\n /**\n * Buyer-visible aesthetic background. Canonical values are URL-only.\n * Compatibility: a historical value containing `assetId` is trace-only and\n * is never rendered or exposed to buyers.\n */\n backgroundImage?: ChartReferenceImage;\n /** Brand/venue theming (colors); categories carry their own colors separately. */\n theme?: ChartTheme;\n /** Parametric-template provenance: present ⇒ the chart came from a capacity-\n * adjustable template family, and the designer offers a capacity control that\n * regenerates it at a new target seat count (Batch 4 \"curated singles + resize\"). */\n template?: { family: string; targetSeats: number };\n /**\n * Venue-wide view-from-seat fallback: a single organizer 360° (or wide photo)\n * inherited by every seat that has no closer row/section/floor photo. Absent ⇒\n * the picker generates a synthetic panorama.\n */\n viewFromSeatUrl?: string;\n viewFromSeatMeta?: SeatViewMetadata;\n /** Venue-scoped confidence evidence. It does not require panorama media. */\n confidenceEvidence?: SeatConfidenceEvidence;\n}\n\n// ---------------------------------------------------------------------------\n// Expanded (render-time) model — produced by layout.ts, consumed by the engine\n// ---------------------------------------------------------------------------\n\nexport interface ExpandedSeat {\n /** Stable id: `${rowId}:${index}` */\n id: string;\n /** Public label: `${rowLabel}-${seatNumber}` */\n label: string;\n /** Buyer-facing copy; absent on legacy charts, where `label` is displayed. */\n displayLabel?: string;\n x: number;\n y: number;\n rowId: string;\n /** Owning logical section and navigation zone, resolved once at expand time. */\n sectionId?: string;\n zoneId?: string;\n /** Zone focal when authored, otherwise the active floor/chart legacy fallback. */\n focalPoint?: Point;\n /** Buyer-facing segmented-row identity. `rowId` stays the physical owner id. */\n logicalRowId?: string;\n /**\n * Seat order inside the logical row. A deliberate missing integer is inserted\n * at every aisle boundary, so numerical adjacency cannot bridge a gap.\n */\n logicalSeatIndex?: number;\n categoryKey: string;\n /** 'booth' units render as blocks (dimensions looked up via rowId = booth id). */\n kind?: 'seat' | 'booth';\n /** True when the seat has any accessibility accommodation — renderer rings/dims these. */\n accessible?: boolean;\n /** Specific accessibility accommodations (absent = none) — picker badges/filters these. */\n accessibility?: AccessibilityType[];\n /** Physical wheelchair provision resolved from the seat override. */\n wheelchairSpaceType?: 'seat-present' | 'no-seat';\n commercial?: SeatCommercialAttributes;\n /** Organizer-supplied view-from-seat image (inherited from the row). */\n viewUrl?: string;\n /** Calibration/truth inherited from the same scope that supplied `viewUrl`. */\n viewMeta?: SeatViewMetadata;\n /** Closest configuration-valid evidence plus its engine-resolved coverage. */\n confidenceEvidence?: ResolvedSeatConfidenceEvidence;\n /** Per-seat label size/color override; absent = inherit the row/theme default. */\n labelStyle?: LabelStyle;\n /**\n * Real-world eye height in metres above the focal/stage datum, resolved at\n * expand time from the owning section's `{height, rake}` + drawn depth (Phase B2).\n * Feeds the auto-360° generator's stage-pitch math. Absent ⇒ flat seated eye\n * height (legacy / seats in no section) — so old charts stay pixel-identical.\n */\n eyeHeightM?: number;\n}\n\nexport type SeatStatus = 'free' | 'held' | 'booked' | 'not_for_sale';\n\n/** Buyer canvas projection. Perspective is a view-only projected-2.5D lane. */\nexport type RendererViewMode = 'flat' | 'isometric' | 'perspective';\n\n// ---------------------------------------------------------------------------\n// Renderer engine public API — implemented in src/engine/SeatmapRenderer.ts\n// ---------------------------------------------------------------------------\n\nexport interface RendererCallbacks {\n onSelect?: (seat: ExpandedSeat) => void;\n onDeselect?: (seat: ExpandedSeat) => void;\n /** Buyer tried to add a seat after the active selection cap was reached. */\n onSelectionLimit?: (maxSelection: number) => void;\n /** seat is null when the pointer leaves any seat. */\n onHover?: (seat: ExpandedSeat | null) => void;\n /** Keyboard focus moved to a seat (arrow-key navigation) — for screen-reader announcements. */\n onFocusSeat?: (seat: ExpandedSeat | null) => void;\n /** Called ~1×/sec with the measured frames-per-second. */\n onFps?: (fps: number) => void;\n /** Fired when a GA area is clicked (quantity picking is UI-side). */\n onGAClick?: (areaId: string) => void;\n /**\n * Fired when a tap lands on a section outline while seats are NOT the active\n * rung (i.e. zoomed out, section/zone LOD). The host glides in + shows a\n * section-summary card instead of trying to select a 4px seat (Slice 5).\n */\n onSectionTap?: (sectionId: string) => void;\n /**\n * Fired when a seat/deck is tapped in the 3D all-floors stacked overview — the\n * host drops back to the flat 2D map on that floor (\"tap a deck to enter\").\n */\n onDeckTap?: (floorId: string) => void;\n /** Fired after any pan/zoom/resize settles — re-anchor screen-space overlays. */\n onViewChange?: () => void;\n /**\n * Organizer manage-mode only (`manageMode` + `marqueeSelect`): fired on\n * pointer-UP after a rubber-band marquee drag, or a ⌘A/Escape bulk shortcut,\n * with the FULL current selection (selectable seats only). The host toolbar\n * reads this to drive bulk block/unblock. Never fires when manageMode is off.\n */\n onMarquee?: (seats: ExpandedSeat[]) => void;\n}\n\n/** Far-zoom level-of-detail rung: whole zones → section blocks → individual seats. */\nexport type LodRung = 'zones' | 'sections' | 'seats';\n\nexport interface RendererOptions extends RendererCallbacks {\n /**\n * Internal fixed-output mode used by the free maker's PNG/PDF pipeline.\n * It disables interaction, ResizeObserver, animation/FPS work and overview\n * bitmap caching while keeping the ordinary buyer renderer unchanged.\n */\n exportMode?: boolean;\n /** Max seats selectable at once (default 10). */\n maxSelection?: number;\n /** Only these statuses are clickable (default ['free']). */\n selectableStatuses?: SeatStatus[];\n /**\n * Opt-in host behaviour flag — the renderer's own click/selection logic is\n * unchanged (it still selects + fires `onSelect` immediately, so the seat\n * highlights right away). When true, the host (e.g. PublicEventPage) treats\n * that `onSelect` as a pending candidate and shows a confirm card instead of\n * pushing straight into the cart; `deselect([seat.id])` on Cancel un-highlights.\n */\n confirmSelection?: boolean;\n /** ISO 4217 currency for on-map prices (\"FROM …\"); defaults to money.DEFAULT_CURRENCY.\n * Locale for grouping/symbol placement comes from the active i18n locale. */\n currency?: string;\n /**\n * Organizer manage surface (SDK SeatManager). Opt-in — enables the manage-mode\n * gestures (marquee, ⌘A/Escape) and the bulk-selection helpers. Buyer pan /\n * pinch / tap and every existing code path are byte-identical when this is\n * false (every manage branch is gated on it). Default false.\n */\n manageMode?: boolean;\n /**\n * When `manageMode` is on, a mouse/pen primary-button drag at the seats rung\n * draws a rubber-band marquee that bulk-selects the seats it covers (emitting\n * `onMarquee` on pointer-up) instead of panning. Touch keeps single-finger\n * pan (pinch to zoom); a middle-button drag pans with a mouse. Disabled below\n * the seats rung (zoom in first). No effect unless `manageMode` is also set.\n */\n marqueeSelect?: boolean;\n}\n\nexport type RenderedLabelHiddenReason =\n | 'below-minimum-size'\n | 'outside-viewport'\n | 'dimmed-or-unavailable'\n | 'clutter-or-fit'\n | 'renderer-hidden';\n\n/** Browser-renderer evidence used by visual QA and catalog release gates. */\nexport interface RenderedBookableLabelEvidence {\n seatId: string;\n label: string;\n kind: 'seat' | 'booth';\n /** Painted inventory silhouette. Empty wheelchair bays are deliberately\n * square, while physical seats retain the ordinary circular marker. */\n markerShape: 'circle' | 'square' | 'booth';\n /** Physical wheelchair provision represented by this inventory unit. */\n wheelchairSpaceType?: 'seat-present' | 'no-seat';\n categoryKey: string;\n sectionId?: string;\n zoneId?: string;\n status: SeatStatus;\n selected: boolean;\n visible: boolean;\n renderedFontPx: number;\n fill: string;\n ink: string;\n opacity: number;\n /** Buyer-visible accessibility glyph evidence. Filter emphasis uses a\n * screen-space minimum so wheelchair provision remains recognizable at fit. */\n accessibilityMarker?: {\n glyphVisible: boolean;\n glyphWidthPx: number;\n emphasizedByFilter: boolean;\n };\n /** Direct Konva shape bounds and the production near-miss rescue combined. */\n pointerTarget: {\n active: boolean;\n directWidthPx: number;\n directHeightPx: number;\n effectiveMinimumPx: number;\n };\n /** Centre of the painted unit, even when its text is intentionally hidden. */\n screenCenter: { x: number; y: number };\n screenBox?: { x: number; y: number; width: number; height: number };\n hiddenReason?: RenderedLabelHiddenReason;\n}\n\nexport interface RenderedHierarchyLabelEvidence {\n id: string;\n kind: 'section' | 'zone';\n role: 'name' | 'availability' | 'price';\n label: string;\n visible: boolean;\n renderedFontPx: number;\n opacity: number;\n fill: string;\n ink: string;\n /** Independent geometric containment check for section-owned text. */\n fitsContainer?: boolean;\n screenBox?: { x: number; y: number; width: number; height: number };\n}\n\nexport interface RenderedFreeTextEvidence {\n objectId: string;\n kind: 'free-text' | 'stage' | 'table' | 'decor' | 'ga-label' | 'ga-capacity';\n text: string;\n visible: boolean;\n renderedFontPx: number;\n ink: string;\n background: string;\n opacity: number;\n screenBox?: { x: number; y: number; width: number; height: number };\n hiddenReason?: 'below-minimum-size' | 'outside-viewport' | 'renderer-hidden';\n}\n\nexport interface RenderedGAAreaEvidence {\n areaId: string;\n label: string;\n capacity: number;\n categoryKey: string;\n /** Owning logical section when the rendered GA surface is section-contained. */\n sectionId?: string;\n visible: boolean;\n interactive: boolean;\n opacity: number;\n fill: string;\n effectiveBackground: string;\n screenBox?: { x: number; y: number; width: number; height: number };\n}\n\nexport interface RendererQualityEvidence {\n viewport: { width: number; height: number };\n /** Runtime projection actually used for the pixels and hit graph below. */\n projection: RendererViewMode;\n /** Phase-C proof metadata. Present only in the projected-2.5D lane. */\n perspective?: {\n model: 'pinhole-exact-seat-anchors';\n sectionSurfaceModel: 'tangent-plane';\n exactSeatAnchorCount: number;\n depthSorted: true;\n };\n canvasBackground: string;\n effectiveScale: number;\n rung: LodRung;\n minimumVisibleLabelPx: number;\n totalLabelledBookableUnits: number;\n visibleLabels: number;\n hiddenLabels: number;\n /** Seats/table-seats/booths plus the full GA capacity. */\n totalBookableUnits: number;\n selectionRingSeatIds: string[];\n selectionRingColor: string;\n focusedSectionId: string | null;\n focusBackdropVisible: boolean;\n categoryFilterKeys: string[] | null;\n /** Exact scene-graph proof for the clean section-first overview contract. */\n overviewStyle: {\n visibleSectionShells: number;\n categoryPaintedSectionShells: number;\n visibleCategoryDetailOutlines: number;\n visibleSectionRowHints: number;\n visibleSectionAvailabilityLabels: number;\n visibleSectionGADetails: number;\n };\n labels: RenderedBookableLabelEvidence[];\n gaAreas: RenderedGAAreaEvidence[];\n hierarchyLabels: RenderedHierarchyLabelEvidence[];\n freeTextLabels: RenderedFreeTextEvidence[];\n}\n\nexport interface ISeatmapRenderer {\n /** Replace the chart. Resets selection and statuses, zooms to fit.\n * `opts.floorId` picks which floor to render on a multi-floor chart (Batch 5). */\n setChart(doc: ChartDoc, opts?: { floorId?: string }): void;\n /** Bulk status update; re-renders affected seats only. */\n setStatus(seatIds: string[], status: SeatStatus): void;\n /**\n * Mark the active buyer's own held seats. They remain server-status `held`,\n * but render with the buyer selection treatment instead of the anonymous\n * unavailable treatment used for another buyer's hold.\n */\n setOwnedHold?(seatIds: string[] | null): void;\n /**\n * Mark one selected seat as the buyer's pending confirmation candidate.\n * The candidate receives a strong focus halo while unrelated seats recede;\n * pass null after Select/Cancel. This is visual only and never mutates the\n * renderer selection.\n */\n setSelectionFocus?(seatId: string | null): void;\n /**\n * SYNCHRONOUS repaint that bypasses requestAnimationFrame. Konva's batchDraw()\n * (used by setStatus and friends) schedules the actual paint on the next rAF\n * tick, which Chrome throttles/pauses on hidden, backgrounded, or occluded\n * tabs — so a seat-status delta updates the scene graph but the pixels never\n * change until the tab is foregrounded again. forceDraw() paints the affected\n * layers immediately (Layer.draw() is synchronous) and flushes any pending\n * cache-debounce, so a caller (visibilitychange catch-up, or an opted-in\n * always-live board) can guarantee the canvas reflects current state\n * regardless of tab visibility. No-op difference in the foreground.\n */\n forceDraw(): void;\n getStatus(seatId: string): SeatStatus;\n getSelection(): ExpandedSeat[];\n clearSelection(): void;\n /** Update the buyer selection cap without rebuilding the chart or camera. */\n setMaxSelection?(maxSelection: number): void;\n /**\n * Programmatically restore free seats (for example an Undo action). Added\n * seats respect the active cap and do not reopen a confirmation popover.\n */\n select?(seatIds: string[]): ExpandedSeat[];\n /**\n * Dynamically update organizer-only interaction without rebuilding the\n * renderer. No buyer surface calls this; every behavior remains gated by\n * `manageMode` exactly as it is at construction time.\n */\n setManageInteraction?(options: {\n manageMode: boolean;\n marqueeSelect: boolean;\n selectableStatuses: SeatStatus[];\n maxSelection?: number;\n }): void;\n /** Organizer-only section heat overlay. Values are normalized 0..1. */\n setSectionHeat?(scores: Record<string, number> | null): void;\n /**\n * Manage-mode bulk selection helpers (no-op / empty unless `manageMode`).\n * They select the matching SELECTABLE seats (respecting `selectableStatuses`\n * + closed sections), union with the current selection, and return the seats\n * they added — the SDK SeatManager expands category/row/section picks to\n * labels and drives one batched block/unblock from them.\n */\n selectAllSelectable?(): ExpandedSeat[];\n selectByLabels?(labels: string[]): ExpandedSeat[];\n /** Exact-render QA only: select one server-chosen unit without label ambiguity. */\n setEvidenceSelection?(seatId: string): boolean;\n /** Selectable seats belonging to a section OR zone id (no selection side-effect). */\n getSelectableInSection?(sectionId: string): ExpandedSeat[];\n /** Programmatic deselect of specific seats (e.g. chip × in the cart). */\n deselect(seatIds: string[]): void;\n /**\n * Brief attention pulse on a seat (a ring that expands + fades once) — used to\n * signal live activity, e.g. a seat \"just taken\" by another buyer via a WS\n * delta. Purely visual; no state change. `color` overrides the default.\n */\n flashSeat(seatId: string, color?: string): void;\n /**\n * Brief organizer attention pulse around a whole section. This is a visual\n * overlay only: it never changes section geometry, hit targets, selection, or\n * the active camera. Useful for grouped realtime operations at venue overview.\n */\n flashSection?(sectionId: string, color?: string): void;\n zoomToFit(): void;\n /** Zoom in one step about the viewport center, clamped to the usual zoom bounds. */\n zoomIn(): void;\n /** Zoom out one step about the viewport center, clamped to the usual zoom bounds. */\n zoomOut(): void;\n /** Individually status-managed seats/table-seats/booths; excludes GA capacity. */\n seatCount(): number;\n /** Seats/table-seats/booths plus the full capacity of rendered GA areas. */\n bookableCount(): number;\n /**\n * Maps a chart-space point (or a seat, by its x/y) to container-relative\n * screen pixels, using the current stage scale/position. Lets host UI anchor\n * DOM overlays (confirm card, tooltip) over a live seat and re-anchor them\n * on `onViewChange`.\n */\n worldToScreen(point: Point): { x: number; y: number };\n /** When on, dim non-accessible free seats so accessible seats stand out. */\n setAccessibleFilter(on: boolean): void;\n /**\n * Dim free seats that lack ANY of these accessibility types. `null` clears the\n * filter; `[]` means \"any accessible seat\" (same as setAccessibleFilter(true)).\n */\n setAccessibilityFilter(types: AccessibilityType[] | null): void;\n /** Legend hover-highlight: dim free seats of other categories (null clears). */\n setCategoryHighlight?(key: string | null): void;\n /** Price-band filter (F4): dim free seats whose category is NOT in `keys`\n * (null clears). The widget resolves which categories fall in the band. */\n setCategoryFilter?(keys: string[] | null): void;\n /** Smoothly frame the currently available seats in these categories. `null`\n * returns to the full chart. Used after an explicit buyer price-filter action. */\n focusCategories?(keys: string[] | null): void;\n /** Dim the seats of these section/zone ids (organizer manager: held-back inventory). */\n setDimmedSections?(ids: string[] | null): void;\n /**\n * Phase 2 event-level section states: mark these section/zone ids `closed` —\n * flat grey block, seats greyed + not pickable, section stays rendered.\n * `null`/empty clears. (Distinct from the buyer's applyHidden seat-strip.)\n */\n setClosedSections?(ids: string[] | null): void;\n /**\n * AXS section-focus: dim + desaturate every other section, draw a calm backdrop\n * behind this section, and glide the camera to frame it. Seat-picking is gated\n * until seats are large enough on screen (≥ LABEL_SCALE). Slice 5 / Phase 2 §4.\n */\n focusSection?(id: string): void;\n /** Clear an AXS section focus (restore full-bowl brightness + drop backdrop). */\n clearSectionFocus?(): void;\n /** The currently AXS-focused section id, or null. */\n getFocusedSection?(): string | null;\n /** World-space rect currently visible in the viewport (minimap viewport frame). */\n getVisibleWorldRect?(): { x: number; y: number; width: number; height: number };\n /** Axis-aligned world bounds of all seats + section outlines (minimap frame). */\n getWorldBounds?(): { x: number; y: number; width: number; height: number };\n /**\n * Colorblind-safe mode: category hues switch to an Okabe-Ito palette and\n * booked seats render hollow (a non-color cue), so seat state never relies\n * on hue alone. Off (the default) renders exactly as before.\n */\n setColorblindSafe?(on: boolean): void;\n /**\n * Switch the projection. `'flat'` = normal top-down; `'isometric'` = the\n * legacy affine preview; `'perspective'` = projected 2.5D with exact pinhole\n * seat anchors/native hit shapes and bounded per-section tangent surfaces.\n * Purely visual — the chart is authored flat.\n */\n setViewMode?(mode: RendererViewMode): void;\n /** Current projection (defaults to 'flat' when unimplemented). */\n getViewMode?(): RendererViewMode;\n /** Multi-floor (Batch 5): switch the shown floor; list floors; read the active id. */\n setActiveFloor?(floorId: string): void;\n getFloors?(): { id: string; name: string }[];\n getActiveFloorId?(): string;\n /** Render all floors stacked (3D overview) vs the active floor. No-op single-floor. */\n setStacked?(on: boolean): void;\n isStacked?(): boolean;\n /**\n * Section id whose outline contains a container-relative screen point (or null).\n * Feeds the far-zoom \"tap a section to zoom in\" flow (Slice 5).\n */\n sectionAt?(clientPoint: Point): string | null;\n /** Seat ids belonging to a section — for the section-summary card (Slice 5). */\n sectionMembers?(id: string): string[];\n /**\n * Smoothly glide (pan+zoom) the camera to frame a section (by id) or a world-\n * space bounds rect over a calm easeInOutCubic glide. `prefers-reduced-motion` snaps.\n * A pointer-down (grab/pan) cancels an in-flight glide. Slice 5 \"glide in\".\n */\n focusRegion?(\n target: string | { x: number; y: number; width: number; height: number },\n opts?: { animate?: boolean; minScale?: number; durationMs?: number },\n ): void;\n /** Current LOD rung derived from zoom (for the ZONES/SECTIONS/SEATS pill). */\n getRung?(): LodRung;\n /** Jump the camera to a rung's zoom band, centred on the chart (glided). */\n setRung?(rung: LodRung): void;\n /** Read actual browser-rendered label visibility, size, fill, ink and state.\n * Pure diagnostic: it never changes chart or renderer state. */\n getRenderedQualityEvidence(): RendererQualityEvidence;\n destroy(): void;\n}\n\n/** localStorage key the Designer writes and the Picker reads. */\nexport const CHART_STORAGE_KEY = 'seatmap.chart';\n","/**\n * Real-world scale primitives — the ONE place the app fixes chart-unit ↔ metre ↔\n * renderer-world scale, and the section 3D-geometry resolver that rides on it.\n *\n * Everything that converts between chart units, metres, and renderer \"world\"\n * units derives from {@link METRES_PER_CHART_UNIT}. Renderer world units and\n * chart units are 1:1, so metres → world is exactly {@link CHART_UNITS_PER_METRE}\n * (the single m→world conversion constant Phase B consumers use).\n *\n * This is a leaf module (types only) so both `layout.ts` and `sections.ts` can\n * import it without the two forming an import cycle.\n */\nimport type { SectionObject } from './types';\n\n/**\n * Chart units → metres. seatSpacing 24 ≈ 0.55 m (a real seat pitch). This single\n * constant anchors every real-world scale in the app; `generatePanorama` and the\n * iso lift both derive from it instead of re-declaring their own 0.55/24.\n */\nexport const METRES_PER_CHART_UNIT = 0.55 / 24;\n\n/**\n * Metres → chart units, i.e. metres → renderer world units (world == chart units\n * in the engine). THE single m→world conversion constant: the iso view lifts an\n * elevated section by `sectionGeometry(section).height × CHART_UNITS_PER_METRE`.\n */\nexport const CHART_UNITS_PER_METRE = 1 / METRES_PER_CHART_UNIT;\n\n/**\n * Renderer world units a section lifts per {@link SectionObject.elevation} tier in\n * the legacy iso view (mirror of `SeatmapRenderer`'s old `LIFT_PER_STEP`). Kept so\n * the tier→metres fallback below reproduces today's iso look byte-for-byte.\n */\nexport const LIFT_PER_STEP_WORLD = 58;\n\n/**\n * Metres of front-edge height one elevation tier represents. Chosen (not guessed)\n * so `elevation × TIER_HEIGHT_M` metres, scaled back through\n * {@link CHART_UNITS_PER_METRE}, equals the legacy `elevation × LIFT_PER_STEP`\n * world lift exactly — an un-authored chart stays pixel-identical. ≈ 1.329 m/tier.\n */\nexport const TIER_HEIGHT_M = LIFT_PER_STEP_WORLD * METRES_PER_CHART_UNIT;\n\n/** Canonical authored section-geometry bounds. Keep every UI/API/renderer on\n * these constants so a producer cannot silently invent a second unit system. */\nexport const SECTION_ELEVATION_TIER_MIN = 0;\nexport const SECTION_ELEVATION_TIER_MAX = 3;\nexport const SECTION_HEIGHT_MIN_M = 0;\nexport const SECTION_HEIGHT_MAX_M = 120;\nexport const SECTION_RAKE_MIN_DEG = 0;\nexport const SECTION_RAKE_MAX_DEG = 45;\n\n/** Curated charts released before the height field used coarse levels 4–7.\n * Preserve their established lift while drafts/templates migrate to an explicit\n * height. Values above 7 came from broken compiler multipliers and must never be\n * interpreted as physical tiers. */\nexport const LEGACY_SECTION_ELEVATION_TIER_MAX = 7;\n\n/**\n * Seated spectator eye height above the tier floor (arena ≈ 1.20 m; theatre refs\n * use 1.15 m). Also the flat-ground baseline eye height, so a flat, ground-level\n * seat produces zero elevation offset in the 360° stage-pitch math (back-compat).\n */\nexport const SEATED_EYE_HEIGHT_M = 1.2;\n\n/**\n * Resolve a section's real 3D geometry, applying the legacy-elevation fallback so\n * old charts and new charts share ONE code path. Every consumer (iso lift, 360°\n * eye-height, author lint, any 2D depth cue) must call this and never read the raw\n * {@link SectionObject.height}/{@link SectionObject.rake} fields — that is what\n * keeps un-authored charts rendering identically to today.\n *\n * - `height`: authored absolute metres if present, else owning-floor base height\n * plus `elevation × TIER_HEIGHT_M`.\n * - `rake`: authored degrees if present, else 0 (flat).\n *\n * Malformed values are bounded here as a last defensive barrier. Structural\n * validation still reports them so drafts can be repaired instead of silently\n * persisting a renderer-only interpretation.\n *\n * Pure — no document mutation, no side effects.\n */\nexport interface SectionGeometryContext {\n /** Absolute physical height of the owning floor above the venue datum. */\n floorBaseHeightM?: number;\n}\n\nfunction finiteClamped(value: number | undefined, min: number, max: number, fallback: number): number {\n return Number.isFinite(value) ? Math.max(min, Math.min(max, value as number)) : fallback;\n}\n\n/** Canonical 0–3 tier exposed by Designer/shared operations. */\nexport function sectionElevationTier(value: number | undefined): number {\n if (!Number.isFinite(value)) return SECTION_ELEVATION_TIER_MIN;\n return Math.max(\n SECTION_ELEVATION_TIER_MIN,\n Math.min(SECTION_ELEVATION_TIER_MAX, Math.round(value as number)),\n );\n}\n\n/** Runtime-only compatibility tier. Released curated charts used integers 4–7;\n * preserve those while bounding compiler mistakes such as 12/55/220 to the\n * canonical maximum. New documents must store only {@link sectionElevationTier}. */\nfunction compatibleAutomaticTier(value: number | undefined): number {\n if (!Number.isFinite(value) || !Number.isInteger(value) || (value as number) < 0) return 0;\n if ((value as number) <= LEGACY_SECTION_ELEVATION_TIER_MAX) return value as number;\n return SECTION_ELEVATION_TIER_MAX;\n}\n\nexport function sectionGeometry(\n section: Pick<SectionObject, 'elevation' | 'height' | 'rake'>,\n context: SectionGeometryContext = {},\n): {\n height: number;\n rake: number;\n} {\n const floorBaseHeightM = finiteClamped(\n context.floorBaseHeightM,\n SECTION_HEIGHT_MIN_M,\n SECTION_HEIGHT_MAX_M,\n 0,\n );\n const automaticHeight = Math.min(\n SECTION_HEIGHT_MAX_M,\n floorBaseHeightM + compatibleAutomaticTier(section.elevation) * TIER_HEIGHT_M,\n );\n const height = section.height === undefined\n ? automaticHeight\n : finiteClamped(section.height, SECTION_HEIGHT_MIN_M, SECTION_HEIGHT_MAX_M, automaticHeight);\n const rake = finiteClamped(section.rake, SECTION_RAKE_MIN_DEG, SECTION_RAKE_MAX_DEG, 0);\n return { height, rake };\n}\n","/**\n * Pure geometry primitives for the view3d scene — no OGL, no DOM, so the whole\n * scene-model builder is unit-testable in a plain runtime.\n *\n * Coordinate convention: chart units (x, y) map to world metres as\n * worldX = x * METRES_PER_CHART_UNIT\n * worldZ = y * METRES_PER_CHART_UNIT\n * worldY = up (height in metres)\n * i.e. the chart's audience-depth (+y) becomes world +Z, and Y is the vertical.\n */\n\nimport earcut from 'earcut';\nimport polygonClipping from 'polygon-clipping';\nimport type { Point } from '../../core/types';\nimport { CHART_UNITS_PER_METRE, METRES_PER_CHART_UNIT } from '../../core/units';\nimport type { RGB } from '../palette';\n\nexport const M = METRES_PER_CHART_UNIT;\n\nexport interface MeshData {\n /** Non-indexed triangle soup: 3 floats per vertex. */\n position: Float32Array;\n normal: Float32Array;\n /** Baked vertex colour incl. AO, 3 floats per vertex. */\n color: Float32Array;\n /**\n * Owning floor index per vertex.\n *\n * Lets one merged mesh be dimmed per floor without splitting it into a draw\n * call per floor. A multi-floor chart (the opera house has three) draws every\n * floor at once, so the balcony sits over the parterre and hides it; isolating\n * one is the only way to look at the level you are actually booking.\n */\n floor: Float32Array;\n /**\n * Small renderer material class per vertex:\n * 0 = ordinary structure, 1 = authored powered display,\n * 2 = row riser, 3 = row tread.\n */\n material: Float32Array;\n /** Vertex count (position.length / 3). */\n count: number;\n}\n\n/**\n * Thinnest triangle the mesh will accept, world metres.\n *\n * Measured as the shortest altitude. Every path that triangulates a polygon can\n * emit degenerate slivers from inputs that are geometrically valid: earcut fans\n * near-collinear runs, and a boolean union of overlapping shapes leaves\n * zero-width SLITS where two boundaries nearly coincide — measured at 9.74 m long\n * and 0.5 mm wide when the block footprints were introduced, with aspect ratios\n * to 4.6 million. Such a triangle has a numerically meaningless face normal and\n * flat-shades as a dark hairline across the surface.\n *\n * Cleaning each producer separately does not work: the slit's vertices are far\n * apart, so point-merging and Douglas-Peucker both legitimately keep them. A\n * guard at the point of emission catches every producer at once, and dropping a\n * 2 mm-wide triangle cannot open a visible hole — it is sub-pixel at any view\n * that could ever show it.\n */\nconst MIN_TRI_ALTITUDE_M = 0.002;\n\n/** True when a triangle is too thin to contribute anything but shading noise. */\nfunction isDegenerate(\n p0: readonly [number, number, number],\n p1: readonly [number, number, number],\n p2: readonly [number, number, number],\n): boolean {\n const e0 = Math.hypot(p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]);\n const e1 = Math.hypot(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]);\n const e2 = Math.hypot(p0[0] - p2[0], p0[1] - p2[1], p0[2] - p2[2]);\n const longest = Math.max(e0, e1, e2);\n if (longest < 1e-9) return true;\n const s = (e0 + e1 + e2) / 2;\n const area = Math.sqrt(Math.max(0, s * (s - e0) * (s - e1) * (s - e2)));\n return (2 * area) / longest < MIN_TRI_ALTITUDE_M;\n}\n\n/** Accumulates flat-shaded, per-vertex-coloured triangles. */\n/**\n * Growable Float32 buffer.\n *\n * `MeshBuilder` accumulated into plain `number[]` and converted once at the end.\n * On a 99k-seat venue that meant four arrays of ~1.2M boxed numbers each and a\n * final copy of all of them — profiled at 11 % of scene build in `build()`\n * alone, plus a share of the 8 % spent in GC. Writing straight into typed\n * storage that doubles when full removes both the boxing and the final copy.\n */\nclass F32Buffer {\n private buf: Float32Array;\n private len = 0;\n\n constructor(initial = 1 << 14) {\n this.buf = new Float32Array(initial);\n }\n\n push3(a: number, b: number, c: number): void {\n if (this.len + 3 > this.buf.length) this.grow(this.len + 3);\n this.buf[this.len++] = a;\n this.buf[this.len++] = b;\n this.buf[this.len++] = c;\n }\n\n push1(a: number): void {\n if (this.len + 1 > this.buf.length) this.grow(this.len + 1);\n this.buf[this.len++] = a;\n }\n\n private grow(need: number): void {\n let cap = this.buf.length * 2;\n while (cap < need) cap *= 2;\n const next = new Float32Array(cap);\n next.set(this.buf.subarray(0, this.len));\n this.buf = next;\n }\n\n get length(): number { return this.len; }\n\n /** A copy trimmed to the used length. */\n toArray(): Float32Array {\n return this.buf.slice(0, this.len);\n }\n}\n\nexport class MeshBuilder {\n private pos = new F32Buffer();\n private nor = new F32Buffer();\n private col = new F32Buffer();\n private flr = new F32Buffer();\n private mat = new F32Buffer();\n /** Floor index stamped onto every triangle emitted from now on. */\n private currentFloor = 0;\n private currentMaterial = 0;\n\n /** Stamp subsequent triangles as belonging to `index`. */\n setFloor(index: number): void {\n this.currentFloor = index;\n }\n\n /** Stamp subsequent triangles with a small renderer material class. */\n setMaterial(index: number): void {\n this.currentMaterial = index;\n }\n\n /** One triangle with a shared (flat) normal and per-vertex colours. */\n tri(\n p0: readonly [number, number, number],\n p1: readonly [number, number, number],\n p2: readonly [number, number, number],\n n: readonly [number, number, number],\n c0: RGB,\n c1: RGB = c0,\n c2: RGB = c0,\n ): void {\n if (isDegenerate(p0, p1, p2)) return;\n this.pos.push3(p0[0], p0[1], p0[2]); this.pos.push3(p1[0], p1[1], p1[2]); this.pos.push3(p2[0], p2[1], p2[2]);\n this.nor.push3(n[0], n[1], n[2]); this.nor.push3(n[0], n[1], n[2]); this.nor.push3(n[0], n[1], n[2]);\n this.col.push3(c0[0], c0[1], c0[2]); this.col.push3(c1[0], c1[1], c1[2]); this.col.push3(c2[0], c2[1], c2[2]);\n this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor);\n this.mat.push1(this.currentMaterial); this.mat.push1(this.currentMaterial); this.mat.push1(this.currentMaterial);\n }\n\n /** One triangle with independent per-vertex normals (smooth shading). */\n triN(\n p0: readonly [number, number, number],\n p1: readonly [number, number, number],\n p2: readonly [number, number, number],\n n0: readonly [number, number, number],\n n1: readonly [number, number, number],\n n2: readonly [number, number, number],\n c0: RGB,\n c1: RGB = c0,\n c2: RGB = c0,\n ): void {\n // `triN` is used for caps with authoritative analytic normals. A very thin\n // earcut triangle therefore shades exactly like its neighbours; deleting it\n // would open a real hole in the otherwise watertight surface. Keep the\n // sliver guard on `tri`, where a face-derived normal is unreliable, but\n // preserve all finite analytically-normaled cap coverage here.\n if (![...p0, ...p1, ...p2].every(Number.isFinite)) return;\n this.pos.push3(p0[0], p0[1], p0[2]); this.pos.push3(p1[0], p1[1], p1[2]); this.pos.push3(p2[0], p2[1], p2[2]);\n this.nor.push3(n0[0], n0[1], n0[2]); this.nor.push3(n1[0], n1[1], n1[2]); this.nor.push3(n2[0], n2[1], n2[2]);\n this.col.push3(c0[0], c0[1], c0[2]); this.col.push3(c1[0], c1[1], c1[2]); this.col.push3(c2[0], c2[1], c2[2]);\n this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor);\n this.mat.push1(this.currentMaterial); this.mat.push1(this.currentMaterial); this.mat.push1(this.currentMaterial);\n }\n\n get vertexCount(): number {\n return this.pos.length / 3;\n }\n\n build(): MeshData {\n return {\n position: this.pos.toArray(),\n normal: this.nor.toArray(),\n color: this.col.toArray(),\n floor: this.flr.toArray(),\n material: this.mat.toArray(),\n count: this.pos.length / 3,\n };\n }\n}\n\n/** Face normal of a triangle (right-handed). */\nexport function faceNormal(\n a: readonly [number, number, number],\n b: readonly [number, number, number],\n c: readonly [number, number, number],\n): [number, number, number] {\n const ux = b[0] - a[0], uy = b[1] - a[1], uz = b[2] - a[2];\n const vx = c[0] - a[0], vy = c[1] - a[1], vz = c[2] - a[2];\n let nx = uy * vz - uz * vy;\n let ny = uz * vx - ux * vz;\n let nz = ux * vy - uy * vx;\n const len = Math.hypot(nx, ny, nz) || 1;\n nx /= len; ny /= len; nz /= len;\n return [nx, ny, nz];\n}\n\nexport interface Triangulation {\n /** Outline points followed by every hole's points, in order. */\n pts: Point[];\n /** Triangle vertex indices into `pts` (length is a multiple of 3). */\n tris: number[];\n}\n\n/** Triangulate a closed polygon with optional holes via earcut. */\nexport function triangulate(outline: Point[], holes?: Point[][]): Triangulation {\n const pts: Point[] = [...outline];\n const flat: number[] = [];\n for (const p of outline) flat.push(p.x, p.y);\n const holeIndices: number[] = [];\n if (holes) {\n for (const hole of holes) {\n if (hole.length < 3) continue;\n holeIndices.push(pts.length);\n for (const p of hole) {\n pts.push(p);\n flat.push(p.x, p.y);\n }\n }\n }\n const tris = earcut(flat, holeIndices.length ? holeIndices : undefined, 2);\n return { pts, tris };\n}\n\n/** Centroid of a point ring (average — good enough for wall orientation). */\nexport function centroid(pts: Point[]): Point {\n let x = 0, y = 0;\n for (const p of pts) { x += p.x; y += p.y; }\n const n = pts.length || 1;\n return { x: x / n, y: y / n };\n}\n\n/** Signed area of a ring (shoelace). Positive = CCW, negative = CW, ~0 = degenerate. */\nexport function signedArea(pts: Point[]): number {\n let a = 0;\n for (let i = 0, n = pts.length; i < n; i++) {\n const p = pts[i], q = pts[(i + 1) % n];\n a += p.x * q.y - q.x * p.y;\n }\n return a / 2;\n}\n\n/** Return the ring wound counter-clockwise (reversed copy if it was CW), so\n * CW and CCW inputs of the same polygon extrude to identical geometry. */\nexport function toCCW(pts: Point[]): Point[] {\n return signedArea(pts) < 0 ? [...pts].reverse() : pts;\n}\n\n/** Recursion ceiling for uniform cap subdivision (4^d triangles per seed). */\nconst MAX_CAP_SPLIT_DEPTH = 3;\n\n/** Max deviation of the plane from the surface, sampled over a triangle. */\nfunction capDeviation(a: Point, b: Point, c: Point, topY: (p: Point) => number): number {\n const ya = topY(a), yb = topY(b), yc = topY(c);\n const edge = (p: Point, q: Point, yp: number, yq: number): number =>\n Math.abs(topY({ x: (p.x + q.x) / 2, y: (p.y + q.y) / 2 }) - (yp + yq) / 2);\n const cx3 = (a.x + b.x + c.x) / 3, cy3 = (a.y + b.y + c.y) / 3;\n const yCen = (ya + yb + yc) / 3;\n let worst = Math.max(\n edge(a, b, ya, yb), edge(b, c, yb, yc), edge(c, a, yc, ya),\n Math.abs(topY({ x: cx3, y: cy3 }) - yCen),\n );\n // Mid-edge-to-centroid probes: the deviation is concave and zero at the\n // vertices, so its maximum lies in the interior and edge samples alone\n // under-report it — worst at the fold where a flat front plateau meets a rake.\n const probe = (px: number, py: number, yp: number): void => {\n const d = Math.abs(topY({ x: (px + cx3) / 2, y: (py + cy3) / 2 }) - (yp + yCen) / 2);\n if (d > worst) worst = d;\n };\n probe((a.x + b.x) / 2, (a.y + b.y) / 2, (ya + yb) / 2);\n probe((b.x + c.x) / 2, (b.y + c.y) / 2, (yb + yc) / 2);\n probe((c.x + a.x) / 2, (c.y + a.y) / 2, (yc + ya) / 2);\n return worst;\n}\n\n/** Worst deviation over a triangle uniformly subdivided `depth` times. */\nfunction maxDevAtDepth(a: Point, b: Point, c: Point, topY: (p: Point) => number, depth: number): number {\n if (depth <= 0) return capDeviation(a, b, c, topY);\n const ab = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n const bc = { x: (b.x + c.x) / 2, y: (b.y + c.y) / 2 };\n const ca = { x: (c.x + a.x) / 2, y: (c.y + a.y) / 2 };\n return Math.max(\n maxDevAtDepth(a, ab, ca, topY, depth - 1),\n maxDevAtDepth(ab, b, bc, topY, depth - 1),\n maxDevAtDepth(ca, bc, c, topY, depth - 1),\n maxDevAtDepth(ab, bc, ca, topY, depth - 1),\n );\n}\n\n/**\n * Emit one cap triangle uniformly subdivided `depth` times (1→4 each level).\n *\n * Uniform, at a depth shared by every triangle in the section — NOT adaptive.\n * Adaptive refinement was tried first and produced visible dark hairlines across\n * the decks: two triangles sharing an edge each pick which edge to split\n * independently, so one can split a shared edge while its neighbour does not.\n * That leaves a T-junction, and the crack between them shows the background\n * through the deck. A uniform depth guarantees both sides of every shared edge\n * are subdivided identically, so the mesh stays watertight by construction.\n */\nfunction emitCapUniform(\n builder: MeshBuilder,\n a: Point, b: Point, c: Point,\n topY: (p: Point) => number,\n colTop: RGB,\n depth: number,\n topN?: (p: Point) => [number, number, number],\n): void {\n if (depth > 0) {\n const ab = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n const bc = { x: (b.x + c.x) / 2, y: (b.y + c.y) / 2 };\n const ca = { x: (c.x + a.x) / 2, y: (c.y + a.y) / 2 };\n emitCapUniform(builder, a, ab, ca, topY, colTop, depth - 1, topN);\n emitCapUniform(builder, ab, b, bc, topY, colTop, depth - 1, topN);\n emitCapUniform(builder, ca, bc, c, topY, colTop, depth - 1, topN);\n emitCapUniform(builder, ab, bc, ca, topY, colTop, depth - 1, topN);\n return;\n }\n const at: [number, number, number] = [a.x * M, topY(a), a.y * M];\n const bt: [number, number, number] = [b.x * M, topY(b), b.y * M];\n const ct: [number, number, number] = [c.x * M, topY(c), c.y * M];\n if (topN) {\n // Analytic per-vertex normals: continuous across every shared edge, so the\n // deck shades as the smooth surface it approximates rather than as facets,\n // and a degenerate triangle (whose face normal is numerically meaningless)\n // still shades identically to its neighbours.\n builder.triN(at, bt, ct, topN(a), topN(b), topN(c), colTop);\n return;\n }\n let n = faceNormal(at, bt, ct);\n if (n[1] < 0) n = [-n[0], -n[1], -n[2]]; // caps face up\n builder.tri(at, bt, ct, n, colTop);\n}\n/** Ceiling on inserted points per ring edge, so a pathological outline cannot\n * explode the vertex count. */\nconst MAX_RING_SPLIT_DEPTH = 8;\n\n/**\n * Insert points along a ring's edges until each sub-edge's midpoint height is\n * within `maxError` of the straight interpolation between its ends.\n *\n * This is what lets the wall tops follow the same curved surface as the cap. A\n * wall's top edge is a straight line between two outline vertices; on a raked\n * tier the true surface bows away from that line, so an undensified ring leaves\n * the wall top hanging above or below the cap it is supposed to meet. Densifying\n * once, before triangulation, fixes cap boundary and wall top together — they\n * are built from the same point list.\n */\nfunction densifyRing(ring: Point[], topY: (p: Point) => number, maxError: number): Point[] {\n if (!Number.isFinite(maxError)) return ring;\n const out: Point[] = [];\n const emit = (p: Point, q: Point, yp: number, yq: number, depth: number): void => {\n if (depth > 0) {\n const mid: Point = { x: (p.x + q.x) / 2, y: (p.y + q.y) / 2 };\n const ym = topY(mid);\n if (Math.abs(ym - (yp + yq) / 2) > maxError) {\n emit(p, mid, yp, ym, depth - 1);\n emit(mid, q, ym, yq, depth - 1);\n return;\n }\n }\n out.push(p); // q is emitted by the next edge (ring is closed)\n };\n for (let i = 0; i < ring.length; i++) {\n const p = ring[i], q = ring[(i + 1) % ring.length];\n emit(p, q, topY(p), topY(q), MAX_RING_SPLIT_DEPTH);\n }\n return out;\n}\n\n/**\n * Drop ring points that sit closer than `eps` to the previous kept point, and\n * points that are collinear with their neighbours to within `eps`.\n *\n * This is the SOURCE of the remaining slivers, and it is upstream of shading.\n * `outsetRing`'s closing union re-emits intersection points that can land within\n * nanometres of an input vertex, and `densifyRing` then splits around them; a\n * point pair 1e-7 chart units apart is a zero-area triangle to earcut, which\n * fans it against a distant vertex. Measured aspect ratios reached 8.7e6.\n *\n * Removing them cannot move the outline perceptibly — `eps` is a fraction of a\n * millimetre in world metres — but it removes the degeneracy earcut amplifies.\n * Collinear removal is the same argument: a point exactly on the segment between\n * its neighbours carries no shape, and it is what turns one clean triangle into\n * a needle plus a remainder.\n */\nfunction dedupeRing(ring: Point[], eps: number): Point[] {\n if (ring.length < 3) return ring;\n const out: Point[] = [];\n for (const p of ring) {\n const last = out[out.length - 1];\n if (last && Math.hypot(p.x - last.x, p.y - last.y) < eps) continue;\n out.push(p);\n }\n // Close-the-loop duplicate.\n while (out.length > 2 && Math.hypot(out[0].x - out[out.length - 1].x, out[0].y - out[out.length - 1].y) < eps) {\n out.pop();\n }\n if (out.length < 3) return ring;\n // Collinear pass: drop p when its perpendicular distance to (prev,next) < eps.\n const keep: Point[] = [];\n for (let i = 0; i < out.length; i++) {\n const prev = keep.length ? keep[keep.length - 1] : out[(i - 1 + out.length) % out.length];\n const p = out[i];\n const next = out[(i + 1) % out.length];\n const ax = next.x - prev.x, ay = next.y - prev.y;\n const len = Math.hypot(ax, ay);\n if (len > eps) {\n const cross = Math.abs((p.x - prev.x) * ay - (p.y - prev.y) * ax) / len;\n if (cross < eps) continue;\n }\n keep.push(p);\n }\n return keep.length >= 3 ? keep : out;\n}\n\n/** Ring cleanup tolerance in chart units (~0.1 mm in world metres). */\nconst RING_EPS_U = 1e-3 * CHART_UNITS_PER_METRE * 0.1;\n\n/**\n * Extrude a closed polygon into a prism: a (possibly sloped) top cap, a bottom\n * cap, and side walls with a baked top→bottom AO gradient.\n *\n * `topY(p)` returns the world-metre height of the top surface at chart-point `p`\n * (constant for a slab, rake-sloped for a raked tier). `bottomY` is the floor.\n *\n * `maxCapError` (metres) bounds how far the drawn cap may deviate from `topY`;\n * omit it (or pass Infinity) for a flat top, where the cap is exact by\n * construction and no subdivision is wanted.\n */\nexport function extrudePrism(\n builder: MeshBuilder,\n outlineIn: Point[],\n holesIn: Point[][] | undefined,\n topY: (p: Point) => number,\n bottomY: number,\n colTop: RGB,\n colWall: RGB,\n ao: { top: number; wallBottom: number; bottomCap: number },\n maxCapError: number | undefined = Infinity,\n topNormal?: (p: Point) => [number, number, number],\n): void {\n // Guard degenerate/near-collinear polygons (zero visible area) — a free-hand\n // or generated outline can collapse to a sliver and would emit garbage tris.\n if (!outlineIn || outlineIn.length < 3) return;\n if (maxCapError === undefined) maxCapError = Infinity;\n if (Math.abs(signedArea(outlineIn)) < 1e-4) return;\n // Normalise winding so CW and CCW inputs produce identical geometry (the solid\n // program also disables culling, but this keeps the emitted mesh deterministic).\n // Densify the rings against the surface FIRST, so the cap boundary and the\n // wall tops are generated from one point list and cannot part company.\n const outline = dedupeRing(densifyRing(dedupeRing(toCCW(outlineIn), RING_EPS_U), topY, maxCapError), RING_EPS_U);\n const holes = holesIn?.map((h) => dedupeRing(densifyRing(dedupeRing(toCCW(h), RING_EPS_U), topY, maxCapError), RING_EPS_U))\n .filter((h) => h.length >= 3 && Math.abs(signedArea(h)) >= 1e-4);\n const { pts, tris } = triangulate(outline, holes);\n const cTop: RGB = [colTop[0] * ao.top, colTop[1] * ao.top, colTop[2] * ao.top];\n const cBot: RGB = [colTop[0] * ao.bottomCap, colTop[1] * ao.bottomCap, colTop[2] * ao.bottomCap];\n const cWallTop: RGB = [colWall[0] * ao.top, colWall[1] * ao.top, colWall[2] * ao.top];\n const cWallBot: RGB = [colWall[0] * ao.wallBottom, colWall[1] * ao.wallBottom, colWall[2] * ao.wallBottom];\n\n // Resolve ONE subdivision depth for the whole section: the smallest depth at\n // which every cap triangle tracks the surface within `maxCapError`. Shared by\n // all triangles so the cap stays watertight (see emitCapUniform).\n let capDepth = 0;\n if (Number.isFinite(maxCapError)) {\n for (; capDepth < MAX_CAP_SPLIT_DEPTH; capDepth++) {\n let worst = 0;\n for (let i = 0; i < tris.length; i += 3) {\n const d = maxDevAtDepth(pts[tris[i]], pts[tris[i + 1]], pts[tris[i + 2]], topY, capDepth);\n if (d > worst) worst = d;\n }\n if (worst <= maxCapError) break;\n }\n }\n\n // Top + bottom caps.\n for (let i = 0; i < tris.length; i += 3) {\n const a = pts[tris[i]], b = pts[tris[i + 1]], c = pts[tris[i + 2]];\n emitCapUniform(builder, a, b, c, topY, cTop, capDepth, topNormal);\n // Bottom cap (reversed winding, faces down).\n const ab: [number, number, number] = [a.x * M, bottomY, a.y * M];\n const bb: [number, number, number] = [b.x * M, bottomY, b.y * M];\n const cb: [number, number, number] = [c.x * M, bottomY, c.y * M];\n builder.tri(ab, cb, bb, [0, -1, 0], cBot);\n }\n\n // Side walls. Orient outline walls away from the outline centroid; hole walls\n // face into the hole (flip). Vertical walls → horizontal normals.\n // Wall tops must land on exactly the vertices the cap's boundary produced, or\n // a crack opens along the top edge. Uniform cap subdivision splits every\n // boundary edge into 2^capDepth equal parts, so split the wall rings the same\n // way and both meshes share the identical point list.\n const splitRing = (ring: Point[]): Point[] => {\n if (capDepth <= 0) return ring;\n const n = 1 << capDepth;\n const out: Point[] = [];\n for (let i = 0; i < ring.length; i++) {\n const a = ring[i], b = ring[(i + 1) % ring.length];\n for (let k = 0; k < n; k++) {\n const t = k / n;\n out.push({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });\n }\n }\n return out;\n };\n const oc = centroid(outline);\n const rings: Array<{ ring: Point[]; flip: boolean }> = [{ ring: splitRing(outline), flip: false }];\n if (holes) for (const h of holes) if (h.length >= 3) rings.push({ ring: splitRing(h), flip: true });\n\n for (const { ring, flip } of rings) {\n for (let i = 0; i < ring.length; i++) {\n const a = ring[i];\n const b = ring[(i + 1) % ring.length];\n const dx = (b.x - a.x) * M;\n const dz = (b.y - a.y) * M;\n let nx = dz, nz = -dx;\n const nl = Math.hypot(nx, nz) || 1;\n nx /= nl; nz /= nl;\n // Orient outward from the outline centroid.\n const mx = (a.x + b.x) / 2 - oc.x;\n const mz = (a.y + b.y) / 2 - oc.y;\n let dot = nx * mx + nz * mz;\n if (flip) dot = -dot;\n if (dot < 0) { nx = -nx; nz = -nz; }\n const n: [number, number, number] = [nx, 0, nz];\n\n const aTop: [number, number, number] = [a.x * M, topY(a), a.y * M];\n const bTop: [number, number, number] = [b.x * M, topY(b), b.y * M];\n const aBot: [number, number, number] = [a.x * M, bottomY, a.y * M];\n const bBot: [number, number, number] = [b.x * M, bottomY, b.y * M];\n builder.tri(aTop, bTop, bBot, n, cWallTop, cWallTop, cWallBot);\n builder.tri(aTop, bBot, aBot, n, cWallTop, cWallBot, cWallBot);\n }\n }\n}\n\n/** Merge several MeshData buffers into one (single draw call). */\nexport function mergeMeshData(parts: MeshData[]): MeshData {\n let total = 0;\n for (const p of parts) total += p.count;\n const position = new Float32Array(total * 3);\n const normal = new Float32Array(total * 3);\n const color = new Float32Array(total * 3);\n const floor = new Float32Array(total);\n const material = new Float32Array(total);\n let off = 0;\n for (const p of parts) {\n position.set(p.position, off * 3);\n normal.set(p.normal, off * 3);\n color.set(p.color, off * 3);\n floor.set(p.floor, off);\n material.set(p.material, off);\n off += p.count;\n }\n return { position, normal, color, floor, material, count: total };\n}\n\n/**\n * Grow a ring outward by `d` chart units — a MITER offset, not a dilation.\n *\n * Seat dots have a real world radius, so a seat authored hard against its\n * section boundary overhangs the deck and appears to float in the aisle. Seats\n * are chart data and must not move, so the DECK grows to meet them.\n *\n * The first implementation was a true Minkowski dilation (union of the ring, a\n * quad per edge, a disc per vertex). It was geometrically correct and visually\n * wrong: it tripled a 30-point outline to ~90 points with clusters of\n * near-duplicates where discs meet quads, and earcut fans such clusters into\n * needle triangles — 10,787 of 23,621 slivers at up to 20,650:1, which\n * flat-shade into dark hairlines streaking across the deck. Decimating the\n * result then cut the very corners the dilation had just rounded, putting seats\n * back over the edge. Dilate-then-decimate was fighting itself.\n *\n * A miter offset emits exactly ONE point per input vertex instead: each vertex\n * moves along its angle bisector by `d / cos(half-angle)`, which lands precisely\n * on the intersection of the two offset edges. Corners stay sharp and exact, the\n * point count does not grow, so there is nothing to decimate and no sliver\n * source. Sharp spikes are bevelled at `miterLimit`, and the single union at the\n * end resolves the self-intersections a miter offset produces where a concave\n * outline folds over itself.\n *\n * Holes are deliberately left alone: eroding them needs the complement and they\n * are rare in practice. A seat authored on a hole's rim can still overhang it.\n */\nexport function outsetRing(ring: Point[], d: number, miterLimit = 2.5): Point[] {\n if (d <= 0 || ring.length < 3) return ring;\n const r = toCCW(ring);\n const n = r.length;\n // Outward unit normal of each edge (valid because the ring is CCW).\n const nrm: Array<{ x: number; y: number }> = [];\n for (let i = 0; i < n; i++) {\n const a = r[i], b = r[(i + 1) % n];\n const dx = b.x - a.x, dy = b.y - a.y;\n const len = Math.hypot(dx, dy) || 1;\n nrm.push({ x: dy / len, y: -dx / len });\n }\n const out: [number, number][] = [];\n for (let i = 0; i < n; i++) {\n const p = r[i];\n const nPrev = nrm[(i - 1 + n) % n]; // edge arriving at p\n const nCur = nrm[i]; // edge leaving p\n let mx = nPrev.x + nCur.x, my = nPrev.y + nCur.y;\n const ml = Math.hypot(mx, my);\n // Bevel a spike (or a full reversal) into the two edge-offset points rather\n // than letting the miter shoot off to infinity.\n const bevel = (): void => {\n out.push([p.x + nPrev.x * d, p.y + nPrev.y * d]);\n out.push([p.x + nCur.x * d, p.y + nCur.y * d]);\n };\n if (ml < 1e-9) { bevel(); continue; }\n mx /= ml; my /= ml;\n const cosHalf = mx * nPrev.x + my * nPrev.y;\n const scale = 1 / Math.max(cosHalf, 1e-6);\n if (!Number.isFinite(scale) || scale > miterLimit) { bevel(); continue; }\n out.push([p.x + mx * d * scale, p.y + my * d * scale]);\n }\n if (out.length < 3) return ring;\n try {\n // Self-union: resolves the loops a miter offset folds into concave corners.\n const merged = polygonClipping.union([[...out, out[0]]]);\n let best: [number, number][] | null = null, bestArea = -Infinity;\n for (const poly of merged) {\n const area = Math.abs(signedArea(poly[0].map(([x, y]) => ({ x, y }))));\n if (area > bestArea) { bestArea = area; best = poly[0]; }\n }\n if (!best) return ring;\n const pts = best.map(([x, y]) => ({ x, y }));\n // union() repeats the first point to close the ring; drop the duplicate.\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n return pts.length >= 3 ? pts : ring;\n } catch {\n return ring; // never let a degenerate outline break the whole scene\n }\n}\n\n/** Sample an ellipse (chart units) into a closed polygon of `seg` points. */\nexport function ellipsePolygon(cx: number, cy: number, rx: number, ry: number, seg = 28): Point[] {\n const out: Point[] = [];\n for (let i = 0; i < seg; i++) {\n const a = (i / seg) * Math.PI * 2;\n out.push({ x: cx + rx * Math.cos(a), y: cy + ry * Math.sin(a) });\n }\n return out;\n}\n\n/** Axis-aligned rectangle (chart units) as a closed polygon. */\nexport function rectPolygon(x: number, y: number, w: number, h: number): Point[] {\n return [\n { x, y },\n { x: x + w, y },\n { x: x + w, y: y + h },\n { x, y: y + h },\n ];\n}\n","/**\n * The near-field seat: a three-box chair plus a compact seated-person\n * silhouette, and the per-seat facing that aims it at the stage.\n *\n * Why this exists at all: every seat in the venue was a camera-facing disc, and\n * a disc has no front. That is fine across a bowl and wrong at the one moment\n * the 3D view is selling — the buyer lands ON their seat and the row they are\n * sitting in is a line of coloured blobs rather than chairs. The renderer's own\n * programme notes call it the biggest visual gap at close range.\n *\n * What it deliberately is NOT: furniture. There are no armrests, no cushion\n * bevel, no legs. At 3–10 m what the eye needs is a pad at seat height and a\n * back rising behind it, facing the right way — the silhouette, not the detail.\n * Everything here is one small instanced base mesh, because the third draw call\n * is the budget. Improving the shared silhouette does not add a draw per person.\n */\n\n/** Local-space part id, so the fragment stage can treat furniture and people apart. */\nexport const CHAIR_PART = { pedestal: 0, pad: 1, back: 2, body: 3, head: 4 } as const;\n\n/**\n * Parts at or above this index belong to the OCCUPANT, not the chair.\n *\n * They are collapsed to nothing on an empty seat (see the chair vertex shader),\n * which is how one instanced mesh draws both a bare seat and a taken one without\n * a second draw call or a second geometry.\n */\nexport const CHAIR_PART_OCCUPANT_MIN = CHAIR_PART.body;\n\nexport interface ChairMesh {\n /**\n * Local vertex positions. x and z are in units of the seat's own chair\n * half-width (`iChairWidth`); y is in METRES and is not scaled.\n *\n * The split matters: a chart authored at a tight seat pitch needs narrower\n * chairs or they interpenetrate, but it does not need SHORTER ones — a tight\n * row is still full of adult-sized people. A uniform scale would have produced\n * a doll's-house block on exactly the densest charts.\n */\n position: Float32Array;\n normal: Float32Array;\n /** Per-vertex CHAIR_PART. */\n part: Float32Array;\n index: Uint16Array;\n vertexCount: number;\n indexCount: number;\n}\n\n/**\n * A chair's half-width as a fraction of its seat's own spacing.\n *\n * NOT the dot's `SEAT_PITCH_FRACTION` (0.42), and the difference matters. That\n * constant exists to keep round dots visually separate, so it is deliberately\n * conservative and it is capped at SEAT_DOT_RADIUS_M on top. Sizing a chair from\n * it produced tall narrow slabs — a 0.34 m chair with a 0.95 m back, an aspect\n * ratio of nearly 3:1, which reads as a gravestone rather than as seating.\n *\n * Real auditorium seats very nearly touch: at a 0.53 m pitch (the measured mean)\n * a seat is about 0.48 m across. 0.44 reproduces that — the pair occupies 0.88\n * of the pitch and the remaining 12 % is the gap you actually see in a venue.\n */\nexport const CHAIR_PITCH_FRACTION = 0.44;\n\n/**\n * Bounds on the resolved half-width, world metres.\n *\n * The floor is the honest compromise in this file. The amphitheatre measures a\n * 0.21 m minimum seat spacing, and at that spacing NO chair both fits between\n * its neighbours and reads as a chair — 21 cm is not a real seat pitch, it is a\n * chart authored tighter than the units claim. Given the choice between chairs\n * that overlap slightly and chairs that are legible, the overlap wins: it is\n * invisible from any angle a buyer actually looks from, whereas a 9 cm-wide\n * 0.95 m-tall slab is wrong from every angle. The ceiling stops a sparse box\n * seat from inflating into an armchair.\n */\nexport const CHAIR_HALF_WIDTH_MIN_M = 0.15;\nexport const CHAIR_HALF_WIDTH_MAX_M = 0.30;\n/** Used when a seat has no resolvable neighbour at all. */\nexport const CHAIR_HALF_WIDTH_DEFAULT_M = 0.24;\n\n/**\n * The world half-width of one seat's chair, from its own spacing in metres.\n *\n * Everything horizontal about the chair scales from this single number, so a\n * chair's depth tracks its width and the proportions hold at any pitch. Height\n * does not — see `ChairMesh.position`.\n */\nexport function chairHalfWidth(pitchM: number | undefined): number {\n if (pitchM === undefined || !Number.isFinite(pitchM) || pitchM <= 0) {\n return CHAIR_HALF_WIDTH_DEFAULT_M;\n }\n return Math.min(CHAIR_HALF_WIDTH_MAX_M, Math.max(CHAIR_HALF_WIDTH_MIN_M, pitchM * CHAIR_PITCH_FRACTION));\n}\n\n/**\n * Backward lean of the seat back, as a slope (metres of z per metre of rise).\n *\n * The first build had a vertical back and the result read as a crate: a flat top\n * face, a flat wall behind it, and nothing to say which way was up or which way\n * was forward. A rake is the cheapest possible chair cue — it costs a shear —\n * and it puts a lit face at an angle to every other surface in the venue, which\n * is what breaks the block up.\n *\n * Applied in the SHADER, in world metres, and deliberately not baked into the\n * base mesh. The mesh's x/z are in half-width units, so a lean baked there would\n * scale with the seat's width: the same chair would lean 20° on a wide-pitch\n * stadium row and 6° on a tight theatre row. 0.21 is about 12° from vertical,\n * which is roughly a real fixed auditorium seat.\n */\nexport const BACK_RAKE_SLOPE = 0.21;\n\n/**\n * Air between the top of the pad and the bottom of the back, world metres.\n *\n * The other half of \"crate vs chair\". Two boxes sharing a face silhouette as one\n * solid; separate them and the eye reads a seat with a back behind it, even at\n * 10 m where the gap itself is barely a pixel — because it breaks the outline.\n */\nconst PAD_BACK_GAP_M = 0.05;\n\nconst PAD_TOP_M = 0.45;\n/** World height at which the back starts, and therefore where the rake pivots. */\nexport const BACK_BASE_M = PAD_TOP_M + PAD_BACK_GAP_M;\n\n/**\n * Chair dimensions. +Z is the direction the seat FACES, so the back panel lives\n * at negative z. x/z are half-width units; y is metres above the seat's own deck\n * point, chosen against the height contract the rest of the renderer uses — a\n * seated eye sits SEAT_EYE_ABOVE_DECK (1.02 m) up, so a 0.45 m pad and a 0.92 m\n * back top put the occupant's shoulders just over the seat back.\n */\nconst CHAIR_BOXES: Array<{\n part: number;\n min: [number, number, number];\n max: [number, number, number];\n}> = [\n // Pedestal — a plain column under the pad. Without it the pad floats 0.36 m\n // over the deck and the row reads as hovering trays.\n { part: CHAIR_PART.pedestal, min: [-0.30, 0.00, -0.30], max: [0.30, 0.36, 0.30] },\n // Seat pad — a full seat width across and about as deep, which is what a real\n // one is. Its depth is bounded by the same pitch as its width, because the\n // pitch measure is the tighter of the in-row and row-to-row spacings, so a\n // tightly-raked tier cannot drive a pad into the back of the row in front.\n { part: CHAIR_PART.pad, min: [-1.00, 0.36, -0.95], max: [1.00, PAD_TOP_M, 1.00] },\n // Back panel — thin, raked, and the tallest thing in the row, so it is what\n // carries the state colour when you look along a row from behind.\n {\n part: CHAIR_PART.back,\n min: [-1.00, BACK_BASE_M, -1.00],\n max: [1.00, 0.92, -0.72],\n },\n];\n\n/** The two legs are split so the gap between them survives in silhouette. */\nconst OCCUPANT_BOXES: Array<{ part: number; min: [number, number, number]; max: [number, number, number] }> = [\n // Thighs project forward from the pad. A single lap slab brought back the\n // same toy-block problem as the old torso, so each leg keeps its own outline.\n { part: CHAIR_PART.body, min: [-0.52, 0.39, -0.08], max: [-0.08, 0.55, 0.82] },\n { part: CHAIR_PART.body, min: [0.08, 0.39, -0.08], max: [0.52, 0.55, 0.82] },\n // Lower legs drop from the knees, making the pose unmistakably seated from\n // the side and from the elevated arena camera.\n { part: CHAIR_PART.body, min: [-0.47, 0.05, 0.58], max: [-0.13, 0.43, 0.82] },\n { part: CHAIR_PART.body, min: [0.13, 0.05, 0.58], max: [0.47, 0.43, 0.82] },\n // A short neck keeps the head connected to the shoulders from behind. Most\n // of it sits inside the torso/head overlap: a long exposed cuboid made the\n // close-range figure read as a mannequin on a post.\n { part: CHAIR_PART.head, min: [-0.16, 1.04, -0.33], max: [0.16, 1.10, -0.13] },\n];\n\n/** The six axis-aligned faces of a box, as (normal, corner order). */\nconst FACES: Array<{ n: [number, number, number]; c: Array<[number, number, number]> }> = [\n // +X\n { n: [1, 0, 0], c: [[1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 0, 1]] },\n // -X\n { n: [-1, 0, 0], c: [[0, 0, 1], [0, 1, 1], [0, 1, 0], [0, 0, 0]] },\n // +Y\n { n: [0, 1, 0], c: [[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]] },\n // -Y\n { n: [0, -1, 0], c: [[0, 0, 1], [0, 0, 0], [1, 0, 0], [1, 0, 1]] },\n // +Z\n { n: [0, 0, 1], c: [[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]] },\n // -Z\n { n: [0, 0, -1], c: [[1, 0, 0], [0, 0, 0], [0, 1, 0], [1, 1, 0]] },\n];\n\ntype V3 = [number, number, number];\n\n/**\n * Build the shared chair/person base mesh once per GPU scene.\n *\n * The old occupant was two cuboids. It was cheap, but its square head and\n * rectangular body looked like a marker post rather than a buyer in a seat.\n * This keeps the same one-mesh/one-draw architecture while spending a few\n * hundred shared vertices on the information-bearing silhouette: tapered\n * shoulders, a round head and separated bent legs.\n */\nexport function buildChairMesh(): ChairMesh {\n const positions: number[] = [];\n const normals: number[] = [];\n const parts: number[] = [];\n const indices: number[] = [];\n\n const addQuad = (corners: [V3, V3, V3, V3], outward: V3, partId: number): void => {\n const base = parts.length;\n const [a, b, c] = corners;\n const ab: V3 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];\n const ac: V3 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];\n let nx = ab[1] * ac[2] - ab[2] * ac[1];\n let ny = ab[2] * ac[0] - ab[0] * ac[2];\n let nz = ab[0] * ac[1] - ab[1] * ac[0];\n const length = Math.hypot(nx, ny, nz);\n if (length > 1e-9) { nx /= length; ny /= length; nz /= length; }\n else { [nx, ny, nz] = outward; }\n if (nx * outward[0] + ny * outward[1] + nz * outward[2] < 0) {\n nx = -nx; ny = -ny; nz = -nz;\n }\n for (const corner of corners) {\n positions.push(...corner);\n normals.push(nx, ny, nz);\n parts.push(partId);\n }\n indices.push(base, base + 1, base + 2, base, base + 2, base + 3);\n };\n\n const addBox = (box: { part: number; min: V3; max: V3 }): void => {\n for (const face of FACES) {\n const corners = face.c.map((corner) => corner.map(\n (side, axis) => side ? box.max[axis] : box.min[axis],\n ) as V3) as [V3, V3, V3, V3];\n addQuad(corners, face.n, box.part);\n }\n };\n\n for (const box of CHAIR_BOXES) addBox(box);\n for (const box of OCCUPANT_BOXES) addBox(box);\n\n // Tapered seated torso. Shoulders are wider than the waist and the front\n // narrows toward the chest, so even an untextured back view reads as a human\n // upper body instead of the old rectangular parcel.\n const waistY = PAD_TOP_M;\n const shoulderY = 1.08;\n const wb = 0.47, wt = 0.69;\n const backBottom = -0.55, frontBottom = 0.08;\n const backTop = -0.48, frontTop = 0.01;\n const blb: V3 = [-wb, waistY, backBottom], brb: V3 = [wb, waistY, backBottom];\n const blf: V3 = [-wb, waistY, frontBottom], brf: V3 = [wb, waistY, frontBottom];\n const tlb: V3 = [-wt, shoulderY, backTop], trb: V3 = [wt, shoulderY, backTop];\n const tlf: V3 = [-wt, shoulderY, frontTop], trf: V3 = [wt, shoulderY, frontTop];\n addQuad([brb, trb, trf, brf], [1, 0, 0], CHAIR_PART.body);\n addQuad([blf, tlf, tlb, blb], [-1, 0, 0], CHAIR_PART.body);\n addQuad([tlf, trf, trb, tlb], [0, 1, 0], CHAIR_PART.body);\n addQuad([blb, brb, brf, blf], [0, -1, 0], CHAIR_PART.body);\n addQuad([brf, trf, tlf, blf], [0, 0, 1], CHAIR_PART.body);\n addQuad([blb, tlb, trb, brb], [0, 0, -1], CHAIR_PART.body);\n\n // Smooth low-poly ellipsoid head: 8 sides × 5 vertical bands. This is only\n // 54 shared vertices, but removing the square head is the largest perceptual\n // gain in the whole occupant. Radii are authored in the mesh's mixed units:\n // x/z scale with chair width, y remains honest world metres in the shader.\n const lonSegments = 10;\n const latSegments = 8;\n // x/z are later multiplied by the chair half-width while y stays in metres.\n // These mixed units therefore look odd on paper: the wider x/z radii produce\n // a roughly round 20–22 cm head in world space instead of the former tall,\n // narrow egg. Its lower pole overlaps the shoulders/short neck slightly so\n // rasterisation cannot open a floating gap.\n const centre: V3 = [0, 1.205, -0.23];\n const radii: V3 = [0.42, 0.115, 0.38];\n const headBase = parts.length;\n for (let lat = 0; lat <= latSegments; lat++) {\n const theta = Math.PI * lat / latSegments;\n const sy = Math.cos(theta);\n const ring = Math.sin(theta);\n for (let lon = 0; lon <= lonSegments; lon++) {\n const phi = Math.PI * 2 * lon / lonSegments;\n const dx = ring * Math.cos(phi);\n const dz = ring * Math.sin(phi);\n positions.push(\n centre[0] + radii[0] * dx,\n centre[1] + radii[1] * sy,\n centre[2] + radii[2] * dz,\n );\n let nx = dx / radii[0];\n let ny = sy / radii[1];\n let nz = dz / radii[2];\n const length = Math.hypot(nx, ny, nz) || 1;\n nx /= length; ny /= length; nz /= length;\n normals.push(nx, ny, nz);\n parts.push(CHAIR_PART.head);\n }\n }\n for (let lat = 0; lat < latSegments; lat++) {\n for (let lon = 0; lon < lonSegments; lon++) {\n const a = headBase + lat * (lonSegments + 1) + lon;\n const b = a + lonSegments + 1;\n indices.push(a, b, a + 1, a + 1, b, b + 1);\n }\n }\n\n const position = new Float32Array(positions);\n const normal = new Float32Array(normals);\n const part = new Float32Array(parts);\n const index = new Uint16Array(indices);\n return {\n position,\n normal,\n part,\n index,\n vertexCount: parts.length,\n indexCount: indices.length,\n };\n}\n\n/**\n * Per-seat facing yaw, radians, such that the chair's local +Z points at what\n * the seat looks at.\n *\n * Derived from the ROW, not from the seat alone. Aiming every seat individually\n * at the focal point is the obvious rule and it is wrong for the commonest case:\n * a straight row in a theatre fans out into a shallow arc of chairs pointing\n * slightly inward, which no real auditorium does. Taking the row's local tangent\n * and turning perpendicular to it gives BOTH cases for free — a straight row\n * comes out uniformly forward, and a curved arena row keeps the genuine radial\n * fan it is built with.\n *\n * The focal only picks the SIGN (which of the two perpendiculars faces the\n * show), so a mis-authored focal can never rotate a row sideways, only flip it.\n * A row with a single seat has no tangent and falls back to facing the focal\n * directly.\n *\n * Row membership is read as RUNS of equal `rowId` rather than through a map:\n * expanded seats come out row-contiguous, the scan is O(n) with no allocation,\n * and a chart that somehow interleaved rows degrades to shorter runs rather than\n * to wrong answers.\n */\nexport function computeSeatYaw(\n iPosition: Float32Array,\n count: number,\n rowIdAt: (index: number) => string | undefined,\n focal: (index: number) => readonly [number, number],\n): Float32Array {\n const yaw = new Float32Array(count);\n const px = (i: number): number => iPosition[i * 3];\n const pz = (i: number): number => iPosition[i * 3 + 2];\n\n let runStart = 0;\n const flushRun = (start: number, end: number): void => {\n // [start, end) share a rowId.\n const n = end - start;\n for (let i = start; i < end; i++) {\n const [fx, fz] = focal(i);\n let dx = fx - px(i);\n let dz = fz - pz(i);\n if (n >= 2) {\n // Central difference where possible; one-sided at the row ends.\n const a = Math.max(start, i - 1);\n const b = Math.min(end - 1, i + 1);\n const tx = px(b) - px(a);\n const tz = pz(b) - pz(a);\n const tl = Math.hypot(tx, tz);\n if (tl > 1e-6) {\n // Perpendicular to the row, signed toward the focal.\n let nx = -tz / tl;\n let nz = tx / tl;\n if (nx * dx + nz * dz < 0) { nx = -nx; nz = -nz; }\n dx = nx;\n dz = nz;\n }\n }\n // atan2(x, z): the yaw that rotates local +Z onto (dx, dz).\n yaw[i] = (dx === 0 && dz === 0) ? 0 : Math.atan2(dx, dz);\n }\n };\n\n for (let i = 1; i <= count; i++) {\n if (i === count || rowIdAt(i) !== rowIdAt(runStart)) {\n flushRun(runStart, i);\n runStart = i;\n }\n }\n return yaw;\n}\n","/**\n * Pure builder for the instanced seat cloud. Produces the per-instance arrays a\n * single OGL InstancedMesh consumes (one draw call for every seat), plus the\n * seatId → instanceIndex map that `setAvailability` uses to patch only the seats\n * that actually changed via a sub-range `bufferSubData` upload.\n */\n\nimport { accessibilityRingColor, type ExpandedSeat } from '../../core/types';\nimport { hexToRgb } from '../palette';\nimport { SEATED_EYE_HEIGHT_M } from '../../core/units';\nimport { M } from './geometry';\nimport { chairHalfWidth } from './seatChair';\nimport { seatStateIndex, SEAT_STATES, type SeatState3D } from '../palette';\nimport type { VenueSurfaces } from './surface';\n\n/**\n * World radius of a seat dot in metres — the single source for the shader\n * uniform AND for how far a section deck is padded so its seats sit on it.\n * (The shader may grow this with distance to hold `uMinPixels`; this is the\n * near-field base.)\n */\nexport const SEAT_DOT_RADIUS_M = 0.22;\n\n/**\n * Whether a seat has somebody in it, and who.\n *\n * Occupancy is not invented — it is the inventory. A seat that is SOLD has been\n * bought by a person, and a HELD one is being bought right now, so those are the\n * seats that get an occupant. Available and dimmed seats stay empty, and a\n * SELECTED seat stays empty too: that is the buyer's own seat, and drawing a\n * stranger in it while they choose it would be worse than an empty hall.\n *\n * This means the crowd tells the truth about the event. A nearly-sold show looks\n * full from the seat you are considering, and a quiet one looks quiet — which is\n * information a buyer actually wants, arrived at without inventing anything.\n *\n * Returns a negative value for an empty seat; otherwise a stable per-seat hash\n * in [0,1) that varies the occupant's build and tone. Stable so a person does\n * not change shape as the near-field set is re-gathered around a moving camera.\n */\nexport function seatOccupantSeed(stateIndex: number, seatIndex: number): number {\n const state = SEAT_STATES[stateIndex];\n if (state !== 'sold' && state !== 'held') return -1;\n // Cheap integer hash — deterministic, no Math.random, and well distributed\n // across neighbouring indices so adjacent seats do not come out matching.\n let h = (seatIndex + 1) * 2654435761;\n h ^= h >>> 15;\n h = Math.imul(h, 2246822519);\n h ^= h >>> 13;\n return ((h >>> 0) % 100000) / 100000;\n}\n\n/**\n * Fraction of a seat's nearest-neighbour spacing that its dot may occupy.\n *\n * Below 0.5 two adjacent dots cannot touch even when both sit at their ceiling,\n * so a gap always survives between rows. 0.42 leaves that gap visible rather\n * than hairline.\n */\nexport const SEAT_PITCH_FRACTION = 0.42;\n\nexport interface SeatInstanceData {\n count: number;\n /** vec3 per instance: world (x, y, z) in metres. */\n iPosition: Float32Array;\n /** float per instance: index into the seat-state colour LUT. */\n iState: Float32Array;\n /**\n * vec3 per instance: the seat's CATEGORY colour, used while it is available.\n *\n * 2D fills a free seat with its category colour and only overrides it once the\n * seat is held/sold/selected. 3D used one flat green for every available seat,\n * which threw away the price-tier read on switching views and made a full\n * house look like a status map instead of a venue.\n */\n iCategory: Float32Array;\n /**\n * float per instance: the largest world radius this dot may take, metres.\n *\n * The shader enforces `uMinPixels` by GROWING a dot's world radius with depth,\n * which is what merges rows into a solid mass at range — measured mean seat\n * spacing is 0.53–0.58 m against a 0.44 m dot diameter, so there is very little\n * slack to spend before neighbours touch. A global cap cannot fix it: spacing\n * is a property of the chart, and varies between sections of the same venue\n * (measured min 0.21 m on the amphitheatre against a 0.58 m mean).\n *\n * So the ceiling travels per seat, derived from that seat's own nearest\n * neighbour. A dot grows to hold its minimum pixel size and then STOPS,\n * whatever the distance. Past that point holding legibility is the LOD ladder's\n * job (fade toward the tier tint), not the dot's.\n */\n iMaxRadius: Float32Array;\n /**\n * vec3 per instance: accommodation ring colour, or (0,0,0) for none.\n *\n * 2D draws a coloured ring around every seat with an accessibility type, and\n * 3D drew nothing at all — so a wheelchair space, a companion seat or a\n * lift-armrest seat was indistinguishable from any other the moment a buyer\n * switched to the 3D view. A ring mirrors the 2D treatment exactly, needs no\n * texture, and costs one instanced attribute rather than a draw call.\n */\n iRing: Float32Array;\n /** float per instance: owning floor index, for per-floor isolation. */\n iFloor: Float32Array;\n /**\n * float per instance: facing yaw in radians, for the near-field chair mesh.\n *\n * A billboard dot has no front, so nothing here ever needed a direction. A\n * chair does, and getting it wrong is worse than drawing nothing — a row of\n * seats with their backs to the stage reads as a mistake immediately.\n *\n * Left at zero by this builder and filled by `sceneModel`, which is the layer\n * that knows what each seat FACES (its floor's focal point). Direct callers\n * and tests that build instances without a scene get zeros, which is a\n * well-defined \"facing +Z\" rather than an undefined attribute.\n */\n iYaw: Float32Array;\n /**\n * float per instance: half-width of this seat's near-field CHAIR, world metres.\n *\n * Deliberately not `iMaxRadius`. That value is the dot's ceiling — conservative\n * by design so round dots stay visually apart, and clamped to\n * SEAT_DOT_RADIUS_M on top of that. Sizing a chair from it gave 0.34 m chairs\n * under 0.95 m backs: an aspect ratio near 3:1, which reads as a headstone.\n * Real seats very nearly touch. See `chairHalfWidth`.\n */\n iChairWidth: Float32Array;\n /** 1 = physical chair; 0 = sellable empty wheelchair bay. */\n iPhysicalSeat: Float32Array;\n /** seatId → instance index (drives targeted availability updates). */\n idToIndex: Map<string, number>;\n}\n\n/**\n * Nearest-neighbour distance in chart units for every seat, via a uniform grid.\n *\n * Linear in seat count for realistic charts (the grid cell is sized to the mean\n * spacing, so each lookup touches a bounded neighbourhood) — the naive pairwise\n * version is 14k² on the Uber Arena and would show up in build time.\n */\nfunction nearestNeighbourSpacing(seats: ExpandedSeat[]): Float64Array {\n const n = seats.length;\n const out = new Float64Array(n);\n if (n < 2) { out.fill(Infinity); return out; }\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const s of seats) {\n if (s.x < minX) minX = s.x;\n if (s.y < minY) minY = s.y;\n if (s.x > maxX) maxX = s.x;\n if (s.y > maxY) maxY = s.y;\n }\n const w = Math.max(maxX - minX, 1e-6), h = Math.max(maxY - minY, 1e-6);\n // Aim at ~1 seat per cell: cell = sqrt(area / count).\n const cell = Math.max(Math.sqrt((w * h) / n), 1e-6);\n const cols = Math.max(1, Math.ceil(w / cell) + 1);\n const rows = Math.max(1, Math.ceil(h / cell) + 1);\n const buckets = new Map<number, number[]>();\n const cellOf = (x: number, y: number): number => {\n const cx = Math.min(cols - 1, Math.max(0, Math.floor((x - minX) / cell)));\n const cy = Math.min(rows - 1, Math.max(0, Math.floor((y - minY) / cell)));\n return cy * cols + cx;\n };\n for (let i = 0; i < n; i++) {\n const k = cellOf(seats[i].x, seats[i].y);\n const b = buckets.get(k);\n if (b) b.push(i); else buckets.set(k, [i]);\n }\n for (let i = 0; i < n; i++) {\n const s = seats[i];\n const cx = Math.min(cols - 1, Math.max(0, Math.floor((s.x - minX) / cell)));\n const cy = Math.min(rows - 1, Math.max(0, Math.floor((s.y - minY) / cell)));\n let best = Infinity;\n // Widen the ring until a hit is found — a sparse region (a lone box seat)\n // would otherwise report Infinity and lose its ceiling entirely.\n for (let r = 1; r <= 4; r++) {\n for (let gy = cy - r; gy <= cy + r; gy++) {\n if (gy < 0 || gy >= rows) continue;\n for (let gx = cx - r; gx <= cx + r; gx++) {\n if (gx < 0 || gx >= cols) continue;\n // Only the newly-added ring after the first pass.\n if (r > 1 && Math.abs(gy - cy) < r && Math.abs(gx - cx) < r) continue;\n const b = buckets.get(gy * cols + gx);\n if (!b) continue;\n for (const j of b) {\n if (j === i) continue;\n const d = Math.hypot(seats[j].x - s.x, seats[j].y - s.y);\n if (d < best) best = d;\n }\n }\n }\n if (Number.isFinite(best) && best <= r * cell) break;\n }\n out[i] = best;\n }\n return out;\n}\n\n/**\n * Resolve a seat's surface height in world metres — the fallback path used only\n * when no venue surface was supplied (direct callers, tests). Prefer the\n * section-resolved eye height minus the seated-eye offset.\n *\n * There is deliberately NO lift constant here any more. A dot is placed exactly\n * ON the deck, and the seat shader offsets the billboard by its own radius along\n * screen-up so the dot RESTS on that point at every distance. A fixed lift could\n * not do that: the shader enforces `uMinPixels`, so a dot's world radius grows\n * with depth and any constant clearance is eventually smaller than the dot.\n */\nfunction seatSurfaceY(seat: ExpandedSeat): number {\n const eye = seat.eyeHeightM;\n if (Number.isFinite(eye)) return Math.max(0, (eye as number) - SEATED_EYE_HEIGHT_M);\n return 0;\n}\n\nexport function buildSeatInstances(\n seats: ExpandedSeat[],\n initial?: (seat: ExpandedSeat) => SeatState3D,\n surfaces?: VenueSurfaces,\n seatFloor?: Float32Array,\n /** categoryKey -> display colour, for the AVAILABLE tint. Omitted by direct\n * callers and tests, which then get the flat state green as before. */\n categoryColor?: Map<string, string>,\n): SeatInstanceData {\n const count = seats.length;\n const iPosition = new Float32Array(count * 3);\n const iState = new Float32Array(count);\n const iCategory = new Float32Array(count * 3);\n const iMaxRadius = new Float32Array(count);\n const iChairWidth = new Float32Array(count);\n const iPhysicalSeat = new Float32Array(count);\n const iRing = new Float32Array(count * 3);\n const idToIndex = new Map<string, number>();\n const spacing = nearestNeighbourSpacing(seats);\n for (let i = 0; i < count; i++) {\n const seat = seats[i];\n // The seat's OWN row pitch when its section resolved into rows; the raw\n // nearest-neighbour distance only as a fallback for unstructured seats.\n // See `VenueSurfaces.seatPitchU` for why the raw measure is wrong.\n const resolved = surfaces?.seatPitchU(i);\n const pitchM = (resolved ?? spacing[i]) * M;\n // The ceiling is allowed BELOW the base radius. A chart authored tighter\n // than the 0.22 m default dot (measured 0.21 m minimum spacing on the\n // amphitheatre) overlaps at every distance today, including close up, and\n // shrinking those dots is the only thing that separates them. A small floor\n // keeps a pathologically dense chart's seats visible rather than vanishing.\n // A seat with no resolvable neighbour keeps the base radius, not an\n // unbounded one.\n iMaxRadius[i] = Number.isFinite(pitchM)\n ? Math.max(0.06, Math.min(SEAT_DOT_RADIUS_M, pitchM * SEAT_PITCH_FRACTION))\n : SEAT_DOT_RADIUS_M;\n // The chair reads the SAME pitch but through its own rule — see iChairWidth.\n iChairWidth[i] = chairHalfWidth(Number.isFinite(pitchM) ? pitchM : undefined);\n iPhysicalSeat[i] = seat.wheelchairSpaceType === 'no-seat' ? 0 : 1;\n iPosition[i * 3] = seat.x * M;\n // The venue surface wins when present: it is the same function the tier cap\n // is built from, which is what guarantees a seat can never sit inside it.\n iPosition[i * 3 + 1] = surfaces ? surfaces.seatDeckY(i) : seatSurfaceY(seat);\n iPosition[i * 3 + 2] = seat.y * M;\n iState[i] = seatStateIndex(initial ? initial(seat) : 'available');\n // The seat's CATEGORY colour, which is what 2D paints a free seat with\n // (`SeatmapRenderer.paintSeat` -> `seatBaseColor(categoryKey)`). 3D painted\n // every available seat one flat green instead, so a venue lost its price\n // tiers the moment a buyer switched view — and at seat level a hall of\n // identical green read as a diagram rather than a room. Falls back to the\n // same #6e7bff 2D uses when a category has no colour.\n const cat = hexToRgb(categoryColor?.get(seat.categoryKey) ?? '#6e7bff') ?? [0.43, 0.48, 1.0];\n iCategory[i * 3] = cat[0];\n iCategory[i * 3 + 1] = cat[1];\n iCategory[i * 3 + 2] = cat[2];\n // Same source of truth as 2D: the ring colour is derived from the seat's\n // accessibility types, so the two views cannot disagree about which seat is\n // an accessible one.\n if (seat.accessibility?.length) {\n const rgb = hexToRgb(accessibilityRingColor(seat.accessibility));\n if (rgb) { iRing[i * 3] = rgb[0]; iRing[i * 3 + 1] = rgb[1]; iRing[i * 3 + 2] = rgb[2]; }\n }\n idToIndex.set(seat.id, i);\n }\n return {\n count, iPosition, iState, iCategory, iMaxRadius, iRing, idToIndex, iChairWidth, iPhysicalSeat,\n iFloor: seatFloor ?? new Float32Array(count),\n iYaw: new Float32Array(count),\n };\n}\n\n/** Contiguous run of instance indices to upload in a single bufferSubData call. */\nexport interface DirtyRun {\n start: number;\n /** number of instances (floats, since iState is 1 float/instance). */\n length: number;\n}\n\n/**\n * Instances worth re-uploading just to avoid another GL call.\n *\n * Runs are merged across gaps up to this size. A `bufferSubData` has fixed\n * driver overhead regardless of how few bytes it carries, so uploading a handful\n * of unchanged instances inside one call is far cheaper than issuing a second.\n *\n * The case this exists for is a live socket delta on a big venue, where the\n * changed seats are SCATTERED rather than contiguous. Measured on a 50,400-seat\n * chart before merging: 1,000 scattered seats produced 983 separate uploads and\n * 5,000 produced 4,438 — thousands of GL calls in a single frame to move a few\n * kilobytes. A block going on sale already coalesced well; random churn did not.\n */\nconst RUN_MERGE_GAP = 64;\n\n/**\n * Apply state updates to the CPU `iState` array and return the coalesced\n * contiguous runs that changed — the caller uploads exactly those ranges and\n * never the whole buffer.\n */\nexport function applySeatStates(\n data: SeatInstanceData,\n updates: Array<{ seatId: string; state: SeatState3D }>,\n): DirtyRun[] {\n const changed: number[] = [];\n for (const u of updates) {\n const idx = data.idToIndex.get(u.seatId);\n if (idx === undefined) continue;\n const v = seatStateIndex(u.state);\n if (data.iState[idx] !== v) {\n data.iState[idx] = v;\n changed.push(idx);\n }\n }\n if (!changed.length) return [];\n changed.sort((a, b) => a - b);\n const runs: DirtyRun[] = [];\n let start = changed[0];\n let prev = changed[0];\n for (let i = 1; i < changed.length; i++) {\n const idx = changed[i];\n if (idx === prev) continue;\n // Bridge small gaps: the unchanged instances in between are rewritten from\n // `iState`, which already holds their current value, so this is a no-op for\n // them and saves a GL call. See RUN_MERGE_GAP.\n if (idx <= prev + RUN_MERGE_GAP) { prev = idx; continue; }\n runs.push({ start, length: prev - start + 1 });\n start = idx;\n prev = idx;\n }\n runs.push({ start, length: prev - start + 1 });\n return runs;\n}\n","/**\n * Resolve a chart's authored theme into the 3D scene's colours.\n *\n * Every chart already carries a `ChartTheme` — background, brand accent, seat\n * scale — and the 2D renderer and the picker chrome honour it. The 3D view did\n * not: `palette.ts` was a fixed set of constants, so a white-labelled event\n * rendered in SeatLayer's own dark grey whatever the organizer had branded. That\n * is a visible gap in a paid feature, and it is the kind of thing a customer\n * notices immediately when they switch from the 2D map to the 3D view.\n *\n * The palette in `palette.ts` stays the DEFAULT and the reference. This module\n * only rebases it, so an unthemed chart is byte-for-byte what it was.\n *\n * ## How structure is rebased\n *\n * Structure colours are not replaced by the brand colour — a venue rendered in\n * flat brand paint reads as a diagram, not a building, and the look brief is\n * deliberately \"desaturated greys for structure, saturated colour only on\n * seats\". Instead each structure colour is blended a little way toward the\n * authored background, so the whole venue picks up the brand's cast and sits in\n * its own light, while keeping the tonal relationships (tier above wall, stage\n * warmer than tier) that make the geometry readable.\n */\n\nimport type { ChartTheme } from '../core/types';\nimport { BACKGROUND, SEAT_STATE_COLORS, STRUCTURE, hexToRgb, mix, scaleRgb, type RGB, type SeatState3D } from './palette';\n\n/** How far a structure colour is pulled toward the authored background. */\nconst BACKGROUND_INFLUENCE = 0.15;\n\n/**\n * The background gradient's two stops as multiples of the authored colour.\n *\n * Fitted to the existing hand-tuned gradient on the default `#0e1117`, whose\n * per-channel ratios are 0.91/0.90/0.89 for the top stop and 1.82/1.80/1.66 for\n * the bottom. A single scalar cannot reproduce a hand-picked triple exactly, so\n * these are the best fit: an authored background lands within ~0.01 of the old\n * look, and an UNTHEMED chart bypasses this path entirely and stays identical.\n * What matters is that a themed chart gets the same vertical falloff around its\n * own colour rather than a flat wash.\n */\nconst BG_TOP_SCALE = 0.9;\nconst BG_BOTTOM_SCALE = 1.76;\n\n/** Bounds on the authored seat-size multiplier, matching `ChartTheme.seatScale`. */\nconst SEAT_SCALE_MIN = 0.7;\nconst SEAT_SCALE_MAX = 1.6;\n\nexport interface Theme3D {\n background: { top: RGB; bottom: RGB };\n structure: typeof STRUCTURE;\n seatStates: Record<SeatState3D, RGB>;\n /** Multiplier on the seat dot's world radius. */\n seatScale: number;\n}\n\n/** The unthemed default — the palette exactly as authored in `palette.ts`. */\nexport function defaultTheme3D(): Theme3D {\n return {\n background: { top: [...BACKGROUND.top] as RGB, bottom: [...BACKGROUND.bottom] as RGB },\n structure: STRUCTURE,\n seatStates: { ...SEAT_STATE_COLORS },\n seatScale: 1,\n };\n}\n\nexport function resolveTheme3D(theme: ChartTheme | undefined): Theme3D {\n const base = defaultTheme3D();\n if (!theme) return base;\n\n const bg = hexToRgb(theme.background);\n if (bg) {\n base.background = {\n top: scaleRgb(bg, BG_TOP_SCALE),\n bottom: scaleRgb(bg, BG_BOTTOM_SCALE),\n };\n // Rebase every structure colour onto the authored background. Done as one\n // pass over the palette rather than field by field, so a colour added to\n // STRUCTURE later is themed automatically instead of silently staying fixed.\n const rebased: Record<string, RGB> = {};\n for (const [key, value] of Object.entries(STRUCTURE)) {\n rebased[key] = mix(value as RGB, bg, BACKGROUND_INFLUENCE);\n }\n base.structure = rebased as unknown as typeof STRUCTURE;\n }\n\n // Selection is the one seat colour a brand owns: it is the buyer's own\n // choice reflected back, and the picker chrome already paints it in `accent`.\n // Availability, held and sold stay fixed — they carry MEANING, and letting a\n // brand recolour \"sold\" would let a chart mislead about what is for sale.\n const selection = hexToRgb(theme.selectionColor) ?? hexToRgb(theme.accent);\n if (selection) base.seatStates = { ...base.seatStates, selected: selection };\n\n const scale = theme.seatScale;\n if (typeof scale === 'number' && Number.isFinite(scale)) {\n base.seatScale = Math.min(SEAT_SCALE_MAX, Math.max(SEAT_SCALE_MIN, scale));\n }\n return base;\n}\n\n/** Flat LUT (5 × vec3) for the seat fragment shader, in `SEAT_STATES` order. */\nexport function themeSeatColorLUT(theme: Theme3D, order: readonly SeatState3D[]): number[] {\n const out: number[] = [];\n for (const s of order) out.push(...theme.seatStates[s]);\n return out;\n}\n","/**\n * Venue labels — anchors, level-of-detail, and world→screen projection.\n *\n * ## Why labels are DOM, not geometry\n *\n * The renderer is deliberately texture-free at three draw calls. Drawing text on\n * the GPU means a signed-distance font atlas: a texture, another shader, a build\n * asset, and a resolution ceiling — a lot of machinery for the few dozen labels a\n * venue actually needs. Projecting anchors and positioning DOM elements costs\n * nothing when a chart has no labels, and buys properties the GPU path cannot:\n *\n * - **Real text.** A screen reader can read the venue's structure. That is the\n * accessibility gap in 3D, not just a rendering convenience.\n * - Crisp at any device pixel ratio and any zoom, with no atlas to outgrow.\n * - `ChartTheme.fontFamily`, i18n and RTL come from the browser.\n *\n * This module is the pure half — what to label, where its anchor sits, and when\n * it should show. The overlay that positions elements lives in `index.ts`.\n *\n * ## Why the rungs mirror 2D\n *\n * 2D melts through zones → sections → seats. Labels follow the same idea for the\n * same reason: at a distance a buyer needs to know which part of the venue they\n * are looking at, and up close they need to know which block and which door. A\n * label set that does not thin out with distance turns a 51-section arena into\n * unreadable confetti.\n */\n\nimport type { Point } from '../core/types';\n\nexport type LabelKind = 'zone' | 'section' | 'ga' | 'annotation' | 'booth' | 'row' | 'seat';\n\nexport interface SceneLabel {\n id: string;\n kind: LabelKind;\n text: string;\n /** World-metre anchor the label is pinned to. */\n anchor: [number, number, number];\n /** Authored colour (`#rrggbb`), when the object carries one. */\n color?: string;\n /** Authored rotation in degrees, for annotations that specify one. */\n rotation?: number;\n}\n\n/**\n * Distance thresholds as multiples of the venue radius, matching the seat LOD's\n * scale so labels and seats thin out together rather than fighting.\n *\n * Zone labels are the far rung and switch OFF close in, where they would sit on\n * top of the section labels that have become more useful. Section labels are the\n * middle rung. Annotations and booth labels are wayfinding — only legible, and\n * only wanted, once the buyer is actually in that part of the venue.\n */\nconst ZONE_MIN_DISTANCE = 1.15;\nconst SECTION_MAX_DISTANCE = 2.2;\nconst NEAR_MAX_DISTANCE = 0.85;\n/**\n * Row and seat identity are the last two rungs, mirroring 2D's melt: a buyer\n * this close has stopped choosing a part of the venue and started choosing a\n * place to sit. Seats come in last of all — a seat number is unreadable and\n * useless until you are close enough that one row fills much of the screen.\n */\nconst ROW_MAX_DISTANCE = 0.5;\nconst SEAT_MAX_DISTANCE = 0.26;\n\n/** Which label kinds should show at this camera distance. */\nexport function visibleLabelKinds(distance: number, venueRadius: number): Set<LabelKind> {\n const r = Math.max(1e-6, venueRadius);\n const d = distance / r;\n const out = new Set<LabelKind>();\n if (d >= ZONE_MIN_DISTANCE) out.add('zone');\n // A GA area is named at the same rung as a section because that is what it is\n // — a sellable part of the venue the buyer picks before picking a place in it.\n if (d <= SECTION_MAX_DISTANCE) { out.add('section'); out.add('ga'); }\n if (d <= NEAR_MAX_DISTANCE) { out.add('annotation'); out.add('booth'); }\n if (d <= ROW_MAX_DISTANCE) out.add('row');\n if (d <= SEAT_MAX_DISTANCE) out.add('seat');\n return out;\n}\n\nexport interface Projected {\n /** CSS pixels from the container's left/top. */\n x: number;\n y: number;\n /** Normalised depth; smaller is nearer. */\n depth: number;\n /** False when the anchor is behind the camera or outside the frustum. */\n visible: boolean;\n}\n\n/**\n * Project a world point through a column-major 4x4 view-projection matrix.\n *\n * Behind-camera points are reported invisible rather than mirrored to the far\n * side of the screen, which is what a naive divide by a negative w produces —\n * a label for the section behind you appearing over the stage in front of you.\n */\nexport function projectToScreen(\n viewProjection: ArrayLike<number>,\n p: readonly [number, number, number],\n width: number,\n height: number,\n): Projected {\n const m = viewProjection;\n const x = p[0], y = p[1], z = p[2];\n const cx = m[0] * x + m[4] * y + m[8] * z + m[12];\n const cy = m[1] * x + m[5] * y + m[9] * z + m[13];\n const cz = m[2] * x + m[6] * y + m[10] * z + m[14];\n const cw = m[3] * x + m[7] * y + m[11] * z + m[15];\n if (!(cw > 1e-6)) return { x: 0, y: 0, depth: Infinity, visible: false };\n const ndcX = cx / cw, ndcY = cy / cw, ndcZ = cz / cw;\n const inside = ndcX >= -1.05 && ndcX <= 1.05 && ndcY >= -1.05 && ndcY <= 1.05 && ndcZ <= 1;\n return {\n x: (ndcX * 0.5 + 0.5) * width,\n y: (1 - (ndcY * 0.5 + 0.5)) * height,\n depth: ndcZ,\n visible: inside,\n };\n}\n\n/**\n * Drop labels that would overlap, nearest kept.\n *\n * Without this a dense venue paints its section names on top of each other and\n * every one of them becomes unreadable — worse than showing fewer. Nearest-wins\n * because the label a buyer is closest to is the one they are asking about.\n *\n * The test is RECTANGULAR, not radial. A label is a line of text: wide and\n * short. A single radius big enough to stop two names colliding side by side is\n * far bigger than the vertical gap they actually need, so a radial test threw\n * away labels that were stacked but perfectly readable — on the amphitheatre it\n * dropped \"Terrace\" purely for sitting between the other two concentric zones.\n */\nexport function cullOverlapping<T extends { screen: Projected }>(\n items: T[],\n separationX: number,\n separationY: number = separationX,\n): T[] {\n const kept: T[] = [];\n const ordered = [...items].sort((a, b) => a.screen.depth - b.screen.depth);\n for (const item of ordered) {\n let clash = false;\n for (const k of kept) {\n const dx = Math.abs(item.screen.x - k.screen.x);\n const dy = Math.abs(item.screen.y - k.screen.y);\n // Only a genuine box overlap counts: near on BOTH axes.\n if (dx < separationX && dy < separationY) { clash = true; break; }\n }\n if (!clash) kept.push(item);\n }\n return kept;\n}\n\n/**\n * How many of the dense per-place labels may be painted at once.\n *\n * The overlap cull above is necessary and NOT sufficient, and the gap between\n * those two is a real defect: it only removes labels that collide, so the closer\n * the camera gets the FEWER collisions there are and the MORE labels survive.\n * Flown to a seat in a 3,605-seat amphitheatre, every row and seat name in the\n * frustum passed the test and the frame became a wall of tiled text with the\n * seating unreadable behind it — the labels stopped describing the venue and\n * became the thing obscuring it.\n *\n * A budget is the missing rung. A buyer arriving at their seat wants the handful\n * of names around them, not an index of the building; past a dozen or so the set\n * has no informational value left, only cost.\n */\nexport const DENSE_LABEL_BUDGET: Record<'row' | 'seat', number> = { row: 8, seat: 14 };\n\n/**\n * Order dense labels by how much the buyer is likely to care: near the camera\n * and near the middle of the frame first.\n *\n * Both terms are needed. Distance alone keeps labels for seats behind the\n * camera's shoulder that happen to be metres away; screen-centre alone keeps a\n * label for a seat on the far side of the venue purely for being dead ahead. The\n * product ranks \"what I am looking at, close to me\" top, which is the seat a\n * buyer just flew to.\n */\nexport function focusScore(\n screen: Projected,\n worldDistance: number,\n width: number,\n height: number,\n): number {\n const half = Math.max(1, Math.min(width, height) * 0.5);\n const off = Math.min(2, Math.hypot(screen.x - width / 2, screen.y - height / 2) / half);\n return worldDistance * (1 + 1.5 * off);\n}\n\n/**\n * Pick the dense labels to paint: rank by focus, drop overlaps in that order,\n * then take at most `budget`.\n *\n * Order matters. Culling overlaps FIRST and then truncating would let an\n * arbitrary far-corner label win a slot over the seat the buyer is sitting in,\n * because the overlap pass keeps whatever it happens to reach first.\n */\nexport function pickDenseLabels<T extends { screen: Projected; focus: number; priority?: number }>(\n items: T[],\n separationX: number,\n separationY: number,\n budget: number,\n): T[] {\n // A selected seat is an identity, not decorative venue copy. Give explicit\n // priorities first refusal before proximity ranking so a neighbouring row's\n // label can never make the highlighted chair appear to have another ID.\n const ordered = [...items].sort((a, b) => (\n (b.priority ?? 0) - (a.priority ?? 0) || a.focus - b.focus\n ));\n const kept: T[] = [];\n for (const item of ordered) {\n if (kept.length >= budget) break;\n let clash = false;\n for (const k of kept) {\n if (Math.abs(item.screen.x - k.screen.x) < separationX\n && Math.abs(item.screen.y - k.screen.y) < separationY) { clash = true; break; }\n }\n if (!clash) kept.push(item);\n }\n return kept;\n}\n\n/** Mean of a point set, or null when empty. */\nexport function centroidOf(points: readonly Point[]): Point | null {\n if (!points.length) return null;\n let x = 0, y = 0;\n for (const p of points) { x += p.x; y += p.y; }\n return { x: x / points.length, y: y / points.length };\n}\n","/**\n * The JS-side source of truth for the 3D scene — a pure, GPU-free description\n * built once from the chart's existing height contract. Everything the renderer\n * uploads (merged solid geometry, the instanced seat cloud, camera-framing\n * bounds, the seat-state colour LUT) is derived here, so it survives a WebGL\n * context loss: on `webglcontextrestored` the renderer simply re-uploads from\n * this model without recomputing anything.\n *\n * Feeds 100% from `sectionGeometry` / `Floor.baseHeightM` / `ExpandedSeat`\n * (docs/3d-program-workorder §Architecture) — no new chart data is invented.\n */\n\nimport type { ChartDoc, ChartObject, ExpandedSeat, Point, SectionObject, ShapeObject } from '../../core/types';\nimport { CHART_UNITS_PER_METRE } from '../../core/units';\nimport { SEAT_STATES, STRUCTURE, hexToRgb, mix, desaturate, scaleRgb, type RGB } from '../palette';\nimport { resolveTheme3D, themeSeatColorLUT, type Theme3D } from '../theme';\nimport { centroidOf, type SceneLabel } from '../labels';\nimport {\n MeshBuilder, extrudePrism, mergeMeshData, ellipsePolygon, rectPolygon, outsetRing, M, type MeshData,\n} from './geometry';\nimport polygonClipping from 'polygon-clipping';\nimport { buildSeatInstances, SEAT_DOT_RADIUS_M, type SeatInstanceData } from './seatInstances';\nimport { computeSeatYaw } from './seatChair';\nimport { buildVenueSurfaces, CAP_MAX_ERROR_M, type SectionSurface } from './surface';\n\n/** Axis-aligned stage footprint in world metres. */\nexport interface StageBounds {\n cx: number;\n cz: number;\n y: number;\n halfX: number;\n halfZ: number;\n}\nimport { emitDeckBands, deckFootprints, rowNeighbourhoods } from './deckBands';\n\n/** One resolved plane of geometry — a single-floor chart is one of these. */\ninterface FloorUnit {\n objects: ChartObject[];\n focal: Point;\n baseHeightM: number;\n}\n\n/**\n * A navigable zone — the venue's own top-level grouping (Orchestra, Lower Bowl,\n * Hall A), resolved for camera framing and for the section/zone LOD rung.\n *\n * Every shipped chart authors zones and gives every section one, and the 2D\n * renderer uses them as its farthest LOD rung. 3D ignored them entirely, so the\n * one structure a buyer navigates by (\"take me to the Grand Circle\") had no\n * representation at all in the 3D view.\n */\nexport interface SceneZone {\n id: string;\n label: string;\n /** Authored zone colour, or null when the chart leaves it to the category mix. */\n color: RGB | null;\n /** Section object ids belonging to this zone. */\n sectionIds: string[];\n /** Seats resolved into this zone. */\n seatCount: number;\n /** World-metre centre of the zone's seats (camera target). */\n center: [number, number, number];\n /** Half-diagonal of its footprint, world metres (camera fit). */\n radius: number;\n /** What this zone faces — its authored focal, else the venue's. */\n focalWorld: [number, number, number];\n}\n\n/**\n * One seated section, resolved for navigation — the middle rung of the venue\n * ladder, between a zone and a seat. A chart with no zones authored still has\n * sections, so this is the level at which every venue can be navigated.\n */\nexport interface SceneSection {\n id: string;\n /** Buyer-facing name, matching the section label. */\n label: string;\n seatCount: number;\n /** World-metre centre of the section's seats (camera target). */\n center: [number, number, number];\n /** Half-diagonal of its seat footprint, world metres (camera fit). */\n radius: number;\n /** What this section faces — the camera approaches from this side. */\n focalWorld: [number, number, number];\n}\n\n/** One authored row resolved for guided large-venue navigation. */\nexport interface SceneRow {\n id: string;\n label: string;\n sectionId?: string;\n seatCount: number;\n center: [number, number, number];\n radius: number;\n}\n\n/** A navigable floor — a logical level of the venue. */\nexport interface SceneFloor {\n index: number;\n id: string;\n label: string;\n seatCount: number;\n center: [number, number, number];\n radius: number;\n}\n\nexport interface SceneModel {\n /** Every non-seat surface merged into one triangle soup (1 draw call). */\n solids: MeshData;\n seats: SeatInstanceData;\n bounds: {\n /** World-metre venue centre (camera target). */\n center: [number, number, number];\n /** Half-diagonal of the horizontal footprint, metres (camera fit). */\n radius: number;\n groundY: number;\n /**\n * Axis-aligned venue extents (horizontal from the chart footprint,\n * vertical from the rendered geometry). The overview camera fits this\n * projected box rather than the radius sphere — a flat wide floor's\n * sphere over-estimates wildly and framed such venues tiny (see\n * OrbitCamera.fitDistance).\n */\n box: { min: [number, number, number]; max: [number, number, number] };\n };\n /** Exact enclosing sphere for rendered solids plus near-field seat detail. */\n renderBounds: {\n center: [number, number, number];\n radius: number;\n };\n /** 5 × vec3 flat LUT for the seat fragment shader. */\n stateColorLUT: number[];\n /** Resolved colours for this chart's authored theme (background, seat scale). */\n theme: Theme3D;\n seatCount: number;\n /** Venue focal point in world metres (cinematic look-at target). */\n focalWorld: [number, number, number];\n /** The venue's zones, in authored order. Empty when the chart has none. */\n zones: SceneZone[];\n /**\n * Stage footprints in world metres.\n *\n * Collected for stage lighting, which is not shipped: additive cones were\n * built, placed correctly and still could not be read from a seat (see\n * `git log` for `stageLight.ts`). The bounds stay because they are cheap,\n * correct, and exactly what a second attempt needs — the geometry was never\n * the hard part, the technique was.\n */\n stages: StageBounds[];\n /** Seated sections, in authored order — the navigable middle rung. */\n sections: SceneSection[];\n /** Rows used by the Section → Row → Seat locator. */\n rows: SceneRow[];\n /**\n * The venue's floors, in authored order. A single-floor chart reports one.\n *\n * Every shipped multi-floor template puts all its floors at baseHeightM 0 and\n * takes height from the sections instead, so floors are a logical grouping,\n * not a physical stack. What they need is ISOLATION: draw all three of an\n * opera house at once and the balcony sits over the parterre.\n */\n floors: SceneFloor[];\n /**\n * Everything worth naming, with a world anchor: zones, sections, authored\n * wayfinding text, and booths. Rendered as a DOM overlay — see `labels.ts` for\n * why text is not geometry here.\n */\n labels: SceneLabel[];\n}\n\nexport function shapeVisibleInConfiguration(\n shape: Pick<ShapeObject, 'eventConfigurationIds'>,\n activeConfigurationId?: string,\n): boolean {\n const ids = shape.eventConfigurationIds;\n return !ids?.length || (!!activeConfigurationId && ids.includes(activeConfigurationId));\n}\n\nfunction visibleObjects(objects: ChartObject[], activeConfigurationId?: string): ChartObject[] {\n return objects.filter((object) => (\n object.type !== 'shape' || shapeVisibleInConfiguration(object, activeConfigurationId)\n ));\n}\n\nfunction floorUnits(doc: ChartDoc, activeConfigurationId?: string): FloorUnit[] {\n if (doc.floors?.length) {\n return doc.floors.map((f) => ({\n objects: visibleObjects(f.objects, activeConfigurationId),\n focal: f.focalPoint ?? doc.focalPoint,\n baseHeightM: f.baseHeightM ?? 0,\n }));\n }\n return [{ objects: visibleObjects(doc.objects, activeConfigurationId), focal: doc.focalPoint, baseHeightM: 0 }];\n}\n\nconst AO = { top: 1.0, wallBottom: 0.5, bottomCap: 0.4 };\n\n// The cap's tessellation tolerance lives beside the surface it approximates\n// (surface.ts), together with the seat clearance derived from it.\n\n/**\n * Fold a surface's 2D fill colour into the dark structure palette: desaturate\n * ~40 %, darken, then ground it in the neutral structure grey so a tier top\n * reads architectural — a recognisable hue (purple/green/orange) but muted, not\n * candy-coloured paint. Risers/walls stay neutral concrete; baked AO still\n * multiplies these per vertex downstream. `null` fill ⇒ the neutral grey.\n */\nfunction tintTop(fill: RGB | null, neutral: RGB): RGB {\n if (!fill) return neutral;\n const muted = scaleRgb(desaturate(fill, 0.4), 0.62);\n return mix(neutral, muted, 0.72);\n}\n\n/**\n * Resolve each section's 2D paint colour, keyed by logical section id\n * (`logicalSectionId ?? id`, matching how expanded seats attribute `sectionId`):\n * the count-weighted mix of its member seats' category colours — the same source\n * the 2D renderer blends into a section's block fill. An explicit `section.color`\n * override is applied later (it wins in `sectionFill`).\n */\nfunction resolveSectionFills(doc: ChartDoc, seats: ExpandedSeat[]): Map<string, RGB> {\n const catColor = new Map<string, string>();\n for (const c of doc.categories ?? []) catColor.set(c.key, c.color);\n const counts = new Map<string, Map<string, number>>();\n for (const s of seats) {\n if (!s.sectionId) continue;\n let m = counts.get(s.sectionId);\n if (!m) { m = new Map(); counts.set(s.sectionId, m); }\n m.set(s.categoryKey, (m.get(s.categoryKey) ?? 0) + 1);\n }\n const out = new Map<string, RGB>();\n for (const [sid, byCat] of counts) {\n let r = 0, g = 0, b = 0, w = 0;\n for (const [key, n] of byCat) {\n const rgb = hexToRgb(catColor.get(key));\n if (!rgb) continue;\n r += rgb[0] * n; g += rgb[1] * n; b += rgb[2] * n; w += n;\n }\n if (w > 0) out.set(sid, [r / w, g / w, b / w]);\n }\n return out;\n}\n\n/** A section's fill: explicit `color` override wins, else the member-category mix. */\nfunction sectionFill(section: SectionObject, byLogical: Map<string, RGB>): RGB | null {\n return hexToRgb(section.color) ?? byLogical.get(section.logicalSectionId ?? section.id) ?? null;\n}\n\n/**\n * Extrude one section into the shared builder, capped by ITS OWN seating surface.\n *\n * The height function is not recomputed here — it comes from `surface.ts`, the\n * same object the seat dots stand on. That is the whole point: there is one\n * definition of the seating surface, and the cap, the walls and the seats all\n * read it, so no cross-file offset constant has to be kept in agreement.\n */\ninterface RingBox { minX: number; minY: number; maxX: number; maxY: number }\n\nfunction bboxOfRing(ring: readonly Point[]): RingBox {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const p of ring) {\n if (p.x < minX) minX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.x > maxX) maxX = p.x;\n if (p.y > maxY) maxY = p.y;\n }\n return { minX, minY, maxX, maxY };\n}\n\nfunction boxesOverlap(a: RingBox, b: RingBox): boolean {\n return a.minX <= b.maxX && b.minX <= a.maxX && a.minY <= b.maxY && b.minY <= a.maxY;\n}\n\n/**\n * A section's padded outline with every OTHER section's authored outline removed.\n *\n * Deck padding grows a section by 0.33 m so a seat authored hard against its\n * boundary still rests on deck. Where two sections abut, that growth crosses into\n * the neighbour — and if the neighbour is lower, the overhang covers its seats.\n * Measured on the concert hall: 27 stalls-terrace seats buried by 0.20 m beneath\n * the terrace beside them, which sits 0.60 m higher.\n *\n * Clipping to the section's own padded ring is not enough, because the padding IS\n * the intrusion. The rule that works is: grow to hold your own seats, but never\n * over another section's floor.\n */\nfunction paddedFootprint(section: SectionObject, siblings: readonly SectionObject[]): Point[][] {\n const padded = outsetRing(section.outline, SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE);\n // Only siblings whose bounding box actually overlaps can remove any area, and\n // a polygon boolean is expensive. Differencing against EVERY sibling made\n // scene build quadratic in section count: measured 2.7 s at 50k seats / 40\n // sections and 11.7 s at 99k / 60, where doubling the venue cost 4.3x the\n // time. A venue's sections are laid out around it, so almost no pair overlaps\n // and this prunes nearly all of the work.\n const box = bboxOfRing(padded);\n const others = siblings.filter((o) => o !== section\n && o.outline && o.outline.length >= 3\n && boxesOverlap(box, bboxOfRing(o.outline)));\n if (!others.length) return [padded];\n const toRing = (pts: readonly Point[]): [number, number][] => {\n const r = pts.map((p) => [p.x, p.y] as [number, number]);\n r.push(r[0]);\n return r;\n };\n try {\n const diff = polygonClipping.difference([toRing(padded)], ...others.map((o) => [toRing(o.outline)]));\n const out: Point[][] = [];\n for (const poly of diff) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push(pts);\n }\n return out.length ? out : [padded];\n } catch {\n return [padded];\n }\n}\n\n/** Intersect each block footprint with the section's own allowed area. */\nfunction clipFootprints(\n blocks: ReturnType<typeof deckFootprints>,\n allowed: Point[][],\n): ReturnType<typeof deckFootprints> {\n if (!allowed.length) return blocks;\n const toRing = (pts: readonly Point[]): [number, number][] => {\n const r = pts.map((p) => [p.x, p.y] as [number, number]);\n r.push(r[0]);\n return r;\n };\n const allowedPolys = allowed.map((a) => [toRing(a)]);\n const out: ReturnType<typeof deckFootprints> = [];\n for (const b of blocks) {\n // NOTE: a \"skip the clip when the block looks contained\" fast path was tried\n // here and reverted. Testing that the block's VERTICES lie inside the allowed\n // ring is not sound for a CONCAVE allowed region — and a section footprint is\n // routinely concave, since an arena wedge curves inward and siblings are\n // subtracted from it. An edge can bulge outside while every vertex is inside,\n // which buried seats on the arena. A sound test needs edge-intersection\n // checks, which is most of the cost of the boolean it would be avoiding.\n let pieces;\n try {\n pieces = polygonClipping.intersection([toRing(b.outline)], ...[allowedPolys.flat()]);\n } catch {\n out.push(b);\n continue;\n }\n for (const poly of pieces) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push({ outline: pts, holes: [], topY: b.topY });\n }\n }\n return out.length ? out : blocks;\n}\n\nfunction buildTier(\n builder: MeshBuilder,\n section: SectionObject,\n unit: FloorUnit,\n fill: RGB | null,\n surface: SectionSurface | undefined,\n claimed: ClaimedArea,\n siblings: readonly SectionObject[],\n S: typeof STRUCTURE,\n): void {\n if (!section.outline || section.outline.length < 3) return;\n const bottomY = unit.baseHeightM;\n const colTop = tintTop(fill, S.tierTop);\n if (!surface) return;\n const topY = (p: Point): number => surface.deckAt(p.x, p.y);\n // Pad the deck so a seat authored hard against the section boundary rests ON\n // its own section instead of overhanging into the aisle. Seats are chart data\n // and never move; the deck grows to meet them. 1.5x the dot footprint: enough\n // that a dot sitting exactly on the boundary still lands clear of the edge,\n // and small against real aisle widths.\n const footprint = paddedFootprint(section, siblings);\n const outline = footprint[0] ?? section.outline;\n\n // A section whose rows resolved into levels is drawn as GEOMETRY, not as a\n // tessellated field: a flat landing prism carrying one level ribbon per row,\n // with a riser between consecutive ribbons. See `deckBands.ts` for why a\n // refined cap cannot work once every row has to be level.\n if (surface.rowLevels.length >= 2) {\n // One base prism per BLOCK, hugging the ribbons above it, instead of one flat\n // plate over the section's whole authored outline. The outline reaches well\n // past the seating, so a single plate stuck out around the stands as a slab\n // and cut every block's end square through it. Each block also stands on its\n // OWN lowest row rather than the section's.\n // Clipped to the section's own footprint like the treads are: a block base is\n // built from a ribbon union carrying its own outward margin, so it overhangs\n // for the same reason and buries a lower neighbour's seats the same way.\n // One neighbourhood solve, shared by the footprint and the emission below.\n const nbrs = rowNeighbourhoods(surface.rowLevels);\n const blocks = clipFootprints(deckFootprints(surface.rowLevels, unit.focal, nbrs), footprint);\n // Explicit UP normals rather than face normals. A block footprint is a\n // boolean union of ~100 overlapping ribbons, and such a union always leaves\n // some very thin triangles along its seams; deriving a normal from one by\n // cross product is numerically meaningless and shades as a dark hairline.\n // The top is FLAT, so the correct normal is known outright and the thin\n // triangles become harmless.\n const capUp = (): [number, number, number] => [0, 1, 0];\n for (const b of blocks) {\n // The base cap occupies the same footprint as the first row's tread. If\n // both sit at `b.topY`, thousands of coplanar triangles compete in the\n // depth buffer and turn into a black radial comb as the camera zooms out.\n // Recess only the structural cap by 2 cm; seats and row treads remain at\n // their exact authored levels, and the small exposed nosing reads as a\n // normal construction joint rather than a floating deck.\n const baseCapY = Math.max(bottomY, b.topY - 0.02);\n // Neighbouring section padding can still make two block bases overlap in\n // a narrow strip. At the same level that is genuine z-fighting, so give\n // each coplanar block exclusive ownership just like the flat-cap path.\n for (const ring of claimed.subtract(b.outline, baseCapY)) {\n extrudePrism(builder, ring, b.holes, () => baseCapY, bottomY,\n colTop, S.tierWall, AO, undefined, capUp);\n }\n }\n // Nothing resolved (a degenerate row set) — fall back to the outline so the\n // section is never simply missing.\n if (!blocks.length) {\n extrudePrism(builder, outline, section.holes, () => surface.landingY, bottomY,\n colTop, S.tierWall, AO);\n }\n const bandColors = {\n tread: [colTop[0] * AO.top, colTop[1] * AO.top, colTop[2] * AO.top] as RGB,\n // Risers read as the structure they are, a shade below their tread, which\n // is what makes the stepping legible from a low angle.\n riser: [colTop[0] * 0.72, colTop[1] * 0.72, colTop[2] * 0.72] as RGB,\n };\n // `paddedFootprint` may return several disjoint polygons after neighbouring\n // sections are subtracted. Flattening those rings into one point list draws\n // synthetic connector edges between them; polygon clipping then cuts long\n // triangular wedges through an arena deck. Clip the same ribbons once per\n // real component instead. The components are disjoint, so their emitted\n // treads cannot overlap or z-fight.\n for (const allowed of footprint.length ? footprint : [outline]) {\n emitDeckBands(builder, surface.rowLevels, unit.focal, surface.landingY,\n bandColors, allowed, nbrs);\n }\n return;\n }\n\n // A flat section's cap is exact at any tessellation, so it is left\n // unsubdivided (Infinity) and stays identical to the pre-surface mesh —\n // legacy height-less charts must not shift by a single vertex.\n const maxErr = surface.flat ? Infinity : CAP_MAX_ERROR_M;\n const topN = (p: Point): [number, number, number] => surface.normalAt(p.x, p.y);\n\n // Take only the ground this section has not already lost to an earlier one.\n //\n // Every flat section's top sits at exactly the same height, and the deck\n // padding above grows each one by 0.33 m, so neighbouring sections OVERLAP.\n // Two coplanar surfaces at identical depth is a z-fight, and it renders as a\n // serrated comb of interpenetrating teeth along every shared boundary — the\n // artifact visible on the flat chart. Depth-biasing them apart would hide it\n // rather than fix it, and would break down as soon as a chart has many\n // sections. Removing the overlap means there is nothing to fight over.\n // A flat section's deck is one constant height, so it can be tested for\n // coplanarity; a raked fallback surface has no single height to offer.\n const level = surface.flat ? surface.landingY : undefined;\n for (const ring of claimed.subtract(outline, level)) {\n extrudePrism(builder, ring, section.holes, topY, bottomY, colTop, S.tierWall, AO, maxErr, topN);\n }\n}\n\n/**\n * The ground already taken by sections drawn so far, so a later section can be\n * clipped to what is left. Only meaningful between COPLANAR surfaces, which is\n * why raked sections (each at its own height) never reach this.\n */\n/** Two claims fight for the same ground only if both are level and level TOGETHER.\n * A claim with no single height (an unresolved raked surface) is treated as\n * comparable with everything, which is what it did before heights existed. */\nfunction coplanarLevels(a: number | undefined, b: number | undefined): boolean {\n if (a === undefined || b === undefined) return true;\n return Math.abs(a - b) < 1e-3;\n}\n\nclass ClaimedArea {\n private rings: Array<[number, number][][]> = [];\n private boxes: RingBox[] = [];\n /** Constant deck height of each claim, or undefined when it is not level. */\n private levels: Array<number | undefined> = [];\n\n /**\n * `ring` minus everything claimed so far AT THE SAME HEIGHT; then claim it.\n *\n * `level` is the claim's constant deck height when it has one. Two decks only\n * z-fight when they are coplanar, so only a coplanar claim may take ground\n * away — an elevated box hanging over a ground-level section overlaps it in\n * plan and must still draw its whole floor, or the box loses the part of its\n * deck that shares a footprint with whatever is underneath it. Passing\n * undefined (a surface with no single height) keeps the original\n * clip-against-everything behaviour.\n */\n subtract(ring: Point[], level?: number): Point[][] {\n const closed: [number, number][] = ring.map((p) => [p.x, p.y]);\n if (closed.length < 3) return [];\n closed.push(closed[0]);\n const box = bboxOfRing(ring);\n // Same prune as `paddedFootprint`: a claim that cannot overlap cannot remove\n // anything, and without this the boolean work is quadratic in section count.\n const overlapping = this.rings.filter((_, i) => boxesOverlap(box, this.boxes[i])\n && coplanarLevels(level, this.levels[i]));\n let pieces: Array<[number, number][][]> = [[closed]];\n if (overlapping.length) {\n try {\n const diff = polygonClipping.difference([closed], ...overlapping);\n pieces = diff.map((poly) => poly.map((r) => r.map(([x, y]) => [x, y] as [number, number])));\n } catch {\n pieces = [[closed]]; // a degenerate outline must not drop the section\n }\n }\n this.rings.push([closed]);\n this.boxes.push(box);\n this.levels.push(level);\n const out: Point[][] = [];\n for (const poly of pieces) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push(pts);\n }\n return out;\n }\n}\n\n/**\n * Height of a booth stand, world metres — a partition wall, not a table.\n *\n * Trade-show and exhibition charts are entirely booths (267 across the two\n * shipped templates), and before this they drew nothing at all: the whole venue\n * rendered as empty floor. A booth is a sellable unit the buyer picks, so it has\n * to be a solid the camera can approach and the pointer can hit.\n */\nconst BOOTH_HEIGHT_M = 2.4;\n\n/** How far a label floats above the thing it names, world metres. */\nconst ZONE_LABEL_LIFT_M = 6;\nconst SECTION_LABEL_LIFT_M = 2.2;\nconst ANNOTATION_LIFT_M = 0.1;\nconst BOOTH_LABEL_LIFT_M = 0.3;\n/** A GA area is flat, so its name sits at head height over the standing floor. */\nconst GA_LABEL_LIFT_M = 1.8;\n/** Row and seat labels ride just above their own deck, like painted markings. */\nconst ROW_LABEL_LIFT_M = 0.9;\nconst SEAT_LABEL_LIFT_M = 0.55;\n/**\n * Seat-label ceiling. Above this the whole set is not emitted — see the note at\n * the emit site. 6,000 covers theatres, halls, cinemas and mid-size arenas; the\n * charts above it are exactly the ones where a per-seat label is least readable.\n */\nconst SEAT_LABEL_MAX = 6_000;\n\n/** Height of a banquet table top, world metres (standard dining height). */\nconst TABLE_HEIGHT_M = 0.75;\n\n/**\n * Height of a décor-image floor plate — a marking on the ground, not an object.\n *\n * Matched to the GA slab (0.15 m) rather than made as thin as it \"should\" be: a\n * 4 cm plate under a 76 m rink is a depth ratio of 5e-4 and Uber Arena's ice\n * z-fought the ground slab into horizontal stripes across the whole surface.\n */\nconst DECOR_PLATE_M = 0.15;\n\n/** Resolve a booth to its closed chart-unit polygon, honouring a custom outline. */\nfunction boothPolygon(booth: Extract<ChartObject, { type: 'booth' }>): Point[] | null {\n // A custom outline wins and ignores rotation, exactly as the 2D renderer does\n // (see BoothObject.points) — L-shaped and island units on an expo floor.\n if (booth.points && booth.points.length >= 3) return booth.points;\n const { center, width, height, rotation } = booth;\n if (!width || !height) return null;\n const a = ((rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: center.x + lx * cos - ly * sin,\n y: center.y + lx * sin + ly * cos,\n }));\n}\n\nfunction buildBooth(\n builder: MeshBuilder,\n booth: Extract<ChartObject, { type: 'booth' }>,\n base: number,\n fill: RGB | null,\n S: typeof STRUCTURE,\n): void {\n const poly = boothPolygon(booth);\n if (!poly) return;\n extrudePrism(builder, poly, undefined, () => base + BOOTH_HEIGHT_M, base,\n tintTop(fill, S.boothTop), S.boothWall, AO);\n}\n\n/** Resolve a table to its closed chart-unit polygon. */\nfunction tablePolygon(table: Extract<ChartObject, { type: 'table' }>): Point[] | null {\n if (table.shape === 'round') {\n const r = table.radius;\n if (!r) return null;\n return ellipsePolygon(table.center.x, table.center.y, r, r, 24);\n }\n const { width, height, rotation, center } = table;\n if (!width || !height) return null;\n const a = ((rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: center.x + lx * cos - ly * sin,\n y: center.y + lx * sin + ly * cos,\n }));\n}\n\nfunction buildTable(\n builder: MeshBuilder,\n table: Extract<ChartObject, { type: 'table' }>,\n base: number,\n fill: RGB | null,\n S: typeof STRUCTURE,\n): void {\n const poly = tablePolygon(table);\n if (!poly) return;\n // Banquet and club charts (77 tables across the shipped templates) drew their\n // chairs floating around nothing. The table is what makes the arrangement read\n // as a table rather than a ring of stray seats.\n extrudePrism(builder, poly, undefined, () => base + TABLE_HEIGHT_M, base,\n tintTop(fill, S.tableTop), S.tableWall, AO);\n}\n\nfunction rotateShapePolygon(poly: Point[], degrees: number | undefined): Point[] {\n if (!degrees) return poly;\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (const point of poly) {\n minX = Math.min(minX, point.x); maxX = Math.max(maxX, point.x);\n minY = Math.min(minY, point.y); maxY = Math.max(maxY, point.y);\n }\n const cx = (minX + maxX) / 2, cy = (minY + maxY) / 2;\n const radians = (degrees * Math.PI) / 180;\n const cos = Math.cos(radians), sin = Math.sin(radians);\n return poly.map((point) => {\n const dx = point.x - cx, dy = point.y - cy;\n return { x: cx + dx * cos - dy * sin, y: cy + dx * sin + dy * cos };\n });\n}\n\n/** Resolve a shape object to its rotated closed chart-unit polygon. */\nfunction shapePolygon(shape: Extract<ChartObject, { type: 'shape' }>): Point[] | null {\n let poly: Point[] | null = null;\n if (shape.kind === 'polygon' && shape.points && shape.points.length >= 3) poly = shape.points;\n if (shape.kind === 'rect' && shape.width && shape.height) {\n poly = rectPolygon(shape.x ?? 0, shape.y ?? 0, shape.width, shape.height);\n }\n if (shape.kind === 'ellipse' && shape.width && shape.height) {\n const cx = (shape.x ?? 0) + shape.width / 2;\n const cy = (shape.y ?? 0) + shape.height / 2;\n poly = ellipsePolygon(cx, cy, shape.width / 2, shape.height / 2);\n }\n return poly ? rotateShapePolygon(poly, shape.rotation) : null; // line / polyline are stroke-only\n}\n\nexport type ShapeArchitectureKind = 'plate' | 'stage' | 'solid' | 'portal' | 'suite';\nexport interface ShapeArchitecture {\n kind: ShapeArchitectureKind;\n heightM: number;\n}\n\n/** Physical interpretation of the chart's existing décor vocabulary. */\nexport function architectureForShape(shape: Pick<ShapeObject, 'role' | 'heightM'>): ShapeArchitecture {\n const role = shape.role ?? '';\n const defaults: Record<string, ShapeArchitecture> = {\n stage: { kind: 'stage', heightM: 1 },\n entrance: { kind: 'portal', heightM: 2.7 },\n exit: { kind: 'portal', heightM: 2.7 },\n screen: { kind: 'solid', heightM: 6 },\n wall: { kind: 'solid', heightM: 2.8 },\n rail: { kind: 'solid', heightM: 1.1 },\n suite: { kind: 'suite', heightM: 3 },\n obstruction: { kind: 'solid', heightM: 4.5 },\n sound: { kind: 'solid', heightM: 1.45 },\n restroom: { kind: 'solid', heightM: 2.4 },\n bar: { kind: 'solid', heightM: 1.1 },\n concession: { kind: 'solid', heightM: 1.1 },\n coat: { kind: 'solid', heightM: 1.1 },\n };\n const resolved = defaults[role] ?? { kind: 'plate' as const, heightM: 0.25 };\n return { ...resolved, heightM: shape.heightM ?? resolved.heightM };\n}\n\ninterface ShapeLayerFootprint {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n topOffsetM: number;\n}\n\n/**\n * Flat authored shapes share one solid draw call, so polygonOffset cannot\n * separate a marking from the surface below it. Preserve the 2D object order by\n * lifting only shapes whose footprints overlap something already emitted.\n * Eight centimetres remains visually flush at venue scale while clearing the\n * depth precision that made Cricket Oval's wicket strip flicker into slivers.\n */\nconst SHAPE_LAYER_LIFT_M = 0.08;\n\nfunction claimShapeTopOffset(\n shape: Extract<ChartObject, { type: 'shape' }>,\n claimed: ShapeLayerFootprint[],\n): number {\n const poly = shapePolygon(shape);\n const architecture = architectureForShape(shape);\n const defaultTop = architecture.heightM;\n if (!poly) return defaultTop;\n // Vertical architecture occupies real space; it does not participate in the\n // tiny z-fight lifts used to order overlapping painted floor plates.\n if (architecture.kind !== 'plate' && architecture.kind !== 'stage') return defaultTop;\n\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const p of poly) {\n minX = Math.min(minX, p.x); minY = Math.min(minY, p.y);\n maxX = Math.max(maxX, p.x); maxY = Math.max(maxY, p.y);\n }\n let topOffsetM = defaultTop;\n for (const prior of claimed) {\n const overlaps = maxX > prior.minX && minX < prior.maxX\n && maxY > prior.minY && minY < prior.maxY;\n if (overlaps && topOffsetM <= prior.topOffsetM + 1e-6) {\n topOffsetM = prior.topOffsetM + SHAPE_LAYER_LIFT_M;\n }\n }\n claimed.push({ minX, minY, maxX, maxY, topOffsetM });\n return topOffsetM;\n}\n\nfunction lerpPoint(a: Point, b: Point, t: number): Point {\n return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };\n}\n\nfunction orientQuadLong(poly: Point[]): Point[] {\n if (poly.length !== 4) return poly;\n const edge0 = Math.hypot(poly[1].x - poly[0].x, poly[1].y - poly[0].y);\n const edge1 = Math.hypot(poly[2].x - poly[1].x, poly[2].y - poly[1].y);\n return edge0 >= edge1 ? poly : [poly[1], poly[2], poly[3], poly[0]];\n}\n\nfunction quadWidthSlice(poly: Point[], t0: number, t1: number): Point[] {\n return [\n lerpPoint(poly[0], poly[1], t0), lerpPoint(poly[0], poly[1], t1),\n lerpPoint(poly[3], poly[2], t1), lerpPoint(poly[3], poly[2], t0),\n ];\n}\n\nfunction quadDepthSlice(poly: Point[], t0: number, t1: number): Point[] {\n return [\n lerpPoint(poly[0], poly[3], t0), lerpPoint(poly[1], poly[2], t0),\n lerpPoint(poly[1], poly[2], t1), lerpPoint(poly[0], poly[3], t1),\n ];\n}\n\nfunction emitArchitecturalPrism(\n builder: MeshBuilder,\n poly: Point[],\n top: number,\n bottom: number,\n colTop: RGB,\n colWall: RGB,\n): void {\n extrudePrism(builder, poly, undefined, () => top, bottom, colTop, colWall, AO);\n}\n\nfunction buildShape(\n builder: MeshBuilder,\n shape: Extract<ChartObject, { type: 'shape' }>,\n base: number,\n topOffsetM: number,\n S: typeof STRUCTURE,\n focal: Point,\n /** Collects the footprint of each STAGE, for the lighting rig above it. */\n stages?: StageBounds[],\n): void {\n const poly = shapePolygon(shape);\n if (!poly) return;\n const isStage = shape.role === 'stage';\n const architecture = architectureForShape(shape);\n const height = base + topOffsetM;\n if (isStage && stages) {\n let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity;\n for (const pt of poly) {\n if (pt.x < minX) minX = pt.x;\n if (pt.x > maxX) maxX = pt.x;\n if (pt.y < minZ) minZ = pt.y;\n if (pt.y > maxZ) maxZ = pt.y;\n }\n // Chart units -> world metres, matching every other position in the scene.\n stages.push({\n cx: ((minX + maxX) / 2) * M,\n cz: ((minZ + maxZ) / 2) * M,\n y: height,\n halfX: Math.max(((maxX - minX) / 2) * M, 0.5),\n halfZ: Math.max(((maxZ - minZ) / 2) * M, 0.5),\n });\n }\n // An authored `fill` is what the 2D renderer paints this shape (see\n // `renderShape`), and 3D used to discard it for a fixed grey — so an organizer\n // who coloured their stage, dance floor or pitch saw it in the map and lost it\n // the moment they switched to 3D. Muted through `tintTop` like every other\n // authored colour, so it keeps its hue without out-shouting the seating.\n const authoredFill = hexToRgb(shape.fill);\n // An explicitly authored stage should keep the material it was given. A\n // mostly warm fixed palette made dark concert decks read like brown timber.\n // Unauthored stages retain the brighter default performance treatment.\n const colTop = isStage && authoredFill\n ? mix(S.stageTop, scaleRgb(desaturate(authoredFill, 0.18), 0.90), 0.90)\n : tintTop(authoredFill, isStage ? S.stageTop : S.decorTop);\n // A screen's useful surface is vertical, so it needs to read from its WALL\n // faces rather than its almost invisible top cap. Treat it like a powered LED\n // panel; every other structure remains inside the muted architectural palette.\n const screenFill = shape.role === 'screen' ? authoredFill : null;\n const colWall: RGB = isStage\n ? authoredFill\n ? mix(S.stageWall, scaleRgb(authoredFill, 0.58), 0.82)\n : S.stageWall\n : shape.role === 'screen'\n ? screenFill\n ? mix(scaleRgb(screenFill, 1.42), [0.24, 0.46, 0.82], 0.08)\n : [0.24, 0.46, 0.82]\n : tintTop(hexToRgb(shape.fill), S.decorWall);\n if (architecture.kind === 'portal' && poly.length === 4) {\n const quad = orientQuadLong(poly);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0, 0.16), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0.84, 1), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quad, height, Math.max(base, height - 0.35), colTop, colWall);\n return;\n }\n if (architecture.kind === 'suite' && poly.length === 4) {\n const edgeDistances = poly.map((point, index) => {\n const next = poly[(index + 1) % 4];\n const mx = (point.x + next.x) / 2, my = (point.y + next.y) / 2;\n return Math.hypot(mx - focal.x, my - focal.y);\n });\n const front = edgeDistances.indexOf(Math.min(...edgeDistances));\n const quad = [0, 1, 2, 3].map((offset) => poly[(front + offset) % 4]);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0, 0.08), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0.92, 1), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quadDepthSlice(quad, 0.88, 1), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quad, height, Math.max(base, height - 0.18), colTop, colWall);\n return;\n }\n emitArchitecturalPrism(builder, poly, height, base, colTop, colWall);\n}\n\n/**\n * Resolve a décor image to its closed chart-unit polygon.\n *\n * `DecorImageObject` is authored as a top-left anchor plus size, rotated about\n * its own CENTRE — the same convention the 2D renderer uses when it offsets the\n * Konva node by half its extent.\n */\nfunction decorPolygon(decor: Extract<ChartObject, { type: 'decorImage' }>): Point[] | null {\n const { x, y, width, height } = decor;\n if (!width || !height) return null;\n const cx = x + width / 2, cy = y + height / 2;\n const a = ((decor.rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: cx + lx * cos - ly * sin,\n y: cy + lx * sin + ly * cos,\n }));\n}\n\n/**\n * The colour a décor plate is painted, sniffed from an SVG data URL.\n *\n * The renderer is deliberately texture-free (three draw calls, no atlas), so a\n * décor image cannot be drawn as an image. What it CAN do is paint the plate in\n * the graphic's own dominant colour, which is the difference between an ice rink\n * reading as ice and reading as a grey slab.\n *\n * The heuristic is narrow on purpose: take the paint of the FIRST covering\n * element (`<rect>` or `<path>`), because an SVG floor graphic is built up\n * background-first — the rink surface, then the lines on top of it. A gradient\n * reference resolves to the mean of that gradient's stops. Anything else (a\n * raster data URL, an external href, an SVG that does not match) returns null\n * and the caller falls back to the neutral décor tone, which is what the object\n * rendered as before this existed.\n */\nfunction decorPlatePaint(href: string | undefined): RGB | null {\n if (!href || !href.startsWith('data:image/svg+xml')) return null;\n let svg = href.slice(href.indexOf(',') + 1);\n if (/;base64/i.test(href.slice(0, href.indexOf(',')))) {\n try { svg = atob(svg); } catch { return null; }\n } else {\n try { svg = decodeURIComponent(svg); } catch { /* already literal */ }\n }\n\n const covering = svg.match(/<(?:rect|path)\\b[^>]*\\bfill=\"([^\"]+)\"/i);\n if (!covering) return null;\n const paint = covering[1].trim();\n if (paint === 'none' || paint === 'transparent') return null;\n\n const gradient = paint.match(/^url\\(#([^)]+)\\)$/);\n if (!gradient) return hexToRgb(paint);\n\n // Average the referenced gradient's stops — a two-stop ice gradient is one\n // colour as far as a flat-shaded plate is concerned.\n const def = svg.match(new RegExp(`<(?:linear|radial)Gradient\\\\b[^>]*\\\\bid=\"${gradient[1]}\"[^>]*>([\\\\s\\\\S]*?)</(?:linear|radial)Gradient>`, 'i'));\n if (!def) return null;\n const stops: RGB[] = [];\n for (const m of def[1].matchAll(/stop-color=\"([^\"]+)\"/gi)) {\n const c = hexToRgb(m[1].trim());\n if (c) stops.push(c);\n }\n if (!stops.length) return null;\n return stops.reduce<RGB>((acc, c, i) => mix(acc, c, 1 / (i + 1)), stops[0]);\n}\n\nfunction buildDecorImage(\n builder: MeshBuilder,\n decor: Extract<ChartObject, { type: 'decorImage' }>,\n base: number,\n S: typeof STRUCTURE,\n): void {\n // A `foreground` décor image draws ABOVE the seats in 2D — a canopy or an\n // overlay graphic. In 3D an opaque plate over the seating would hide the very\n // thing the buyer came to pick from, so it is left undrawn rather than drawn\n // wrongly. No shipped template authors one; the designer can.\n if (decor.layer === 'foreground') return;\n const poly = decorPolygon(decor);\n if (!poly) return;\n // The plate is a floor marking, not an object: it sits a few centimetres proud\n // of the ground slab so it cannot z-fight, and well under the 0.25 m décor\n // shapes that may be standing on it.\n const top = base + DECOR_PLATE_M;\n // Muted into the structure palette exactly like a category fill on a tier cap.\n // Taken literally, the rink's #f4f9ff ice is the BRIGHTEST thing in the venue\n // and the floor out-shouts the seating — against the look brief, which puts\n // saturated colour only on seats. Through `tintTop` it keeps its blue cast and\n // sits in the same tonal range as every other surface.\n let paint = tintTop(decorPlatePaint(decor.href), S.decorTop);\n // No alpha in the solid pass, so authored opacity is honoured by mixing the\n // plate toward the ground it would otherwise be showing through to.\n const opacity = Math.max(0, Math.min(1, decor.opacity ?? 1));\n if (opacity < 1) paint = mix(S.ground, paint, opacity);\n extrudePrism(builder, poly, undefined, () => top, base, paint, mix(paint, S.decorWall, 0.5), AO);\n}\n\nfunction buildGa(builder: MeshBuilder, ga: Extract<ChartObject, { type: 'gaArea' }>, base: number, fill: RGB | null, S: typeof STRUCTURE): void {\n if (!ga.points || ga.points.length < 3) return;\n const colTop = tintTop(fill, S.gaTop);\n extrudePrism(builder, ga.points, ga.holes, () => base + 0.15, base, colTop, S.gaWall, AO);\n}\n\n/** Compute the horizontal chart-unit footprint over everything drawable. */\nfunction chartFootprint(units: FloorUnit[], seats: ExpandedSeat[]): { minX: number; minY: number; maxX: number; maxY: number } {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n const acc = (x: number, y: number): void => {\n if (x < minX) minX = x; if (y < minY) minY = y;\n if (x > maxX) maxX = x; if (y > maxY) maxY = y;\n };\n for (const s of seats) acc(s.x, s.y);\n for (const u of units) {\n for (const o of u.objects) {\n if (o.type === 'section') for (const p of o.outline) acc(p.x, p.y);\n else if (o.type === 'shape' && o.points) for (const p of o.points) acc(p.x, p.y);\n else if (o.type === 'gaArea') for (const p of o.points) acc(p.x, p.y);\n else if (o.type === 'booth') { const b = boothPolygon(o); if (b) for (const p of b) acc(p.x, p.y); }\n else if (o.type === 'table') { const t = tablePolygon(o); if (t) for (const p of t) acc(p.x, p.y); }\n else if (o.type === 'decorImage') { const d = decorPolygon(o); if (d) for (const p of d) acc(p.x, p.y); }\n }\n }\n if (!Number.isFinite(minX)) { minX = -100; minY = -100; maxX = 100; maxY = 100; }\n return { minX, minY, maxX, maxY };\n}\n\nexport interface SceneModelInput {\n doc: ChartDoc;\n seats: ExpandedSeat[];\n /** Overrides the chart's active layout without mutating its authored data. */\n eventConfigurationId?: string;\n /** Optional initial per-seat state (default all available). */\n initialState?: (seat: ExpandedSeat) => import('../palette').SeatState3D;\n}\n\nexport function buildSceneModel(input: SceneModelInput): SceneModel {\n const { doc, seats } = input;\n // Resolved once and threaded down, so no builder reaches for the module-level\n // palette and quietly ignores an organizer's branding.\n const theme = resolveTheme3D(doc.theme);\n const S = theme.structure;\n const units = floorUnits(doc, input.eventConfigurationId ?? doc.eventConfigurationId);\n const builder = new MeshBuilder();\n\n // Ground slab sized to the footprint (+ margin), sitting at datum 0.\n const fp = chartFootprint(units, seats);\n const padU = Math.max(60, (fp.maxX - fp.minX + fp.maxY - fp.minY) * 0.06);\n const groundPoly = rectPolygon(fp.minX - padU, fp.minY - padU, (fp.maxX - fp.minX) + padU * 2, (fp.maxY - fp.minY) + padU * 2);\n extrudePrism(builder, groundPoly, undefined, () => 0, -0.4, S.ground, S.ground, AO);\n\n // Per-section 2D fill colours (member-category mix), carried onto tier tops.\n const sectionFills = resolveSectionFills(doc, seats);\n const catColor = new Map<string, string>();\n for (const c of doc.categories ?? []) catColor.set(c.key, c.color);\n const stageBounds: StageBounds[] = [];\n\n // ONE seating surface per section, resolved from the same model layout.ts uses\n // for eye heights. Consumed below by the tier caps and by the seat instances.\n const surfaces = buildVenueSurfaces(units, seats);\n /** Owning floor per seat, for per-instance floor isolation. */\n const seatFloor = new Float32Array(seats.length);\n\n // Coplanar sections must not overlap (see ClaimedArea). One claim per floor:\n // sections on different floors sit at different heights and cannot z-fight.\n for (let unitIndex = 0; unitIndex < units.length; unitIndex++) {\n const unit = units[unitIndex];\n builder.setFloor(unitIndex);\n const claimed = new ClaimedArea();\n const claimedShapeLayers: ShapeLayerFootprint[] = [];\n const siblings = unit.objects.filter((o): o is SectionObject => o.type === 'section' && !!o.outline && o.outline.length >= 3);\n for (const o of unit.objects) {\n if (o.type === 'section') buildTier(builder, o, unit, sectionFill(o, sectionFills), surfaces.bySection.get(o.id), claimed, siblings, S);\n else if (o.type === 'shape') {\n builder.setMaterial(o.role === 'screen' ? 1 : 0);\n buildShape(\n builder,\n o,\n unit.baseHeightM,\n claimShapeTopOffset(o, claimedShapeLayers),\n S,\n unit.focal,\n stageBounds,\n );\n builder.setMaterial(0);\n }\n else if (o.type === 'gaArea') buildGa(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'booth') buildBooth(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'table') buildTable(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'decorImage') buildDecorImage(builder, o, unit.baseHeightM, S);\n }\n }\n\n const focal = doc.focalPoint ?? { x: (fp.minX + fp.maxX) / 2, y: (fp.minY + fp.maxY) / 2 };\n\n // NOTE — per-row stepped treads (./rowSteps.ts) are DISABLED pending the\n // unified-surface rework. See docs/handover-2026-07-25-3d-surface-rework.md.\n //\n // They layered a second surface on top of the section prism, and the two could\n // not be kept in agreement across chart shapes: treads became large flat plates\n // that swallowed the stepping, sliver triangles appeared where seat ordering\n // failed, and seat dots were sliced by their own tread. The fix is one surface\n // function per section consumed by cap + steps + dots, not a second layer.\n // The module is kept (with its measurements in the doc comments) as input to\n // that rework; re-enabling it as-is is not the intended path.\n\n const solids = mergeMeshData([builder.build()]);\n const seatData: SeatInstanceData = buildSeatInstances(seats, input.initialState, surfaces, seatFloor, catColor);\n\n // --- Zones -----------------------------------------------------------------\n // Resolved from the SEATS, not the section outlines: a zone's meaning to a\n // buyer is the seats in it, and framing on the outlines would include the\n // aisles and margins a section is drawn with.\n const zoneDefs = doc.zones ?? [];\n const zones: SceneZone[] = [];\n if (zoneDefs.length) {\n const sectionZone = new Map<string, string>();\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'section' && o.zone) sectionZone.set(o.id, o.zone);\n }\n }\n interface Acc { n: number; minX: number; minY: number; maxX: number; maxY: number; sumY: number }\n const acc = new Map<string, Acc>();\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n // Prefer the seat's own resolved zone; fall back to its owning section's.\n const owner = surfaces.seatOwner[i];\n const zid = s.zoneId ?? (owner ? sectionZone.get(owner) : undefined);\n if (!zid) continue;\n let a = acc.get(zid);\n if (!a) { a = { n: 0, minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity, sumY: 0 }; acc.set(zid, a); }\n a.n++;\n if (s.x < a.minX) a.minX = s.x;\n if (s.y < a.minY) a.minY = s.y;\n if (s.x > a.maxX) a.maxX = s.x;\n if (s.y > a.maxY) a.maxY = s.y;\n a.sumY += surfaces.seatDeckY(i);\n }\n for (const z of zoneDefs) {\n const a = acc.get(z.id);\n const sectionIds: string[] = [];\n for (const [secId, zid] of sectionZone) if (zid === z.id) sectionIds.push(secId);\n if (!a || a.n === 0) {\n // A zone with no seats still exists (an empty hall, a zone of GA areas);\n // report it rather than dropping it, so navigation can show it as empty.\n zones.push({\n id: z.id, label: z.label, color: hexToRgb(z.color), sectionIds, seatCount: 0,\n center: [0, 0, 0], radius: 0,\n focalWorld: [(z.focalPoint ?? focal).x * M, 1.5, (z.focalPoint ?? focal).y * M],\n });\n continue;\n }\n zones.push({\n id: z.id,\n label: z.label,\n color: hexToRgb(z.color),\n sectionIds,\n seatCount: a.n,\n center: [((a.minX + a.maxX) / 2) * M, a.sumY / a.n, ((a.minY + a.maxY) / 2) * M],\n radius: 0.5 * Math.hypot((a.maxX - a.minX) * M, (a.maxY - a.minY) * M) || 1,\n // A zone may face its own point (`ZoneDef.focalPoint`); the documented\n // fallback is the floor/chart focal.\n focalWorld: [(z.focalPoint ?? focal).x * M, 1.5, (z.focalPoint ?? focal).y * M],\n });\n }\n }\n\n // --- Labels ----------------------------------------------------------------\n const labels: SceneLabel[] = [];\n const sections: SceneSection[] = [];\n const rows: SceneRow[] = [];\n for (const z of zones) {\n if (z.seatCount === 0) continue;\n // Zone labels float above the seating they name, so they read as belonging\n // to the whole block rather than to whichever seat is under them.\n labels.push({\n id: `zone:${z.id}`,\n kind: 'zone',\n text: z.label,\n anchor: [z.center[0], z.center[1] + ZONE_LABEL_LIFT_M, z.center[2]],\n color: doc.zones?.find((d) => d.id === z.id)?.color,\n });\n }\n {\n // A section is named over its own SEATS, at their mean deck height. Its\n // outline centroid would drift into the aisles a concave section wraps, and\n // on a raked tier the label would sit at the wrong height entirely.\n const acc = new Map<string, { n: number; x: number; y: number; deck: number }>();\n // Seat extent per section, for the camera fit `focusSection` needs.\n const ext = new Map<string, { minX: number; maxX: number; minY: number; maxY: number }>();\n for (let i = 0; i < seats.length; i++) {\n const owner = surfaces.seatOwner[i];\n if (!owner) continue;\n let a = acc.get(owner);\n if (!a) { a = { n: 0, x: 0, y: 0, deck: 0 }; acc.set(owner, a); }\n a.n++; a.x += seats[i].x; a.y += seats[i].y; a.deck += surfaces.seatDeckY(i);\n let e = ext.get(owner);\n if (!e) { e = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity }; ext.set(owner, e); }\n if (seats[i].x < e.minX) e.minX = seats[i].x;\n if (seats[i].x > e.maxX) e.maxX = seats[i].x;\n if (seats[i].y < e.minY) e.minY = seats[i].y;\n if (seats[i].y > e.maxY) e.maxY = seats[i].y;\n }\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type !== 'section') continue;\n const a = acc.get(o.id);\n if (!a || a.n === 0) continue;\n const e = ext.get(o.id)!;\n const centre: [number, number, number] = [(a.x / a.n) * M, a.deck / a.n, (a.y / a.n) * M];\n sections.push({\n id: o.id,\n label: o.displayLabel || o.label || o.id,\n seatCount: a.n,\n center: centre,\n // Half-diagonal of the seat extent — the same fit the zones use.\n radius: Math.max(1, Math.hypot(e.maxX - e.minX, e.maxY - e.minY) * 0.5 * M),\n // A section faces its floor's focal, which is what the camera should\n // look along so the seats present their fronts.\n focalWorld: [unit.focal.x * M, unit.baseHeightM + 1.5, unit.focal.y * M],\n });\n labels.push({\n id: `section:${o.id}`,\n kind: 'section',\n // The buyer-facing name wins over the technical one, as it does in 2D.\n text: o.displayLabel || o.label || o.id,\n anchor: [centre[0], centre[1] + SECTION_LABEL_LIFT_M, centre[2]],\n });\n }\n }\n }\n // --- GA areas, rows and seats ----------------------------------------------\n {\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type !== 'gaArea') continue;\n const c = centroidOf(o.points);\n if (!c) continue;\n // A GA area's capacity IS its buyer-facing fact — \"Standing Floor\" alone\n // does not say whether 40 or 4,000 people fit in it, and 2D shows the\n // number. The label carries both, as one line.\n const name = o.displayLabel || o.label || o.id;\n labels.push({\n id: `ga:${o.id}`,\n kind: 'ga',\n text: o.capacity > 0 ? `${name} · ${o.capacity.toLocaleString()}` : name,\n anchor: [c.x * M, unit.baseHeightM + GA_LABEL_LIFT_M, c.y * M],\n });\n }\n }\n\n // Rows are labelled at the END of the row, where a real venue paints them —\n // on the aisle, not over the middle of the seating. Anchored to the outermost\n // seat so the label tracks the row's own rake instead of a flat guess.\n const rowEnds = new Map<string, { seat: ExpandedSeat; index: number; far: number }>();\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n const far = Math.hypot(s.x - focal.x, s.y - focal.y);\n const cur = rowEnds.get(s.rowId);\n // \"End\" = furthest along the row from its own midpoint; approximated by\n // distance from the venue focal, which for a row is one of its two ends.\n if (!cur || far > cur.far) rowEnds.set(s.rowId, { seat: s, index: i, far });\n }\n for (const [rowId, end] of rowEnds) {\n // A seat's public label is `${rowLabel}-${seatNumber}`, so the row's name\n // is everything before the LAST separator — a row legitimately called\n // \"A-1\" in a segmented plan must not be truncated to \"A\".\n const cut = end.seat.label ? end.seat.label.lastIndexOf('-') : -1;\n const text = cut > 0 ? end.seat.label.slice(0, cut) : rowId;\n if (!text) continue;\n labels.push({\n id: `row:${rowId}`,\n kind: 'row',\n text,\n anchor: [end.seat.x * M, surfaces.seatDeckY(end.index) + ROW_LABEL_LIFT_M, end.seat.y * M],\n });\n }\n\n const rowAcc = new Map<string, {\n n: number; x: number; y: number; z: number;\n minX: number; maxX: number; minZ: number; maxZ: number;\n label: string; sectionId?: string;\n }>();\n for (let i = 0; i < seats.length; i++) {\n const seat = seats[i];\n const cut = seat.label ? seat.label.lastIndexOf('-') : -1;\n let acc = rowAcc.get(seat.rowId);\n if (!acc) {\n acc = {\n n: 0, x: 0, y: 0, z: 0,\n minX: Infinity, maxX: -Infinity, minZ: Infinity, maxZ: -Infinity,\n label: cut > 0 ? seat.label.slice(0, cut) : seat.rowId,\n sectionId: seat.sectionId,\n };\n rowAcc.set(seat.rowId, acc);\n }\n const offset = i * 3;\n const x = seatData.iPosition[offset];\n const y = seatData.iPosition[offset + 1];\n const z = seatData.iPosition[offset + 2];\n acc.n++; acc.x += x; acc.y += y; acc.z += z;\n acc.minX = Math.min(acc.minX, x); acc.maxX = Math.max(acc.maxX, x);\n acc.minZ = Math.min(acc.minZ, z); acc.maxZ = Math.max(acc.maxZ, z);\n }\n for (const [id, acc] of rowAcc) {\n rows.push({\n id,\n label: acc.label,\n sectionId: acc.sectionId,\n seatCount: acc.n,\n center: [acc.x / acc.n, acc.y / acc.n, acc.z / acc.n],\n radius: Math.max(0.8, Math.hypot(acc.maxX - acc.minX, acc.maxZ - acc.minZ) * 0.5),\n });\n }\n\n // Seat labels are the densest rung by far, and unlike every other kind their\n // count is the seat count. On a 52,000-seat ground that is 52,000 DOM-backed\n // labels to hold and project, for text that is only legible when a handful\n // are on screen. So they are emitted for charts where the whole set stays\n // cheap, and above that a seat identifies itself the way it already does on\n // every surface: by being tapped, which raises the seat's own card.\n if (seats.length <= SEAT_LABEL_MAX) {\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n const text = s.displayLabel || s.label;\n if (!text) continue;\n labels.push({\n id: `seat:${s.id}`,\n kind: 'seat',\n text,\n anchor: [s.x * M, surfaces.seatDeckY(i) + SEAT_LABEL_LIFT_M, s.y * M],\n });\n }\n }\n }\n\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'shape' && o.role && ['entrance', 'exit', 'screen', 'suite', 'obstruction'].includes(o.role)) {\n const poly = shapePolygon(o);\n const centre = poly ? centroidOf(poly) : null;\n if (!centre || !o.label) continue;\n labels.push({\n id: `architecture:${o.id}`,\n kind: 'annotation',\n text: o.label,\n anchor: [centre.x * M, unit.baseHeightM + architectureForShape(o).heightM + ANNOTATION_LIFT_M, centre.y * M],\n color: o.fill,\n });\n } else if (o.type === 'text') {\n // Authored wayfinding sits just above the floor it annotates — a door or\n // aisle name belongs to the ground, not to the air above it.\n if (!o.text) continue;\n labels.push({\n id: `text:${o.id}`,\n kind: 'annotation',\n text: o.text,\n anchor: [o.position.x * M, unit.baseHeightM + ANNOTATION_LIFT_M, o.position.y * M],\n color: o.color,\n rotation: o.rotation,\n });\n } else if (o.type === 'booth') {\n // A booth is a sellable unit the buyer picks by NAME, so it is labelled\n // on top of its own stand rather than left as an anonymous box.\n const poly = boothPolygon(o);\n if (!poly) continue;\n const c = centroidOf(poly);\n if (!c) continue;\n labels.push({\n id: `booth:${o.id}`,\n kind: 'booth',\n text: o.displayLabel || o.label || o.id,\n anchor: [c.x * M, unit.baseHeightM + BOOTH_HEIGHT_M + BOOTH_LABEL_LIFT_M, c.y * M],\n });\n }\n }\n }\n\n // --- Floors ----------------------------------------------------------------\n const floors: SceneFloor[] = [];\n {\n const sectionFloor = new Map<string, number>();\n for (let ui = 0; ui < units.length; ui++) {\n for (const o of units[ui].objects) if (o.type === 'section') sectionFloor.set(o.id, ui);\n }\n const acc = units.map(() => ({ n: 0, minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity, sumY: 0 }));\n for (let i = 0; i < seats.length; i++) {\n const owner = surfaces.seatOwner[i];\n const ui = owner !== null ? sectionFloor.get(owner) : undefined;\n if (ui === undefined) continue;\n const a = acc[ui];\n const s = seats[i];\n a.n++;\n if (s.x < a.minX) a.minX = s.x;\n if (s.y < a.minY) a.minY = s.y;\n if (s.x > a.maxX) a.maxX = s.x;\n if (s.y > a.maxY) a.maxY = s.y;\n a.sumY += surfaces.seatDeckY(i);\n seatFloor[i] = ui;\n }\n for (let ui = 0; ui < units.length; ui++) {\n const a = acc[ui];\n const f = doc.floors?.[ui];\n floors.push({\n index: ui,\n id: f?.id ?? `floor-${ui}`,\n label: f?.name ?? (units.length > 1 ? `Floor ${ui + 1}` : 'Venue'),\n seatCount: a.n,\n center: a.n ? [((a.minX + a.maxX) / 2) * M, a.sumY / a.n, ((a.minY + a.maxY) / 2) * M] : [0, 0, 0],\n radius: a.n ? (0.5 * Math.hypot((a.maxX - a.minX) * M, (a.maxY - a.minY) * M) || 1) : 0,\n });\n }\n }\n\n // --- Seat facing -----------------------------------------------------------\n // Resolved here rather than in `buildSeatInstances` because it needs the thing\n // only this layer knows: which floor a seat belongs to, and therefore what it\n // looks at. Runs after the floors block above, which is what fills `seatFloor`.\n seatData.iYaw = computeSeatYaw(\n seatData.iPosition,\n seats.length,\n (i) => seats[i].rowId,\n (i) => {\n const f = units[seatFloor[i]]?.focal ?? focal;\n return [f.x * M, f.y * M];\n },\n );\n\n const cx = ((fp.minX + fp.maxX) / 2) * M;\n const cz = ((fp.minY + fp.maxY) / 2) * M;\n const radius = 0.5 * Math.hypot((fp.maxX - fp.minX) * M, (fp.maxY - fp.minY) * M) || 10;\n // Camera fitting intentionally uses the authored horizontal footprint above.\n // Clipping needs the actual render volume: padded ground, tall screens, upper\n // floors and near-field chairs can all extend beyond that 2D radius.\n let renderMinX = Infinity, renderMinY = Infinity, renderMinZ = Infinity;\n let renderMaxX = -Infinity, renderMaxY = -Infinity, renderMaxZ = -Infinity;\n const includeRenderPoint = (x: number, y: number, z: number): void => {\n renderMinX = Math.min(renderMinX, x); renderMaxX = Math.max(renderMaxX, x);\n renderMinY = Math.min(renderMinY, y); renderMaxY = Math.max(renderMaxY, y);\n renderMinZ = Math.min(renderMinZ, z); renderMaxZ = Math.max(renderMaxZ, z);\n };\n for (let i = 0; i < solids.position.length; i += 3) {\n includeRenderPoint(solids.position[i], solids.position[i + 1], solids.position[i + 2]);\n }\n for (let i = 0; i < seatData.count; i++) {\n const o = i * 3;\n const x = seatData.iPosition[o], y = seatData.iPosition[o + 1], z = seatData.iPosition[o + 2];\n const r = Math.max(0.35, seatData.iMaxRadius[i] ?? 0.35);\n includeRenderPoint(x - r, y, z - r);\n includeRenderPoint(x + r, y + 1.6, z + r);\n }\n if (!Number.isFinite(renderMinX)) {\n renderMinX = cx - radius; renderMaxX = cx + radius;\n renderMinY = 0; renderMaxY = radius * 0.16;\n renderMinZ = cz - radius; renderMaxZ = cz + radius;\n }\n const renderCenter: [number, number, number] = [\n (renderMinX + renderMaxX) / 2,\n (renderMinY + renderMaxY) / 2,\n (renderMinZ + renderMaxZ) / 2,\n ];\n const renderRadius = Math.max(1, 0.5 * Math.hypot(\n renderMaxX - renderMinX,\n renderMaxY - renderMinY,\n renderMaxZ - renderMinZ,\n ));\n\n // WHAT THE CAMERA SHOULD FRAME: the sellable content, not the authored\n // floor. A gala chart's deck extends far beyond its table cluster, and\n // framing the full footprint parked the venue as a small cluster in the\n // middle of a dark field. The overview box is the seats ∪ stages extent\n // plus breathing room, clamped to the footprint (never wider than the venue\n // itself); a chart with no seats or stages falls back to the footprint.\n let contentMinX = Infinity, contentMinZ = Infinity, contentMaxX = -Infinity, contentMaxZ = -Infinity;\n for (let i = 0; i < seatData.count; i++) {\n const o = i * 3;\n const x = seatData.iPosition[o], z = seatData.iPosition[o + 2];\n if (x < contentMinX) contentMinX = x;\n if (x > contentMaxX) contentMaxX = x;\n if (z < contentMinZ) contentMinZ = z;\n if (z > contentMaxZ) contentMaxZ = z;\n }\n for (const s of stageBounds) {\n contentMinX = Math.min(contentMinX, s.cx - s.halfX);\n contentMaxX = Math.max(contentMaxX, s.cx + s.halfX);\n contentMinZ = Math.min(contentMinZ, s.cz - s.halfZ);\n contentMaxZ = Math.max(contentMaxZ, s.cz + s.halfZ);\n }\n const fpMinX = fp.minX * M, fpMaxX = fp.maxX * M, fpMinZ = fp.minY * M, fpMaxZ = fp.maxY * M;\n let boxMinX = fpMinX, boxMaxX = fpMaxX, boxMinZ = fpMinZ, boxMaxZ = fpMaxZ;\n if (Number.isFinite(contentMinX)) {\n const pad = Math.max(2, 0.06 * Math.hypot(contentMaxX - contentMinX, contentMaxZ - contentMinZ));\n boxMinX = Math.max(fpMinX, contentMinX - pad);\n boxMaxX = Math.min(fpMaxX, contentMaxX + pad);\n boxMinZ = Math.max(fpMinZ, contentMinZ - pad);\n boxMaxZ = Math.min(fpMaxZ, contentMaxZ + pad);\n }\n\n return {\n solids,\n seats: seatData,\n bounds: {\n // Centred on the CONTENT box, so an off-centre table cluster arrives\n // centred on screen instead of wherever the floor's midpoint put it.\n center: [(boxMinX + boxMaxX) / 2, radius * 0.08, (boxMinZ + boxMaxZ) / 2],\n radius,\n groundY: 0,\n box: {\n min: [boxMinX, Math.min(0, renderMinY), boxMinZ],\n max: [boxMaxX, Math.max(renderMaxY, radius * 0.05), boxMaxZ],\n },\n },\n renderBounds: { center: renderCenter, radius: renderRadius },\n stateColorLUT: themeSeatColorLUT(theme, SEAT_STATES),\n theme,\n seatCount: seats.length,\n // Look-at target ~1.5 m up so a seated camera aims slightly down at the stage.\n focalWorld: [focal.x * M, 1.5, focal.y * M],\n zones,\n stages: stageBounds,\n sections,\n rows,\n labels,\n floors,\n };\n}\n","/**\n * THE structure resolver — what a chart's seats actually form, geometrically.\n *\n * A chart is authored in 2D as sections, rows and seats. Rendering it in 3D needs\n * a level above that which the author never states: which rows belong to the same\n * physical STAND, which of them is that stand's front row, and how deep into the\n * stand each row sits. Every height in the venue follows from those three facts.\n *\n * This module resolves them, once, from the authored geometry alone. It is pure —\n * no renderer types, no GL — so the same structure feeds the 3D surface, the\n * eye-height model in `layout.ts`, and anything added later (labels, LOD\n * streaming, per-block camera framing) without any of them re-deriving it. That\n * matters because re-derivation is this codebase's most expensive bug class:\n * three surfaces held apart by offsets, and a cap and its seats measured from\n * different datums, both came from two files computing the same fact differently.\n *\n * ## Why blocks, and why depth is measured inside one\n *\n * A section is not a stand. The amphitheatre's `sec-gall` is a single section\n * holding six wedge blocks that wrap the bowl; the arena's tiers are four blocks\n * each. Height used to be a function of distance from the venue focal, which is\n * wrong for exactly this reason — the side blocks of a tier sit further from the\n * focal than the centre blocks, so they were raised as though they were further\n * back. Measured on `sec-gall`, authored at 6.20 m: its six blocks started at\n * 6.35, 6.37, 8.18, 8.23, 10.60 and 10.65 m. The side wedges were **4.4 m too\n * high**, and that is the tilt-and-lift the owner could see.\n *\n * Depth is therefore measured WITHIN a block, from that block's own front row:\n *\n * level(row) = section.height + blockDepth(row) · tan(rake)\n *\n * so every block of a tier starts at the tier's authored height, and rows rise by\n * the real spacing between them. A venue whose tier is genuinely flat authors\n * `rake = 0` and every row lands on the same level — the same model, no special\n * case, which is what lets one code path serve a stepped bowl and a flat terrace.\n */\n\nimport type { ExpandedSeat, Point } from './types';\n\n/** A row resolved into its stand. */\nexport interface ResolvedRow {\n /** The row's authored id, or a synthetic one for a seat that has none. */\n id: string;\n /** The row's seats as an ordered polyline (the shape the deck is built on). */\n pts: Point[];\n /** Indices into the input seat array, parallel to nothing — for attribution. */\n seatIndices: number[];\n /** Which block (stand) this row belongs to, within its section. */\n blockId: number;\n /** 0 for the block's front row, then 1, 2, … going back. */\n ordinal: number;\n /**\n * Distance back from the block's FRONT row, chart units, accumulated over the\n * real gaps between consecutive rows.\n *\n * Not the ordinal times a nominal pitch: a stand with an aisle break or a\n * widening row gap should rise over that gap, and accumulating the measured\n * spacing keeps the drawn deck on the seats. Not distance from the venue focal\n * either — that is the defect described above.\n */\n blockDepth: number;\n /** Mean distance of the row's seats from the venue focal (front-row ordering). */\n focalDistance: number;\n}\n\nexport interface ResolvedSection {\n sectionId: string;\n /** Rows ordered by block, then front-to-back within the block. */\n rows: ResolvedRow[];\n /** Number of distinct blocks found. */\n blockCount: number;\n}\n\n/**\n * Two rows join the same block when a probe on one lands within this multiple of\n * the section's typical row spacing of the other.\n *\n * Above 1 so consecutive rows of one stand always connect (they are one spacing\n * apart by definition, and real charts vary). Well below the width of an aisle or\n * a vomitory, which is what has to stay a break — otherwise the six wedges of a\n * bowl merge into one block and the defect this module exists to fix comes back.\n */\nconst BLOCK_LINK_RATIO = 1.8;\n\n/** Sample points along a row, used for every distance test here. */\nfunction probesOf(pts: readonly Point[]): Point[] {\n const n = pts.length;\n if (n <= 3) return [...pts];\n return [pts[Math.floor(n * 0.25)], pts[Math.floor(n * 0.5)], pts[Math.floor(n * 0.75)]];\n}\n\n/** Perpendicular distance from a point to a polyline, chart units. */\nexport function distanceToPolyline(pts: readonly Point[], x: number, y: number): number {\n if (!pts.length) return Infinity;\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** A row's bounding circle — the prune that keeps block detection off O(n²) work. */\ninterface RowBound { cx: number; cy: number; r: number }\n\nfunction boundOf(pts: readonly Point[]): RowBound {\n let cx = 0, cy = 0;\n for (const p of pts) { cx += p.x; cy += p.y; }\n const n = pts.length || 1;\n cx /= n; cy /= n;\n let r = 0;\n for (const p of pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > r) r = d;\n }\n return { cx, cy, r };\n}\n\n/**\n * Lower bound on the gap between two rows, from their bounding circles.\n *\n * Admissible: the true gap can never be smaller, so pruning on this cannot\n * discard a pair that would have linked. Pair testing is quadratic in a section's\n * row count, and a 50k-seat venue has sections with thousands of rows — without\n * this prune, resolving one of them is hundreds of millions of segment tests.\n */\nfunction boundGap(a: RowBound, b: RowBound): number {\n return Math.hypot(a.cx - b.cx, a.cy - b.cy) - a.r - b.r;\n}\n\n/** Shortest distance between two rows, measured from probes on each. */\nfunction rowGap(a: readonly Point[], b: readonly Point[]): number {\n let best = Infinity;\n for (const p of probesOf(a)) {\n const d = distanceToPolyline(b, p.x, p.y);\n if (d < best) best = d;\n }\n for (const p of probesOf(b)) {\n const d = distanceToPolyline(a, p.x, p.y);\n if (d < best) best = d;\n }\n return best;\n}\n\n/**\n * Order a row's seats along the row.\n *\n * The chord between the two extremes only decides the ORDER; distances are never\n * measured against it. A curved row orders correctly under this even though its\n * chord is a poor fit to its shape.\n */\nfunction orderAlongRow(pts: Point[], indices: number[]): { pts: Point[]; seatIndices: number[] } {\n if (pts.length < 3) return { pts, seatIndices: indices };\n let cx = 0, cy = 0;\n for (const p of pts) { cx += p.x; cy += p.y; }\n cx /= pts.length; cy /= pts.length;\n let far = -1, ax = pts[0];\n for (const p of pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > far) { far = d; ax = p; }\n }\n let dx = ax.x - cx, dy = ax.y - cy;\n const len = Math.hypot(dx, dy);\n if (len > 1e-9) { dx /= len; dy /= len; } else { dx = 1; dy = 0; }\n const order = pts.map((p, i) => ({ p, i, t: (p.x - cx) * dx + (p.y - cy) * dy }))\n .sort((u, v) => u.t - v.t);\n return { pts: order.map((o) => o.p), seatIndices: order.map((o) => indices[o.i]) };\n}\n\n/**\n * Least-squares circle through a set of points (Kåsa fit), or null if degenerate.\n *\n * Used to find the centre a section's rows were struck about, which is NOT the\n * chart's focal point. The amphitheatre makes this concrete: its rows are arcs\n * about a centre `C`, while `focalPoint` is the stage at {0, 75}. Measuring depth\n * from the focal therefore varied ALONG each arc — the original row tilt — and\n * made concentric rows look radial to every heuristic downstream.\n *\n * The focal is where the audience LOOKS. The row centre is what the seating was\n * drawn around. Conflating them is the mistake underneath several of the defects\n * in this file's history.\n */\nfunction fitCircle(pts: readonly Point[]): { cx: number; cy: number } | null {\n const n = pts.length;\n if (n < 3) return null;\n let mx = 0, my = 0;\n for (const p of pts) { mx += p.x; my += p.y; }\n mx /= n; my /= n;\n let suu = 0, suv = 0, svv = 0, suuu = 0, svvv = 0, suvv = 0, svuu = 0;\n for (const p of pts) {\n const u = p.x - mx, v = p.y - my;\n suu += u * u; svv += v * v; suv += u * v;\n suuu += u * u * u; svvv += v * v * v;\n suvv += u * v * v; svuu += v * u * u;\n }\n const det = suu * svv - suv * suv;\n if (Math.abs(det) < 1e-9) return null;\n const b1 = (suuu + suvv) / 2;\n const b2 = (svvv + svuu) / 2;\n const uc = (b1 * svv - b2 * suv) / det;\n const vc = (b2 * suu - b1 * suv) / det;\n const cx = uc + mx, cy = vc + my;\n if (!Number.isFinite(cx) || !Number.isFinite(cy)) return null;\n return { cx, cy };\n}\n\n/**\n * Largest share of a section's radial depth that one row may span about a fitted\n * centre before the concentric model is rejected.\n *\n * A true arc row spans ~0 radially. A straight row spans a lot. 0.15 separates\n * them decisively without needing to know which kind of venue this is.\n */\nconst CIRCLE_ROW_SPREAD_LIMIT = 0.15;\n\n/**\n * A block's rake axis: the direction its rows recede in, or null when its rows do\n * not share one.\n *\n * Fitted PER BLOCK, never per section. A section routinely holds several stands\n * at different angles (a bowl's six wedges), and one axis over all of them fits\n * none — that was tried and did nothing. Within a block the rows ARE parallel, so\n * the fit is well posed.\n *\n * The fit is then VERIFIED rather than trusted: a row must be narrow along the\n * axis compared with the block's whole depth, which is what \"the rows lie across\n * the axis\" means. Rows that run radially fail this by construction, and the\n * caller falls back rather than producing nonsense from a fit that does not hold.\n */\nfunction fitBlockAxis(group: readonly ResolvedRow[]): { x: number; y: number } | null {\n if (group.length < 2) return null;\n const cents = group.map((r) => {\n let x = 0, y = 0;\n for (const p of r.pts) { x += p.x; y += p.y; }\n return { x: x / r.pts.length, y: y / r.pts.length };\n });\n let ox = 0, oy = 0;\n for (const c of cents) { ox += c.x; oy += c.y; }\n ox /= cents.length; oy /= cents.length;\n\n let sxx = 0, sxy = 0, syy = 0;\n for (const c of cents) {\n const dx = c.x - ox, dy = c.y - oy;\n sxx += dx * dx; sxy += dx * dy; syy += dy * dy;\n }\n const tr = sxx + syy;\n const det = sxx * syy - sxy * sxy;\n const lambda = tr / 2 + Math.sqrt(Math.max(0, (tr * tr) / 4 - det));\n let ax: number, ay: number;\n if (Math.abs(sxy) > 1e-12) { ax = lambda - syy; ay = sxy; }\n else if (sxx >= syy) { ax = 1; ay = 0; }\n else { ax = 0; ay = 1; }\n const len = Math.hypot(ax, ay);\n if (!(len > 1e-12)) return null;\n ax /= len; ay /= len;\n\n // Verify: how wide is a row along the axis, against the block's total range?\n let lo = Infinity, hi = -Infinity;\n const widths: number[] = [];\n for (const r of group) {\n let rlo = Infinity, rhi = -Infinity;\n for (const p of r.pts) {\n const t = p.x * ax + p.y * ay;\n if (t < rlo) rlo = t;\n if (t > rhi) rhi = t;\n if (t < lo) lo = t;\n if (t > hi) hi = t;\n }\n widths.push(rhi - rlo);\n }\n const range = hi - lo;\n if (!(range > 1e-9)) return null;\n widths.sort((a, b) => a - b);\n // The 90th percentile, not the median. \"The rows lie across this axis\" has to\n // hold for nearly ALL of them; a median passes a block that is half wrong. The\n // amphitheatre orchestra is exactly that case: its four wedges merge into one\n // block (their radial rows converge near the focal and genuinely touch), so\n // rows in two of the wedges lie across any fitted axis and rows in the other\n // two run along it. The median was narrow, the fit was accepted, and depth\n // accumulated to 1,716 units — a 9.81 m rise on a tier authored at 0 m, with\n // the terrace above it at 2.20 m. One outlying tail is tolerated; half a block\n // is not.\n const p90 = widths[Math.min(widths.length - 1, Math.floor(widths.length * 0.9))];\n return p90 / range <= AXIS_ROW_WIDTH_LIMIT ? { x: ax, y: ay } : null;\n}\n\n/**\n * Largest share of a block's total depth that a single row may span along the\n * fitted axis before the fit is rejected.\n *\n * A row lying across the axis spans almost nothing along it, so real stands sit\n * far below this. A radially-running row spans nearly the whole range and is\n * rejected. 0.35 sits clear of both.\n */\nconst AXIS_ROW_WIDTH_LIMIT = 0.35;\n\n/**\n * Resolve one section's seats into blocks and depth-ordered rows.\n *\n * `seatIndices` are indices into the caller's own seat array, so a caller can\n * attribute the result back without this module knowing what it is attributing to.\n */\nexport function resolveSection(\n sectionId: string,\n seats: readonly ExpandedSeat[],\n seatIndices: readonly number[],\n focal: Point,\n): ResolvedSection {\n // --- Group by authored row -------------------------------------------------\n const groups = new Map<string, { pts: Point[]; idx: number[] }>();\n for (const i of seatIndices) {\n const s = seats[i];\n // A seat with no row is its own group: it can never drag a real row's shape,\n // and it still gets a deck under it downstream.\n const key = s.rowId || `__seat-${i}`;\n let g = groups.get(key);\n if (!g) { g = { pts: [], idx: [] }; groups.set(key, g); }\n g.pts.push({ x: s.x, y: s.y });\n g.idx.push(i);\n }\n if (!groups.size) return { sectionId, rows: [], blockCount: 0 };\n\n const rows: ResolvedRow[] = [];\n for (const [id, g] of groups) {\n const ordered = orderAlongRow(g.pts, g.idx);\n let sum = 0;\n for (const p of ordered.pts) sum += Math.hypot(p.x - focal.x, p.y - focal.y);\n rows.push({\n id,\n pts: ordered.pts,\n seatIndices: ordered.seatIndices,\n blockId: -1,\n ordinal: 0,\n blockDepth: 0,\n focalDistance: sum / ordered.pts.length,\n });\n }\n\n // --- Typical row spacing, to scale the block-link threshold -----------------\n // The median nearest-row gap: robust to a section holding one isolated row, and\n // to blocks whose internal spacing differs from each other's.\n const bounds = rows.map((r) => boundOf(r.pts));\n const nearest: number[] = [];\n for (let i = 0; i < rows.length; i++) {\n let best = Infinity;\n for (let j = 0; j < rows.length; j++) {\n if (i === j) continue;\n // Prune before the exact test; the bound can only understate the gap.\n if (boundGap(bounds[i], bounds[j]) >= best) continue;\n const d = rowGap(rows[i].pts, rows[j].pts);\n if (d < best) best = d;\n }\n if (Number.isFinite(best)) nearest.push(best);\n }\n nearest.sort((a, b) => a - b);\n const typicalGap = nearest.length ? nearest[Math.floor(nearest.length / 2)] : 1;\n const linkDistance = Math.max(typicalGap * BLOCK_LINK_RATIO, 1e-6);\n\n // --- Blocks: connected components of \"close enough to be the same stand\" ----\n const adjacency: number[][] = rows.map(() => []);\n for (let i = 0; i < rows.length; i++) {\n for (let j = i + 1; j < rows.length; j++) {\n if (boundGap(bounds[i], bounds[j]) > linkDistance) continue;\n if (rowGap(rows[i].pts, rows[j].pts) <= linkDistance) {\n adjacency[i].push(j);\n adjacency[j].push(i);\n }\n }\n }\n let blockCount = 0;\n for (let i = 0; i < rows.length; i++) {\n if (rows[i].blockId !== -1) continue;\n const id = blockCount++;\n const stack = [i];\n rows[i].blockId = id;\n while (stack.length) {\n const k = stack.pop()!;\n for (const n of adjacency[k]) {\n if (rows[n].blockId !== -1) continue;\n rows[n].blockId = id;\n stack.push(n);\n }\n }\n }\n\n // --- Depth within each block, along that block's own rake axis --------------\n const byBlock = new Map<number, ResolvedRow[]>();\n for (const r of rows) {\n const a = byBlock.get(r.blockId) ?? [];\n a.push(r);\n byBlock.set(r.blockId, a);\n }\n // --- Concentric model first: is this section a set of arcs about one centre? --\n //\n // Tried before the per-block axis because it is the stronger statement. When it\n // holds, depth is a single scalar (radius) shared by EVERY block, so the wedges\n // of a tier are level with each other by construction rather than by each\n // block happening to resolve the same way. That is what a real tier is.\n // Fit PER ROW and take the median centre, rather than one circle through every\n // seat. A single algebraic fit over points lying on many concentric rings is\n // biased toward the middle ring — measured, it put the amphitheatre orchestra's\n // centre at y = -265 when the chart struck those arcs about y = -520, which\n // made true arcs look 27 % radially spread and got the concentric model\n // rejected. One row is a single arc, so its own fit is well conditioned, and\n // the median across rows is immune to the few rows that are short or straight.\n const centres: Point[] = [];\n for (const r of rows) {\n if (r.pts.length < 4) continue;\n const c = fitCircle(r.pts);\n if (!c) continue;\n if (!Number.isFinite(c.cx) || !Number.isFinite(c.cy)) continue;\n centres.push({ x: c.cx, y: c.cy });\n }\n const centre = centres.length >= 2\n ? ((): { cx: number; cy: number } => {\n const xs = centres.map((c) => c.x).sort((a, b) => a - b);\n const ys = centres.map((c) => c.y).sort((a, b) => a - b);\n const mid = Math.floor(centres.length / 2);\n return { cx: xs[mid], cy: ys[mid] };\n })()\n : null;\n if (centre) {\n const radiusOf = (p: Point): number => Math.hypot(p.x - centre.cx, p.y - centre.cy);\n let lo = Infinity, hi = -Infinity;\n const spreads: number[] = [];\n for (const r of rows) {\n let rlo = Infinity, rhi = -Infinity;\n for (const p of r.pts) {\n const d = radiusOf(p);\n if (d < rlo) rlo = d;\n if (d > rhi) rhi = d;\n }\n spreads.push(rhi - rlo);\n if (rlo < lo) lo = rlo;\n if (rhi > hi) hi = rhi;\n }\n const range = hi - lo;\n spreads.sort((a, b) => a - b);\n const p90 = spreads[Math.min(spreads.length - 1, Math.floor(spreads.length * 0.9))];\n if (range > 1e-9 && p90 / range <= CIRCLE_ROW_SPREAD_LIMIT) {\n const withRadius = rows.map((r) => {\n let sum = 0;\n for (const p of r.pts) sum += radiusOf(p);\n return { row: r, radius: sum / r.pts.length };\n });\n let minR = Infinity;\n for (const w of withRadius) if (w.radius < minR) minR = w.radius;\n withRadius.sort((a, b) => a.radius - b.radius);\n // Rows at the same radius are the SAME row split by an aisle, so they share\n // an ordinal and a level regardless of which block they landed in.\n let ordinal = -1, lastRadius = -Infinity;\n const tolerance = range / Math.max(1, withRadius.length) * 0.5;\n const ordered: ResolvedRow[] = [];\n for (const w of withRadius) {\n if (w.radius - lastRadius > tolerance) { ordinal++; lastRadius = w.radius; }\n w.row.ordinal = ordinal;\n w.row.blockDepth = w.radius - minR;\n ordered.push(w.row);\n }\n return { sectionId, rows: ordered, blockCount };\n }\n }\n\n const out: ResolvedRow[] = [];\n for (const [, group] of byBlock) {\n const axis = fitBlockAxis(group);\n if (axis) {\n // Rows are parallel and across the axis: order and measure along it. Depth\n // accumulates the REAL gap between consecutive rows, so an aisle break\n // inside a stand rises across it rather than being flattened to one step.\n const key = (r: ResolvedRow): number => {\n let sum = 0;\n for (const p of r.pts) sum += p.x * axis.x + p.y * axis.y;\n return sum / r.pts.length;\n };\n group.sort((a, b) => key(a) - key(b));\n let depth = 0;\n for (let i = 0; i < group.length; i++) {\n if (i > 0) depth += rowGap(group[i - 1].pts, group[i].pts);\n group[i].ordinal = i;\n group[i].blockDepth = depth;\n out.push(group[i]);\n }\n } else {\n // No usable axis — this block's rows are not across a common direction.\n // The amphitheatre orchestra is the real case: its rows run RADIALLY, each\n // spanning radius 6 to 310, so no ordering of them is a front-to-back\n // ordering. Sorting by focal distance and accumulating gaps ran the depth\n // to 8,585 units (197 m) and pinned the tier against its runaway clamp.\n //\n // Fall back to depth measured straight from the focal, relative to this\n // block's own front. It is the pre-block model, which handled this shape\n // correctly, and confining it to the block keeps the property that every\n // block of a tier starts at the tier's authored height.\n let front = Infinity;\n for (const r of group) if (r.focalDistance < front) front = r.focalDistance;\n group.sort((a, b) => a.focalDistance - b.focalDistance);\n for (let i = 0; i < group.length; i++) {\n group[i].ordinal = i;\n group[i].blockDepth = Math.max(0, group[i].focalDistance - front);\n out.push(group[i]);\n }\n }\n }\n return { sectionId, rows: out, blockCount };\n}\n","/**\n * Pure geometry helpers — no Konva, no DOM. Turns the parametric chart\n * document into concrete seat coordinates and computes bounding boxes.\n */\n\nimport type {\n AccessibilityType,\n BoothObject,\n ChartDoc,\n ChartObject,\n ExpandedSeat,\n Floor,\n LabelStyle,\n Point,\n RowObject,\n RectTableSeatCounts,\n RectTableSide,\n SeatConfidenceCoverage,\n SeatConfidenceEvidence,\n SeatOverride,\n SeatViewMetadata,\n SectionObject,\n TableObject,\n} from './types';\nimport { textLayoutMetrics } from './textLayout';\nimport { distributeAlongCubic } from './complexGeometry';\nimport { polygonInscribedAnchor } from './polygonAnchor';\nimport { translateSectionOutlinePath } from './sectionPath';\nimport { resolveSegmentedRowGroups, type SegmentedRowPlacement } from './segmentedRowModel';\nimport { seatDisplayLabel, seatInventoryLabel } from './seatNumbering';\nimport { METRES_PER_CHART_UNIT, SEATED_EYE_HEIGHT_M, sectionGeometry } from './units';\nimport { resolveSection } from './venueStructure';\n\n/** Resolve a seat override's accessibility, honouring the legacy boolean flag. */\nfunction overrideAccessibility(o: SeatOverride | undefined): AccessibilityType[] {\n if (!o) return [];\n if (o.accessibility && o.accessibility.length) {\n return o.wheelchairSpaceType && !o.accessibility.includes('wheelchair')\n ? ['wheelchair', ...o.accessibility]\n : o.accessibility;\n }\n if (o.wheelchairSpaceType) return ['wheelchair'];\n return o.accessible ? ['wheelchair'] : [];\n}\n\nconst DEG = Math.PI / 180;\n/** Seat visual radius (mirrors the renderer) — used only for bounds padding. */\nconst SEAT_R = 9;\n/** How far outside a table's body its seats sit. */\nconst TABLE_SEAT_OFFSET = 16;\n\n/** Rotate a local point clockwise by `deg` (screen coords, +y down) then translate. */\nfunction place(lx: number, ly: number, deg: number, origin: Point): { x: number; y: number } {\n const a = deg * DEG;\n const cos = Math.cos(a);\n const sin = Math.sin(a);\n return {\n x: origin.x + lx * cos - ly * sin,\n y: origin.y + lx * sin + ly * cos,\n };\n}\n\n/**\n * Expand a parametric row into seat positions.\n *\n * Straight (curve === 0): seat i sits at local (i·spacing, 0).\n *\n * Curved: seats lie on a circular arc. Per-seat angular step is\n * `curve/(seatCount-1)`; radius is derived so the chord between neighbours\n * equals seatSpacing → radius = spacing / (2·sin(step/2)). Seat 0 sits at the\n * bottom of the arc (local origin) with the circle centre directly above it at\n * (0,-radius); increasing index sweeps toward +x while the ends rise toward -y,\n * i.e. positive curve is concave toward -y (a row wrapping a stage above it).\n * Local points are then rotated by `rotation` and translated to `origin`.\n *\n * Numbering (labels only, never geometry): `seatNumbering.direction === 'rtl'`\n * numbers from the far physical end; `step === 2` produces odd/even numbering\n * (start at 1 → 1,3,5…; start at 2 → 2,4,6…).\n */\n/** Base (pre-override) seat centre per index — shared by expandRow + designer edit mode. */\nexport function rowSeatPositions(row: RowObject): Point[] {\n const { seatCount, seatSpacing, curve, rotation, origin } = row;\n const out: Point[] = [];\n if (row.path) return distributeAlongCubic(row.path, seatCount);\n if (seatCount <= 1) {\n if (seatCount === 1) out.push({ x: origin.x, y: origin.y });\n return out;\n }\n if (curve === 0) {\n for (let i = 0; i < seatCount; i++) out.push(place(i * seatSpacing, 0, rotation, origin));\n return out;\n }\n const arcStep = (curve / (seatCount - 1)) * DEG; // radians per seat\n const radius = seatSpacing / (2 * Math.sin(Math.abs(arcStep) / 2));\n // Centre above seat 0. φ measured from the downward vertical, growing with index.\n for (let i = 0; i < seatCount; i++) {\n const phi = i * arcStep;\n const lx = radius * Math.sin(phi);\n const ly = -radius + radius * Math.cos(phi); // ≤ 0 → ends rise toward -y\n out.push(place(lx, ly, rotation, origin));\n }\n return out;\n}\n\nfunction overrideMap(row: RowObject): Map<number, SeatOverride> {\n const m = new Map<number, SeatOverride>();\n if (row.overrides) for (const o of row.overrides) m.set(o.index, o);\n return m;\n}\n\n/** Sellable row slots: skipped slots are absent; empty wheelchair bays remain. */\nexport function rowInventoryCount(row: RowObject): number {\n const skipped = new Set((row.overrides ?? [])\n .filter((override) => override.skip && Number.isInteger(override.index)\n && override.index >= 0 && override.index < row.seatCount)\n .map((override) => override.index));\n return Math.max(0, row.seatCount - skipped.size);\n}\n\n/**\n * Every seat slot of a row INCLUDING skipped ones (designer seat-edit mode uses\n * this to draw un-skip handles). Overrides (dx/dy/label/categoryKey) are applied\n * but a skipped slot is flagged, not omitted.\n */\nexport interface RowSeatSlot {\n index: number;\n x: number;\n y: number;\n label: string;\n displayLabel: string;\n categoryKey: string;\n skipped: boolean;\n accessible: boolean;\n accessibility: AccessibilityType[];\n wheelchairSpaceType?: SeatOverride['wheelchairSpaceType'];\n commercial?: RowObject['commercial'];\n viewUrl?: string;\n viewMeta?: SeatViewMetadata;\n labelStyle?: LabelStyle;\n}\n\n/** Every authored table-chair slot, including skipped inventory. This mirrors\n * row seat slots so Designer, MCP and buyer/event expansion share one semantic\n * source while retaining the table's stable numeric slot identity. */\nexport interface TableSeatSlot extends RowSeatSlot {\n side?: RectTableSide;\n}\n\n/**\n * Seat slots for one physical row.\n *\n * `placement` makes the slots read as part of a logical segmented row: labels\n * continue across components (`A-8`, not a second `A-1`) using the logical row's\n * numbering scheme and total seat count.\n */\nexport function expandRowSlots(row: RowObject, placement?: SegmentedRowPlacement): RowSeatSlot[] {\n const ov = overrideMap(row);\n return rowSeatPositions(row).map((p, i) => {\n const o = ov.get(i);\n const accessibility = overrideAccessibility(o);\n // Inventory label stays PHYSICAL (stable booking identity); only the\n // human-facing label follows a segmented row's logical numbering. Both\n // resolved by `seatNumbering` so no surface can make this split differently.\n const inventoryLabel = seatInventoryLabel(row, i, o);\n const commercial = { ...row.commercial, ...o?.commercial };\n return {\n index: i,\n x: p.x + (o?.dx ?? 0),\n y: p.y + (o?.dy ?? 0),\n label: inventoryLabel,\n displayLabel: seatDisplayLabel(row, i, placement, o),\n categoryKey: o?.categoryKey ?? row.categoryKey,\n skipped: !!o?.skip,\n accessible: accessibility.length > 0,\n accessibility,\n wheelchairSpaceType: o?.wheelchairSpaceType,\n commercial: Object.values(commercial).some((value) => value !== undefined && value !== false && value !== '') ? commercial : undefined,\n viewUrl: o?.viewFromSeatUrl ?? row.viewFromSeatUrl,\n viewMeta: o?.viewFromSeatUrl ? o.viewFromSeatMeta : row.viewFromSeatMeta,\n labelStyle: o?.labelStyle,\n };\n });\n}\n\nexport function expandRow(row: RowObject): ExpandedSeat[] {\n const seats: ExpandedSeat[] = [];\n for (const slot of expandRowSlots(row)) {\n if (slot.skipped) continue; // physical seat absent; numbering gap preserved\n seats.push({\n id: `${row.id}:${slot.index}`,\n label: slot.label,\n displayLabel: slot.displayLabel === slot.label ? undefined : slot.displayLabel,\n x: slot.x,\n y: slot.y,\n rowId: row.id,\n categoryKey: slot.categoryKey,\n accessible: slot.accessible || undefined,\n accessibility: slot.accessibility.length ? slot.accessibility : undefined,\n wheelchairSpaceType: slot.wheelchairSpaceType,\n commercial: slot.commercial,\n viewUrl: slot.viewUrl,\n viewMeta: slot.viewMeta,\n labelStyle: slot.labelStyle,\n });\n }\n return seats;\n}\n\n/**\n * Expand a table into its perimeter seats.\n *\n * Round: seats spread evenly on a circle of radius `radius + 16`, the first at\n * angle `rotation` (degrees, clockwise from +x). Rect: seats line the top and\n * bottom edges (split evenly, any remainder to the top), 16u outside the edge,\n * the whole set rotated about the table centre.\n */\n/** Legacy rect tables distribute aggregate capacity round-robin across enabled\n * sides, then emit chairs in canonical top/bottom/left/right order. */\nexport function tableSeatCountsBySide(t: TableObject): RectTableSeatCounts {\n if (t.seatCountsBySide) return { ...t.seatCountsBySide };\n const enabled = t.sides && t.sides.length ? t.sides : ['top', 'bottom'];\n const order = (['top', 'bottom', 'left', 'right'] as const).filter((side) => enabled.includes(side));\n const counts: RectTableSeatCounts = { top: 0, bottom: 0, left: 0, right: 0 };\n if (!order.length) return counts;\n const n = Math.max(0, Math.round(t.seatCount));\n for (let index = 0; index < n; index++) counts[order[index % order.length]] += 1;\n return counts;\n}\n\n/** Expand every authored table chair, including skipped slots needed by the\n * Designer to restore inventory. */\nexport function expandTableSlots(t: TableObject): TableSeatSlot[] {\n const seats: TableSeatSlot[] = [];\n const n = Math.max(0, Math.round(t.seatCount));\n if (n === 0) return seats;\n const overrides = new Map((t.overrides ?? []).map((override) => [override.index, override]));\n const mk = (index: number, x: number, y: number, side?: RectTableSide): TableSeatSlot => {\n const override = overrides.get(index);\n const accessibility = overrideAccessibility(override);\n const label = override?.label ?? `${t.label}-${index + 1}`;\n const displayPrefix = t.displayLabel ?? t.label;\n return {\n index,\n label,\n displayLabel: override?.displayLabel ?? `${displayPrefix}-${index + 1}`,\n x: x + (override?.dx ?? 0),\n y: y + (override?.dy ?? 0),\n categoryKey: override?.categoryKey ?? t.categoryKey,\n skipped: !!override?.skip,\n accessible: accessibility.length > 0,\n accessibility,\n wheelchairSpaceType: override?.wheelchairSpaceType,\n commercial: override?.commercial,\n viewUrl: override?.viewFromSeatUrl,\n viewMeta: override?.viewFromSeatUrl ? override.viewFromSeatMeta : undefined,\n labelStyle: override?.labelStyle,\n ...(side ? { side } : {}),\n };\n };\n\n if (t.shape === 'round') {\n const R = (t.radius ?? 40) + TABLE_SEAT_OFFSET;\n const base = t.rotation * DEG;\n const arc = Math.max(0, Math.min(360, t.seatArc ?? 360));\n if (arc >= 360 || n === 1) {\n for (let i = 0; i < n; i++) {\n const a = base + (i / n) * 2 * Math.PI;\n seats.push(mk(i, t.center.x + R * Math.cos(a), t.center.y + R * Math.sin(a)));\n }\n return seats;\n }\n const arcRad = arc * DEG;\n const halfGap = (2 * Math.PI - arcRad) / 2;\n const start = base + halfGap;\n for (let i = 0; i < n; i++) {\n const a = start + (i / (n - 1)) * arcRad;\n seats.push(mk(i, t.center.x + R * Math.cos(a), t.center.y + R * Math.sin(a)));\n }\n return seats;\n }\n\n const w = t.width ?? 80;\n const h = t.height ?? 50;\n const counts = tableSeatCountsBySide(t);\n const order = ['top', 'bottom', 'left', 'right'] as const;\n let idx = 0;\n for (const side of order) {\n const count = counts[side];\n for (let j = 0; j < count; j++) {\n let localX: number;\n let localY: number;\n if (side === 'top') {\n localX = -w / 2 + ((j + 0.5) * w) / count;\n localY = -h / 2 - TABLE_SEAT_OFFSET;\n } else if (side === 'bottom') {\n localX = -w / 2 + ((j + 0.5) * w) / count;\n localY = h / 2 + TABLE_SEAT_OFFSET;\n } else if (side === 'left') {\n localX = -w / 2 - TABLE_SEAT_OFFSET;\n localY = -h / 2 + ((j + 0.5) * h) / count;\n } else {\n localX = w / 2 + TABLE_SEAT_OFFSET;\n localY = -h / 2 + ((j + 0.5) * h) / count;\n }\n const point = place(localX, localY, t.rotation, t.center);\n seats.push(mk(idx++, point.x, point.y, side));\n }\n }\n return seats;\n}\n\n/** Sellable individual table chairs. Grouped tables own one atomic inventory\n * unit elsewhere and therefore retain their full authored chair capacity. */\nexport function tableInventoryCount(t: TableObject): number {\n if (t.bookAsWhole || t.variableOccupancy) return Math.max(0, Math.round(t.seatCount));\n return expandTableSlots(t).filter((slot) => !slot.skipped).length;\n}\n\nexport function expandTable(t: TableObject): ExpandedSeat[] {\n return expandTableSlots(t).filter((slot) => !slot.skipped).map((slot) => ({\n id: `${t.id}:${slot.index}`,\n label: slot.label,\n ...(slot.displayLabel !== slot.label ? { displayLabel: slot.displayLabel } : {}),\n x: slot.x,\n y: slot.y,\n rowId: t.id,\n categoryKey: slot.categoryKey,\n ...(slot.accessible ? { accessible: true } : {}),\n ...(slot.accessibility.length ? { accessibility: slot.accessibility } : {}),\n ...(slot.wheelchairSpaceType ? { wheelchairSpaceType: slot.wheelchairSpaceType } : {}),\n ...(slot.commercial ? { commercial: slot.commercial } : {}),\n ...(slot.viewUrl ? { viewUrl: slot.viewUrl } : {}),\n ...(slot.viewUrl && slot.viewMeta ? { viewMeta: slot.viewMeta } : {}),\n ...(slot.labelStyle ? { labelStyle: slot.labelStyle } : {}),\n }));\n}\n\n/** Expand a booth into its single bookable block unit. */\nexport function expandBooth(b: BoothObject): ExpandedSeat[] {\n return [\n {\n id: `${b.id}:0`,\n label: b.label,\n ...(b.displayLabel && b.displayLabel !== b.label ? { displayLabel: b.displayLabel } : {}),\n x: b.center.x,\n y: b.center.y,\n rowId: b.id,\n categoryKey: b.categoryKey,\n kind: 'booth',\n },\n ];\n}\n\n/** Ray-cast point-in-polygon test — odd crossings ⇒ inside. */\nexport function pointInPolygon(p: Point, poly: Point[]): boolean {\n let inside = false;\n for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {\n const xi = poly[i].x;\n const yi = poly[i].y;\n const xj = poly[j].x;\n const yj = poly[j].y;\n const hit = yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi;\n if (hit) inside = !inside;\n }\n return inside;\n}\n\nfunction pointOnPolygonBoundary(p: Point, poly: Point[]): boolean {\n return poly.some((start, index) => {\n const end = poly[(index + 1) % poly.length];\n const cross = (p.y - start.y) * (end.x - start.x) - (p.x - start.x) * (end.y - start.y);\n if (Math.abs(cross) > 1e-7) return false;\n const dot = (p.x - start.x) * (end.x - start.x) + (p.y - start.y) * (end.y - start.y);\n const lengthSquared = (end.x - start.x) ** 2 + (end.y - start.y) ** 2;\n return dot >= -1e-7 && dot <= lengthSquared + 1e-7;\n });\n}\n\nexport function pointInPolygonWithHoles(p: Point, outer: Point[], holes: Point[][] | undefined): boolean {\n return pointInPolygon(p, outer)\n && !(holes ?? []).some((hole) => pointInPolygon(p, hole) || pointOnPolygonBoundary(p, hole));\n}\n\n/**\n * Stable interior label anchor: the centre of the largest circle that fits in\n * the polygon (holes excluded).\n *\n * This used to return the average-of-vertices centroid whenever that happened\n * to be inside, and only fell back to a coarse 24×24 grid search when it was\n * not. \"Inside\" is a much weaker property than \"has room\": on a traced arena\n * the outer arc of a wedge carries dozens of sampled vertices and the radial\n * edges carry two, so the average is dragged onto the arc and the label pill —\n * far wider than a point — straddled the boundary into the next section.\n * Measured on the 36-section traced arena corpus, 15 of 36 anchors had under\n * 75% of the clearance actually available, some as little as 43%.\n *\n * `polygonInscribedAnchor` is deterministic and coordinate-free, so callers may\n * memoize it on geometry alone.\n */\nexport function polygonLabelPoint(outer: Point[], holes: Point[][] | undefined): Point {\n return polygonInscribedAnchor(outer, holes).point;\n}\n\n/** Average of polygon vertices (v1 centroid — good enough for labels/membership). */\nfunction polygonCentroid(pts: Point[]): Point {\n if (!pts.length) return { x: 0, y: 0 };\n let x = 0;\n let y = 0;\n for (const p of pts) {\n x += p.x;\n y += p.y;\n }\n return { x: x / pts.length, y: y / pts.length };\n}\n\n/**\n * Visual centre of any object — used for spatial section membership and for\n * rotating an object about its centre. Rows: centroid of expanded seats (or\n * origin when empty); tables/booths: centre; GA/section: polygon centroid;\n * shape: bbox centre; text: its position.\n */\nexport function objectCenter(o: ChartObject): Point {\n switch (o.type) {\n case 'row': {\n const seats = expandRow(o);\n if (!seats.length) return { x: o.origin.x, y: o.origin.y };\n let x = 0;\n let y = 0;\n for (const s of seats) {\n x += s.x;\n y += s.y;\n }\n return { x: x / seats.length, y: y / seats.length };\n }\n case 'table':\n case 'booth':\n return { x: o.center.x, y: o.center.y };\n case 'gaArea':\n return polygonCentroid(o.points);\n case 'section':\n return polygonCentroid(o.outline);\n case 'text':\n return { x: o.position.x, y: o.position.y };\n case 'shape':\n if (o.points && o.points.length) return polygonCentroid(o.points);\n if (o.x != null && o.y != null && o.width != null && o.height != null) {\n return { x: o.x + o.width / 2, y: o.y + o.height / 2 };\n }\n return { x: o.x ?? 0, y: o.y ?? 0 };\n case 'decorImage':\n return { x: o.x + o.width / 2, y: o.y + o.height / 2 };\n }\n}\n\nfunction samePoints(left: Point[], right: Point[]): boolean {\n return left.length === right.length\n && left.every((point, index) => point.x === right[index].x && point.y === right[index].y);\n}\n\nfunction sameGASurfaceAsSection(object: ChartObject, section: SectionObject): boolean {\n if (object.type !== 'gaArea' || !samePoints(object.points, section.outline)) return false;\n const objectHoles = object.holes ?? [];\n const sectionHoles = section.holes ?? [];\n return objectHoles.length === sectionHoles.length\n && objectHoles.every((hole, index) => samePoints(hole, sectionHoles[index]));\n}\n\n/**\n * Resolve one bookable object's owning physical section using the canonical\n * first-match rule. Generated reference inventory may name a logical section,\n * but that provenance is trusted only when the stored geometry confirms it.\n * Keeping this primitive in layout lets section inventory, category painting,\n * and buyer view inheritance share one ownership decision.\n */\nexport function owningSectionForObject(\n objects: ChartObject[],\n object: ChartObject,\n): SectionObject | undefined {\n const sections = objects.filter((candidate): candidate is SectionObject => candidate.type === 'section');\n const referencedLogicalId = 'referenceInventorySource' in object\n ? object.referenceInventorySource?.logicalSectionId\n : undefined;\n const center = objectCenter(object);\n const referencedOwner = referencedLogicalId\n ? sections.find((section) => (\n (section.logicalSectionId ?? section.id) === referencedLogicalId\n && (sameGASurfaceAsSection(object, section)\n || pointInPolygonWithHoles(center, section.outline, section.holes))\n ))\n : undefined;\n return referencedOwner\n ?? sections.find((section) => pointInPolygonWithHoles(center, section.outline, section.holes));\n}\n\n/**\n * Normalized floor list (Batch 5): a multi-floor chart's `floors`, or a synthetic\n * single floor wrapping a single-floor chart's `objects`. Every consumer that needs\n * to reason about floors goes through this so single-floor charts stay untouched.\n */\nexport function floorsOf(doc: ChartDoc): Floor[] {\n if (doc.floors && doc.floors.length) return doc.floors;\n return [{\n id: 'floor-0',\n name: 'Main',\n objects: doc.objects,\n focalPoint: doc.focalPoint,\n referenceImage: doc.referenceImage,\n backgroundImage: doc.backgroundImage,\n }];\n}\n\n/** Objects of one floor by id (defaults to the first floor). */\nexport function floorObjects(doc: ChartDoc, floorId?: string): ChartObject[] {\n const floors = floorsOf(doc);\n return (floorId ? floors.find((f) => f.id === floorId) : floors[0])?.objects ?? [];\n}\n\n/** Every object across ALL floors — the whole venue (single-floor = `doc.objects`). */\nexport function allObjects(doc: ChartDoc): ChartObject[] {\n return doc.floors && doc.floors.length ? doc.floors.flatMap((f) => f.objects) : doc.objects;\n}\n\n/** A copy of an object translated by (dx, dy) — every coordinate field shifted. */\nfunction translateObject(o: ChartObject, dx: number, dy: number): ChartObject {\n const p = (pt: Point): Point => ({ x: pt.x + dx, y: pt.y + dy });\n const pts = (a: Point[]): Point[] => a.map(p);\n switch (o.type) {\n case 'row':\n return { ...o, origin: p(o.origin) };\n case 'table':\n case 'booth':\n return { ...o, center: p(o.center) };\n case 'gaArea':\n return { ...o, points: pts(o.points), ...(o.holes ? { holes: o.holes.map(pts) } : {}) };\n case 'section':\n return {\n ...o,\n outline: pts(o.outline),\n ...(o.outlinePath ? { outlinePath: translateSectionOutlinePath(o.outlinePath, dx, dy) } : {}),\n ...(o.holes ? { holes: o.holes.map(pts) } : {}),\n };\n case 'text':\n return { ...o, position: p(o.position) };\n case 'shape':\n return {\n ...o,\n ...(o.points ? { points: pts(o.points) } : {}),\n ...(o.x != null ? { x: o.x + dx } : {}),\n ...(o.y != null ? { y: o.y + dy } : {}),\n };\n case 'decorImage':\n return { ...o, x: o.x + dx, y: o.y + dy };\n }\n}\n\n/**\n * Multi-floor 3D stack (Batch 5): flatten every floor into ONE doc with floor `i`\n * lifted by `i * spread` in −y, so the isometric view shows the floors as stacked\n * decks (ground at the bottom). Returns a single-floor doc (no `floors`). Meant\n * only for the 3D overview render — 2D still shows one floor via `floorObjects`.\n */\nexport function stackFloors(doc: ChartDoc, spread = 900): ChartDoc {\n if (!doc.floors || doc.floors.length < 2) return doc;\n const objects: ChartObject[] = [];\n doc.floors.forEach((f, i) => {\n const dy = -i * spread;\n for (const o of f.objects) objects.push(dy === 0 ? o : translateObject(o, 0, dy));\n });\n return { ...doc, objects, floors: undefined };\n}\n\nexport interface ExpandChartOptions {\n /** Physical height for a single-floor projection extracted from a multi-floor\n * document. Full multi-floor documents resolve each floor directly. */\n floorBaseHeightM?: number;\n /** Resolve per-seat 3D eye heights (default true). Two-dimensional consumers\n * such as thumbnails can disable this expensive stand-structure pass. */\n resolveEyeHeights?: boolean;\n}\n\nfunction expandFloorObjects(\n objects: ChartObject[],\n zones: ChartDoc['zones'],\n fallbackFocal: Point | undefined,\n activeConfigurationId: string | undefined,\n /**\n * Lowest-priority authored view-from-seat fallback for this floor: the\n * floor-level url if set, else the venue/chart-level default. Applied as the\n * final `??=` after seat/row/section, so any closer photo always wins. Absent\n * ⇒ seats with no closer photo keep `viewUrl` undefined (generated panorama).\n */\n viewFallback?: { url: string; meta?: SeatViewMetadata },\n confidenceFallback?: { evidence: SeatConfidenceEvidence; coverage: SeatConfidenceCoverage },\n): ExpandedSeat[] {\n const out: ExpandedSeat[] = [];\n const segmented = resolveSegmentedRowGroups(objects);\n const appliesToConfiguration = (meta: SeatViewMetadata | undefined): boolean => (\n !meta?.eventConfigurationId || meta.eventConfigurationId === activeConfigurationId\n );\n const confidenceApplies = (evidence: SeatConfidenceEvidence | undefined): evidence is SeatConfidenceEvidence => (\n !!evidence && (!evidence.eventConfigurationId || evidence.eventConfigurationId === activeConfigurationId)\n );\n const resolvedConfidence = (\n evidence: SeatConfidenceEvidence | undefined,\n coverage: SeatConfidenceCoverage,\n ) => confidenceApplies(evidence) ? { ...evidence, coverage } : undefined;\n\n for (const obj of objects) {\n let seats: ExpandedSeat[] = [];\n if (obj.type === 'row') seats = expandRow(obj);\n else if (obj.type === 'table') seats = expandTable(obj);\n else if (obj.type === 'booth') seats = expandBooth(obj);\n if (!seats.length) continue;\n // `expandRow` applies authored row and exact-seat media before this\n // inheritance pass. Drop a source that belongs to another event build so\n // the next valid scope—or generated live geometry—can resolve truthfully.\n for (const seat of seats) {\n if (seat.viewUrl && !appliesToConfiguration(seat.viewMeta)) {\n seat.viewUrl = undefined;\n seat.viewMeta = undefined;\n }\n }\n if (obj.type === 'row') {\n const logical = segmented.get(obj.id);\n if (logical) {\n const overrides = new Map((obj.overrides ?? []).map((override) => [override.index, override]));\n for (const seat of seats) {\n const physicalIndex = Number(seat.id.slice(seat.id.lastIndexOf(':') + 1));\n if (!Number.isInteger(physicalIndex)) continue;\n seat.logicalRowId = logical.groupId;\n seat.logicalSeatIndex = logical.adjacencyOffset + physicalIndex;\n // A seat-level display override remains the highest-precedence copy.\n // Left to `expandRow`, which drops it when it equals the inventory\n // label; only the DERIVED logical label is recomputed here.\n if (!overrides.get(physicalIndex)?.displayLabel) {\n seat.displayLabel = seatDisplayLabel(obj, physicalIndex, logical);\n }\n if (!seat.viewUrl && logical.viewFromSeatUrl && appliesToConfiguration(logical.viewFromSeatMeta)) {\n seat.viewUrl = logical.viewFromSeatUrl;\n seat.viewMeta = logical.viewFromSeatMeta;\n }\n const exactEvidence = overrides.get(physicalIndex)?.confidenceEvidence;\n seat.confidenceEvidence = resolvedConfidence(exactEvidence, 'exact-seat')\n ?? resolvedConfidence(obj.confidenceEvidence, 'row-representative')\n ?? resolvedConfidence(logical.confidenceEvidence, 'row-representative');\n }\n }\n }\n const owner = owningSectionForObject(objects, obj);\n const inheritedView = owner?.viewFromSeatUrl;\n const zone = owner?.zone ? zones?.find((candidate) => candidate.id === owner.zone) : undefined;\n const resolvedFocal = zone?.focalPoint ?? fallbackFocal;\n for (const seat of seats) {\n if (!seat.confidenceEvidence) {\n const physicalIndex = Number(seat.id.slice(seat.id.lastIndexOf(':') + 1));\n const exactEvidence = (obj.type === 'row' || obj.type === 'table') && Number.isInteger(physicalIndex)\n ? obj.overrides?.find((override) => override.index === physicalIndex)?.confidenceEvidence\n : undefined;\n seat.confidenceEvidence = resolvedConfidence(exactEvidence, 'exact-seat')\n ?? (obj.type === 'row' ? resolvedConfidence(obj.confidenceEvidence, 'row-representative') : undefined);\n }\n seat.confidenceEvidence ??= resolvedConfidence(owner?.confidenceEvidence, 'section-representative')\n ?? (confidenceFallback\n ? resolvedConfidence(confidenceFallback.evidence, confidenceFallback.coverage)\n : undefined);\n if (!seat.viewUrl && inheritedView && appliesToConfiguration(owner?.viewFromSeatMeta)) {\n seat.viewUrl = inheritedView;\n seat.viewMeta = owner?.viewFromSeatMeta;\n }\n // Floor > venue default: the last authored tier before a generated panorama.\n if (!seat.viewUrl && viewFallback && appliesToConfiguration(viewFallback.meta)) {\n seat.viewUrl = viewFallback.url;\n seat.viewMeta = viewFallback.meta;\n }\n if (owner) seat.sectionId = owner.logicalSectionId ?? owner.id;\n if (owner?.zone) seat.zoneId = owner.zone;\n if (resolvedFocal) seat.focalPoint = { ...resolvedFocal };\n }\n out.push(...seats);\n }\n return out;\n}\n\n/** Expand every seat-bearing object across all floors (rows, tables, booths).\n * Multi-floor ownership is resolved one floor at a time: local coordinates may\n * overlap between floors and must never assign a seat to another floor's section. */\nexport function expandChart(doc: ChartDoc, options: ExpandChartOptions = {}): ExpandedSeat[] {\n if (doc.floors?.length) {\n const out: ExpandedSeat[] = [];\n for (const floor of doc.floors) {\n const floorFocal = floor.focalPoint ?? doc.focalPoint;\n // Per-floor photo overrides the venue default for seats on this floor.\n const floorView = floor.viewFromSeatUrl\n ? { url: floor.viewFromSeatUrl, meta: floor.viewFromSeatMeta }\n : doc.viewFromSeatUrl\n ? { url: doc.viewFromSeatUrl, meta: doc.viewFromSeatMeta }\n : undefined;\n const confidenceFallback = floor.confidenceEvidence\n ? { evidence: floor.confidenceEvidence, coverage: 'floor-representative' as const }\n : doc.confidenceEvidence\n ? { evidence: doc.confidenceEvidence, coverage: 'venue-representative' as const }\n : undefined;\n const seats = expandFloorObjects(\n floor.objects,\n doc.zones,\n floorFocal,\n doc.eventConfigurationId,\n floorView,\n confidenceFallback,\n );\n if (options.resolveEyeHeights !== false) {\n assignEyeHeights(floor.objects, floor.focalPoint ?? doc.focalPoint, floor.baseHeightM ?? 0, seats);\n }\n out.push(...seats);\n }\n return out;\n }\n const out = expandFloorObjects(\n doc.objects,\n doc.zones,\n doc.focalPoint,\n doc.eventConfigurationId,\n doc.viewFromSeatUrl ? { url: doc.viewFromSeatUrl, meta: doc.viewFromSeatMeta } : undefined,\n doc.confidenceEvidence\n ? { evidence: doc.confidenceEvidence, coverage: 'venue-representative' }\n : undefined,\n );\n if (options.resolveEyeHeights !== false) {\n assignEyeHeights(doc.objects, doc.focalPoint, options.floorBaseHeightM ?? 0, out);\n }\n return out;\n}\n\n/**\n * Phase B2: annotate each expanded seat with a real-world eye height (metres above\n * the focal/stage datum) for the auto-360° generator. Resolved once at expand time\n * — never per render frame — so the 13k-seat render path pays no cost.\n *\n * `eyeHeightM = section front-edge height + row rise + seated eye height`, where\n * the ROW RISE is derived from the seat's DRAWN radial depth into its section\n * (chart units → metres × tan(rake)), NOT a hard-coded row pitch. Deriving rise\n * from drawn geometry (the same distances the panorama already uses horizontally)\n * is the binding fix for the 30–40% under-rise the sightline de-risk study flagged\n * (docs/3d-sightline-derisk-2026-07-21.md §7).\n *\n * A chart with no elevated/raked/height-authored section skips the spatial pass\n * entirely and every seat resolves to the flat seated-eye baseline — so legacy\n * charts stay pixel-identical and `expandChart`'s other callers pay nothing.\n */\nfunction assignEyeHeights(\n objects: ChartObject[],\n focal: Point | undefined,\n floorBaseHeightM: number,\n seats: ExpandedSeat[],\n): void {\n const sections = objects.filter((o): o is SectionObject => o.type === 'section');\n const hasGeometry = floorBaseHeightM > 0\n || sections.some((s) => s.height !== undefined || s.rake !== undefined || (s.elevation ?? 0) > 0);\n if (!sections.length || !hasGeometry || !focal) {\n for (const seat of seats) seat.eyeHeightM = floorBaseHeightM + SEATED_EYE_HEIGHT_M;\n return;\n }\n // Pass 1: owning section (first drawn section containing the seat), grouped by\n // row so each section can fit its own rake axis.\n const owner = new Array<SectionObject | null>(seats.length);\n const geo = new Map<string, { height: number; rake: number }>();\n const seatsBySection = new Map<string, number[]>();\n for (let i = 0; i < seats.length; i++) {\n const seat = seats[i];\n const sec = sections.find((s) => pointInPolygonWithHoles({ x: seat.x, y: seat.y }, s.outline, s.holes)) ?? null;\n owner[i] = sec;\n if (!sec) continue;\n if (!geo.has(sec.id)) geo.set(sec.id, sectionGeometry(sec, { floorBaseHeightM }));\n const list = seatsBySection.get(sec.id);\n if (list) list.push(i); else seatsBySection.set(sec.id, [i]);\n }\n\n // Pass 2: resolve each section's blocks and per-row depth, then bake a level.\n //\n // The SAME resolver the 3D deck is built from (`venueStructure.ts`). Depth is\n // measured back from each BLOCK's own front row, not from the venue focal:\n // measuring from the focal raised a bowl's side wedges as though they sat\n // further back, by up to 4.4 m on the amphitheatre gallery. And it must be the\n // same call in both files — a seat's eye height and the deck it stands on\n // disagreeing is the trap that made every offset constant chart-specific.\n const seatLevel = new Array<number | undefined>(seats.length).fill(undefined);\n for (const [secId, indices] of seatsBySection) {\n const g = geo.get(secId);\n if (!g) continue;\n const rakeTan = g.rake > 0 ? Math.tan((g.rake * Math.PI) / 180) : 0;\n const structure = resolveSection(secId, seats, indices, focal);\n for (const row of structure.rows) {\n const riseM = row.blockDepth * METRES_PER_CHART_UNIT * rakeTan;\n const level = g.height + riseM;\n for (const si of row.seatIndices) seatLevel[si] = level;\n }\n }\n\n for (let i = 0; i < seats.length; i++) {\n const seat = seats[i];\n const sec = owner[i];\n if (!sec) { seat.eyeHeightM = floorBaseHeightM + SEATED_EYE_HEIGHT_M; continue; }\n const g = geo.get(sec.id)!;\n seat.eyeHeightM = (seatLevel[i] ?? g.height) + SEATED_EYE_HEIGHT_M;\n }\n}\n\nconst PAD = 40;\n\n/** Axis-aligned bounds over every object plus the background image, with padding. */\nexport function chartBounds(doc: ChartDoc): { x: number; y: number; width: number; height: number } {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n const acc = (x: number, y: number) => {\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n };\n\n for (const obj of allObjects(doc)) {\n if (obj.type === 'row') {\n // Bounds need only authored 2D coordinates. Going through expandChart()\n // also resolves section ownership, labels, view metadata and (unless\n // explicitly disabled) 3D rake/elevation for every seat. On Mega Stadium\n // that unrelated work dominated both fit-to-view and the Designer minimap.\n const overrides = new Map((obj.overrides ?? []).map((override) => [override.index, override]));\n rowSeatPositions(obj).forEach((point, index) => {\n const override = overrides.get(index);\n if (override?.skip) return;\n acc(point.x + (override?.dx ?? 0), point.y + (override?.dy ?? 0));\n });\n } else if (obj.type === 'gaArea') {\n for (const p of obj.points) acc(p.x, p.y);\n } else if (obj.type === 'section') {\n for (const p of obj.outline) acc(p.x, p.y);\n } else if (obj.type === 'decorImage') {\n acc(obj.x, obj.y);\n acc(obj.x + obj.width, obj.y + obj.height);\n } else if (obj.type === 'shape') {\n if (obj.points && obj.points.length) {\n for (const p of obj.points) acc(p.x, p.y);\n } else if (obj.x != null && obj.y != null && obj.width != null && obj.height != null) {\n acc(obj.x, obj.y);\n acc(obj.x + obj.width, obj.y + obj.height);\n }\n } else if (obj.type === 'table') {\n const off = TABLE_SEAT_OFFSET + SEAT_R;\n const ext =\n obj.shape === 'round'\n ? (obj.radius ?? 40) + off\n : Math.max((obj.width ?? 80) / 2, (obj.height ?? 50) / 2) + off; // rotation-agnostic over-approximation\n acc(obj.center.x - ext, obj.center.y - ext);\n acc(obj.center.x + ext, obj.center.y + ext);\n } else if (obj.type === 'booth') {\n const ext = Math.max(obj.width, obj.height) / 2;\n acc(obj.center.x - ext, obj.center.y - ext);\n acc(obj.center.x + ext, obj.center.y + ext);\n } else if (obj.type === 'text') {\n const box = textLayoutMetrics(obj);\n acc(obj.position.x, obj.position.y);\n acc(obj.position.x + box.width, obj.position.y + box.height);\n }\n }\n\n for (const image of [doc.referenceImage, doc.backgroundImage]) {\n if (!image) continue;\n const { center, width } = image;\n const bh = (width * 3) / 4; // assume 4:3 when the true aspect is unknown at bounds-time\n acc(center.x - width / 2, center.y - bh / 2);\n acc(center.x + width / 2, center.y + bh / 2);\n }\n\n // Empty document → a sane default box around the focal point.\n if (!isFinite(minX)) {\n const f = doc.focalPoint ?? { x: 0, y: 0 };\n return { x: f.x - 200, y: f.y - 200, width: 400, height: 400 };\n }\n\n return {\n x: minX - PAD,\n y: minY - PAD,\n width: maxX - minX + PAD * 2,\n height: maxY - minY + PAD * 2,\n };\n}\n","/**\n * A section's RAKE FIELD — the scalar \"depth\" that its seating rises along.\n *\n * ## The defect this exists to fix\n *\n * Both the eye-height model (`layout.ts`) and the 3D seating surface\n * (`view3d/scene/surface.ts`) used to raise a seat by its radial distance from\n * ONE venue focal point. That is only correct when a section's rows are\n * concentric arcs about that focal.\n *\n * Measured on the amphitheatre gallery, whose rows are straight blocks: a single\n * row's seats span a radial-distance range of 105 chart units on the\n * centre-facing blocks and 240 on the side blocks. Since height is a function of\n * that distance, one row's seats landed at DIFFERENT heights — a within-row deck\n * spread of 2.30 m on the side blocks against 1.05 m on the centre-facing ones.\n * On screen the rows visibly tilt, and the tilt differs block to block, which is\n * exactly the left/right asymmetry the owner reported.\n *\n * ## Why a per-section AXIS is not enough\n *\n * The first attempt fitted one straight rake axis per section by PCA over its row\n * centroids, and scored it against the radial model. It did not fix the\n * amphitheatre, because `sec-gall` is a SINGLE section containing six wedge\n * blocks at six different orientations. No single axis — straight or radial —\n * fits them all, so the fit correctly fell back to radial and the tilt survived.\n * Sections containing several blocks are normal, not exotic.\n *\n * ## The model that does work\n *\n * Depth is defined by the section's OWN ROWS. Each row gets one depth value (the\n * mean distance of its seats from the focal), and the field at an arbitrary point\n * is an inverse-distance blend of the nearest rows' depths.\n *\n * The properties that matter:\n *\n * - **Exact on a row.** A point on a row has zero distance to it, so it takes\n * that row's depth exactly and every seat in the row lands at ONE height.\n * The tilt cannot come back, whatever the row's orientation.\n * - **Orientation-free.** Straight blocks, arcs, fans, in-the-round and blocks\n * at six different angles in one section all work, because nothing is fitted\n * to a direction.\n * - **Continuous.** It is a field over the plane, not a per-seat lookup. The 3D\n * cap is tessellated at points BETWEEN seats and needs a value and a gradient\n * there; a per-row lookup would leave the deck undefined between rows and put\n * the seats back off the surface they stand on.\n *\n * That last point is why quantising per row inside `assignEyeHeights` was tried\n * and removed: the renderer cannot follow a step function it has no definition\n * for, and the two models diverging is the trap that made every offset constant\n * chart-specific.\n */\n\nimport type { Point } from './types';\n\n/** A resolved row: its seats as an ordered polyline, plus its depth ordinate. */\nexport interface RakeRowFit {\n /** The row's seats, ordered along the row. */\n readonly pts: readonly Point[];\n /** Mean distance of the row's seats from the focal — what rise is a function of. */\n readonly depth: number;\n}\n\nexport interface SectionRake {\n readonly kind: 'radial' | 'rows';\n /** Depth ordinate at a chart-unit point; rise is a function of this. */\n depthAt(x: number, y: number): number;\n /** Unit gradient direction of `depthAt` — turns a rake angle into a normal. */\n gradientAt(x: number, y: number): readonly [number, number];\n /** Number of rows the field was built from (0 for the radial fallback). */\n readonly rowCount: number;\n /**\n * The section's rows, ORDERED BY DEPTH (front first).\n *\n * The deck is built directly from these — one level ribbon per row — rather\n * than by tessellating `depthAt`. See `deckBands.ts` for why: a section holding\n * several blocks at different heights has a genuine cliff between them, and no\n * continuous field can be both level on every row and trackable by refinement.\n */\n readonly rows: readonly RakeRowFit[];\n /** Index into `rows` of the row nearest a point, or -1 when there are none. */\n nearestRow(x: number, y: number): number;\n}\n\n/** One row's member points, in any order. */\nexport interface RakeRow {\n points: Point[];\n}\n\n/** How many nearest rows contribute to a blended sample. */\nconst BLEND_ROWS = 3;\n\n/**\n * A row further than this multiple of the nearest row's distance contributes\n * nothing. Just above 2 so the two rows bracketing a point midway between them\n * both count (their distances differ by at most the row pitch), while a row on\n * the far side of an aisle or beyond the last row does not.\n */\nconst BLEND_DISTANCE_RATIO = 2.2;\n\n/** Step used for the finite-difference gradient, chart units. */\nconst GRAD_STEP = 0.5;\n\nfunction radialRake(focal: Point): SectionRake {\n return {\n kind: 'radial',\n rowCount: 0,\n rows: [],\n nearestRow: () => -1,\n depthAt: (x, y) => Math.hypot(x - focal.x, y - focal.y),\n gradientAt: (x, y) => {\n const dx = x - focal.x, dy = y - focal.y;\n const d = Math.hypot(dx, dy);\n // At the focal the gradient is undefined; the surface is flat there anyway.\n return d < 1e-9 ? [0, 0] : [dx / d, dy / d];\n },\n };\n}\n\ninterface RowFit {\n /**\n * The row as an ordered POLYLINE through its own seats — not a straight chord.\n *\n * A chord was tried first and broke the arena: its upper-bowl rows are strong\n * arcs, so a seat at the end of a row sits far from the chord between the row's\n * extremes. The blend below then treated that seat as \"between rows\" and gave\n * it a neighbouring row's depth, which put the within-row spread UP from 0.07 m\n * to 2.83 m. Distance must be measured to the row's real shape, so that every\n * one of its seats reads as being exactly on it.\n */\n pts: Point[];\n /** Row centroid, and the radius of a circle about it containing the row. */\n cx: number;\n cy: number;\n radius: number;\n /** The row's depth ordinate — mean distance of its seats from the focal. */\n depth: number;\n}\n\n/**\n * Reduce a row to a segment plus a depth.\n *\n * The direction comes from the row's own extent (the two furthest-apart seats),\n * not from a fitted line: it is exact for a straight row, good enough for a\n * gently curved one, and cannot be thrown off by an outlier the way a\n * least-squares fit through few points can.\n */\nfunction fitRow(points: Point[], focal: Point): RowFit | null {\n if (!points.length) return null;\n let cx = 0, cy = 0, depth = 0;\n for (const p of points) {\n cx += p.x; cy += p.y;\n depth += Math.hypot(p.x - focal.x, p.y - focal.y);\n }\n const n = points.length;\n cx /= n; cy /= n; depth /= n;\n\n // Order the seats along the row so consecutive pairs are real segments. The\n // chord direction is only used for this ORDERING, never for distance, so a\n // curved row orders correctly even though its chord is a poor fit to it.\n let ax = points[0], far = -1;\n for (const p of points) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > far) { far = d; ax = p; }\n }\n let dx = ax.x - cx, dy = ax.y - cy;\n const len = Math.hypot(dx, dy);\n if (len > 1e-9) { dx /= len; dy /= len; } else { dx = 1; dy = 0; }\n const pts = [...points].sort((p, q) =>\n ((p.x - cx) * dx + (p.y - cy) * dy) - ((q.x - cx) * dx + (q.y - cy) * dy));\n\n let radius = 0;\n for (const p of pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > radius) radius = d;\n }\n return { pts, cx, cy, radius, depth };\n}\n\n/** Distance from a point to a row's polyline. */\nfunction distToRow(r: RowFit, x: number, y: number): number {\n const pts = r.pts;\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** How many rows survive the cheap bounding-circle prefilter. */\nconst CANDIDATE_ROWS = 8;\n\n/**\n * Inverse-distance blend of the nearest rows' depths.\n *\n * Only the nearest few rows contribute, so a distant block on the far side of the\n * venue cannot drag a section's near rows. Squared inverse distance makes the\n * nearest row dominate quickly, which keeps the field flat ALONG a row and\n * varying across it — the shape a rake actually has.\n */\nfunction sampleRows(rows: RowFit[], x: number, y: number): number {\n // Prefilter on each row's bounding circle. `centroidDist - radius` is a true\n // LOWER bound on the polyline distance, so this cannot discard a row that would\n // have won — it just keeps the exact polyline test off ~90 % of the rows, which\n // is what makes the field affordable per cap vertex.\n const candD = new Array<number>(CANDIDATE_ROWS).fill(Infinity);\n const candI = new Array<number>(CANDIDATE_ROWS).fill(-1);\n for (let i = 0; i < rows.length; i++) {\n const r = rows[i];\n const lower = Math.hypot(x - r.cx, y - r.cy) - r.radius;\n for (let k = 0; k < CANDIDATE_ROWS; k++) {\n if (lower < candD[k]) {\n for (let j = CANDIDATE_ROWS - 1; j > k; j--) { candD[j] = candD[j - 1]; candI[j] = candI[j - 1]; }\n candD[k] = lower; candI[k] = i;\n break;\n }\n }\n }\n\n // Nearest BLEND_ROWS by insertion (the list is short and this avoids a sort).\n const bestD = new Array<number>(BLEND_ROWS).fill(Infinity);\n const bestI = new Array<number>(BLEND_ROWS).fill(-1);\n for (const i of candI) {\n if (i < 0) continue;\n const d = distToRow(rows[i], x, y);\n for (let k = 0; k < BLEND_ROWS; k++) {\n if (d < bestD[k]) {\n for (let j = BLEND_ROWS - 1; j > k; j--) { bestD[j] = bestD[j - 1]; bestI[j] = bestI[j - 1]; }\n bestD[k] = d; bestI[k] = i;\n break;\n }\n }\n }\n if (bestI[0] < 0) return 0;\n // Exactly on a row: take its depth, so a seat is never blended off its own row.\n if (bestD[0] < 1e-6) return rows[bestI[0]].depth;\n\n // Blend only across rows at COMPARABLE distance. Absolute inverse-distance\n // weighting was tried and produced the cap's worst error: in the empty parts of\n // a section outline — beyond the last row, or the gap between two blocks — the\n // two nearest rows can both be hundreds of units away, so their weights stay\n // comparable and the field goes on interpolating between them across the void.\n // Measured, 79 of the gallery's 97 out-of-tolerance sample points sat more than\n // 100 units from ANY seat, and a cap triangle spanning that region missed the\n // surface by up to 10.84 m.\n //\n // A relative cutoff makes the far field settle to the nearest row's depth, i.e.\n // locally CONSTANT, which a flat triangle interpolates exactly. Inside the\n // seating the nearest rows are all within the cutoff, so blending is unchanged\n // and rows stay level.\n const cutoff = bestD[0] * BLEND_DISTANCE_RATIO;\n let num = 0, den = 0;\n for (let k = 0; k < BLEND_ROWS; k++) {\n const i = bestI[k];\n if (i < 0 || bestD[k] > cutoff) continue;\n const w = 1 / (bestD[k] * bestD[k]);\n num += w * rows[i].depth;\n den += w;\n }\n return den > 0 ? num / den : rows[bestI[0]].depth;\n}\n\nfunction rowsRake(rows: RowFit[]): SectionRake {\n const depthAt = (x: number, y: number): number => sampleRows(rows, x, y);\n const nearestRow = (x: number, y: number): number => {\n let best = Infinity, bestI = -1;\n for (let i = 0; i < rows.length; i++) {\n // Bounding-circle lower bound first; only test the polyline if it can win.\n const r = rows[i];\n if (Math.hypot(x - r.cx, y - r.cy) - r.radius >= best) continue;\n const d = distToRow(r, x, y);\n if (d < best) { best = d; bestI = i; }\n }\n return bestI;\n };\n return {\n kind: 'rows',\n rowCount: rows.length,\n rows,\n nearestRow,\n depthAt,\n gradientAt: (x, y) => {\n // Central differences: the blend has no closed form, and shading only needs\n // the direction. A half-unit step is far below row pitch, so this tracks\n // the field rather than smoothing across rows.\n const gx = (depthAt(x + GRAD_STEP, y) - depthAt(x - GRAD_STEP, y)) / (2 * GRAD_STEP);\n const gy = (depthAt(x, y + GRAD_STEP) - depthAt(x, y - GRAD_STEP)) / (2 * GRAD_STEP);\n const len = Math.hypot(gx, gy);\n return len < 1e-9 ? [0, 0] : [gx / len, gy / len];\n },\n };\n}\n\n/**\n * Build a section's rake field from its rows.\n *\n * `rows` should be the section's member seats grouped by row. With fewer than two\n * usable rows there is nothing to build a field from, so the radial model is kept\n * — which is also how every chart behaved before this, leaving a row-less or\n * single-row section unchanged.\n */\nexport function buildSectionRake(rows: RakeRow[], focal: Point): SectionRake {\n const fits: RowFit[] = [];\n for (const r of rows) {\n const f = fitRow(r.points, focal);\n if (f) fits.push(f);\n }\n if (fits.length < 2) return radialRake(focal);\n // Front-first ordering is the contract `rows` promises, and the deck builder\n // relies on it to pair each ribbon with the riser below it.\n fits.sort((a, b) => a.depth - b.depth);\n return rowsRake(fits);\n}\n","/**\n * The raked deck, built from a section's ROWS instead of from its outline.\n *\n * ## Why the deck is not a tessellated surface any more\n *\n * The previous deck was one cap over the section outline, tessellated against a\n * height field and refined until it tracked it. That works only while the field\n * is smooth and convex. Once height is defined so that every row is LEVEL — the\n * fix for rows visibly tilting, see `core/rake.ts` — the field stops being\n * either:\n *\n * - A section commonly holds several blocks at different heights (the\n * amphitheatre gallery is one section holding six wedges). Any field that is\n * level on each row must JUMP between blocks. Measured, that jump drove the\n * cap's worst error to 11 m against a 0.15 m bound.\n * - Smoothing the jump away puts the tilt straight back. The two requirements\n * are contradictory, so no amount of refinement resolves it.\n *\n * The jump is not an artefact — it is a step between two blocks, which is a\n * WALL. Walls are geometry. So the deck is emitted as geometry that already has\n * the right shape rather than as a surface something has to approximate:\n *\n * - one **ribbon** per row, horizontal, at that row's level;\n * - a **riser** dropping from each ribbon's front edge to the level below;\n * - a flat **landing** under everything, for the parts of the outline that hold\n * no rows (aisles, margins, the gaps between blocks).\n *\n * Every deck vertex therefore lies on a row, where the height is exactly defined.\n * A seat's clearance above its own ribbon is exact by construction instead of\n * measured against a tessellation error, and because every ribbon is horizontal\n * the whole deck shades with a single up normal.\n */\n\nimport type { Point } from '../../core/types';\nimport type { RGB } from '../palette';\nimport polygonClipping from 'polygon-clipping';\nimport { CHART_UNITS_PER_METRE } from '../../core/units';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport earcut from 'earcut';\nimport { MeshBuilder, M } from './geometry';\n\n/** A row ready to draw: its seats as a polyline, and the level it sits at. */\nexport interface BandRow {\n readonly pts: readonly Point[];\n /** World-metre height of this row's ribbon. */\n readonly y: number;\n /**\n * The row's depth ordinate, chart units — its MEAN distance from the focal.\n *\n * Carried through rather than recomputed here. Deriving it from the row's first\n * seat was tried and buried 20 seats: in a section holding several blocks, one\n * row's first seat and the next row's first seat can be in different blocks\n * entirely, so the implied row gap was wrong by a large factor and a ribbon\n * reached forward far enough to cover the row in front of it.\n */\n readonly depth: number;\n /**\n * Which block (stand) this row belongs to.\n *\n * Neighbour and riser resolution must stay inside a block. Across blocks the\n * nearest row is often the one across an aisle, at a different level, so a\n * ribbon would size itself against a stand it is not part of and a riser would\n * step down onto the wrong deck.\n */\n readonly blockId: number;\n /**\n * A ready-made deck FOOTPRINT for this entry, chart units, convex and closed —\n * used instead of the row-ribbon path when the entry is not a row at all.\n *\n * A table is the case this exists for. Its seats ring a centre rather than\n * running across the view, so \"offset the polyline sideways\" describes nothing:\n * whichever way the ribbon is offset it covers an arc of the ring and leaves\n * the rest of it hanging. Measured on the nightclub VIP terrace (14 six-seat\n * round tables), authoring any rake at all left 5 seats off-deck at 1°, 18 at\n * 5° and 24 at 10°. The count barely moves with the rake because the fault is\n * in the deck's PLAN SHAPE, not in its height — a taller step just exposes the\n * ground that was already missing.\n *\n * The patch is the convex hull of the entry's seats grown outward by the same\n * clearance a ribbon carries ({@link MIN_REACH_U}), so a table's deck reaches\n * past its outermost seat CENTRE by a full seat-dot allowance exactly as a\n * ribbon does past its end seat. It is supplied by the surface builder, which\n * is where the authored objects are known — deriving \"is this a table\" by\n * guessing at seat geometry here would re-answer a question the chart already\n * answers.\n */\n readonly patch?: readonly Point[];\n}\n\n/**\n * How far a ribbon reaches FORWARD, as a fraction of the gap to the row in front.\n *\n * Deliberately past the halfway line. A ribbon is built by offsetting its own\n * row's polyline, and the ribbon in front is built by offsetting a different\n * polyline with a different vertex count, so their edges do not land on exactly\n * the same curve. Meeting them at 0.5 each would leave hairline gaps showing the\n * landing through the deck. Overlapping instead is free: the ribbon behind is\n * HIGHER, so it simply covers the seam, and the riser closes the vertical face.\n */\nconst FRONT_REACH = 0.58;\n\n/**\n * Smallest step worth drawing a riser for, world metres.\n *\n * A 1e-4 threshold was tried and emitted 142 needle triangles with aspect ratios\n * to 17,776: risers 5 m long and 0.3 mm tall, where two rows resolved to almost\n * the same level. Such a step is invisible, and the ribbons already overlap\n * enough to close the seam without it. 1 cm is below anything a viewer can see\n * and far above the degenerate range.\n */\nconst MIN_RISER_M = 0.01;\n\n/**\n * Smallest half-width a ribbon may have, chart units.\n *\n * A seat's dot has a real world radius, so a ribbon narrower than that leaves the\n * dot's rim hanging over the aisle beside it — the same defect the deck padding\n * (`outsetRing`) exists to prevent on the cap path, and it is the same 1.5x\n * margin here. Two orchestra seats were still failing on ribbon width alone\n * before this floor; row pitch happened to be barely over a dot diameter there.\n */\nexport const MIN_REACH_U = SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE;\n\n/** How far a ribbon reaches BACK, as a fraction of the gap to the row behind. */\nconst BACK_REACH = 0.5;\n\n/**\n * How far a ribbon extends past its end seats, as a fraction of the SEAT spacing\n * along the row — not of the row gap.\n *\n * The row gap was tried and buried 23 seats. A section can hold several blocks\n * side by side (the amphitheatre gallery holds six), and the aisle between two\n * blocks is often narrower than a row gap, so a ribbon extended by a row gap\n * reached over its neighbour's seats — which sit at a DIFFERENT level, so the\n * overhanging ribbon painted over them. Seat spacing is the right measure: it is\n * exactly enough to carry the end seat's own dot and cannot cross an aisle.\n */\nconst END_REACH = 0.6;\n\n/**\n * Outward normals along a polyline, pointing in the direction of increasing\n * depth (away from `focal`).\n *\n * Taken from the row's own local direction rather than from the height field's\n * gradient: the field is a step function now, so its gradient is zero almost\n * everywhere and undefined on the steps.\n */\nfunction rowNormals(pts: readonly Point[], focal: Point): Array<readonly [number, number]> {\n const n = pts.length;\n const raw: Array<readonly [number, number]> = [];\n for (let i = 0; i < n; i++) {\n // Central difference gives a smooth normal along a curved row; the ends fall\n // back to their single adjacent segment.\n const a = pts[Math.max(0, i - 1)];\n const b = pts[Math.min(n - 1, i + 1)];\n let dx = b.x - a.x, dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-9) { raw.push([0, 0]); continue; }\n dx /= len; dy /= len;\n raw.push([-dy, dx]);\n }\n\n // Orient the WHOLE ROW at once, from the sum of the per-vertex tests, rather\n // than flipping each vertex on its own.\n //\n // Per-vertex orientation was tried and left a hole in the deck. The test is\n // \"does this normal point away from the focal\", and its value passes through\n // zero when a row runs radially — straight at the stage rather than across it.\n // The amphitheatre orchestra has such rows, and there the sign flipped between\n // two adjacent vertices, so the quad between them was built with its two ends\n // offset in OPPOSITE directions: a bow-tie that covers neither side properly.\n // One seat's dot rim ended up over the void.\n //\n // A row is a single object with one front and one back, so the decision belongs\n // to the row. Summing is also the stable form of the same test: it is dominated\n // by the vertices where the answer is unambiguous.\n let vote = 0;\n for (let i = 0; i < n; i++) {\n vote += (pts[i].x - focal.x) * raw[i][0] + (pts[i].y - focal.y) * raw[i][1];\n }\n const flip = vote < 0;\n return flip ? raw.map(([x, y]) => [-x, -y] as const) : raw;\n}\n\n/** Extend a polyline past both ends along its end tangents by `by` chart units. */\nfunction extendEnds(pts: readonly Point[], by: number): Point[] {\n if (pts.length < 2 || by <= 0) return [...pts];\n const out = [...pts];\n const dir = (p: Point, q: Point): Point => {\n const dx = q.x - p.x, dy = q.y - p.y;\n const len = Math.hypot(dx, dy) || 1;\n return { x: dx / len, y: dy / len };\n };\n const head = dir(out[1], out[0]);\n const tail = dir(out[out.length - 2], out[out.length - 1]);\n out.unshift({ x: out[0].x + head.x * by, y: out[0].y + head.y * by });\n out.push({\n x: out[out.length - 1].x + tail.x * by,\n y: out[out.length - 1].y + tail.y * by,\n });\n return out;\n}\n\n/**\n * Angular step used when rounding a patch's corners, radians (~15°).\n *\n * The offset of a convex hull by a fixed distance is that hull swept by a DISC,\n * so every corner is a circular arc. Sampling it is the only approximation in\n * the patch, and it is taken on the OUTSIDE (see `CORNER_SEGMENTS` below) so the\n * sampled polygon still contains the true offset rather than cutting inside it.\n */\nconst CORNER_STEP_RAD = Math.PI / 12;\n\n/**\n * Convex hull of a point set (Andrew's monotone chain), counter-clockwise.\n *\n * Returns fewer than 3 points when the input is a point or a segment; callers\n * fall back to a circle there, which is the correct offset of both.\n */\nfunction convexHull(pts: readonly Point[]): Point[] {\n if (pts.length < 3) return [...pts];\n const s = [...pts].sort((a, b) => (a.x - b.x) || (a.y - b.y));\n const cross = (o: Point, a: Point, b: Point): number =>\n (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);\n const half = (src: readonly Point[]): Point[] => {\n const out: Point[] = [];\n for (const p of src) {\n while (out.length >= 2 && cross(out[out.length - 2], out[out.length - 1], p) <= 0) out.pop();\n out.push(p);\n }\n out.pop();\n return out;\n };\n const hull = [...half(s), ...half([...s].reverse())];\n return hull.length >= 3 ? hull : [...pts];\n}\n\n/**\n * The deck footprint a cluster of seats needs: their convex hull grown outward\n * by `pad` chart units, as a closed counter-clockwise ring.\n *\n * Exact rather than approximate, and that matters: the property the caller is\n * buying is \"every seat centre lies at least `pad` inside this ring\", which is\n * what makes a table's deck carry its own seat dots. A hull offset is the\n * Minkowski sum of the hull with a disc of radius `pad`, and that sum contains\n * every seat's own disc by construction. Sampling the corner arcs is the one\n * place accuracy could leak, so the samples are placed on the CIRCUMSCRIBED\n * radius — the sampled polygon then contains the true arc instead of chording\n * across it, and the guarantee survives the discretisation.\n */\nexport function seatClusterPatch(pts: readonly Point[], pad: number): Point[] {\n if (!pts.length || !(pad > 0)) return [];\n const hull = convexHull(pts);\n // Round a corner from one outward normal to the next, on the circumscribed\n // radius so the chord between samples still clears `pad`.\n const arc = (v: Point, from: number, to: number, out: Point[]): void => {\n let sweep = to - from;\n while (sweep < 0) sweep += Math.PI * 2;\n while (sweep > Math.PI * 2) sweep -= Math.PI * 2;\n const steps = Math.max(1, Math.ceil(sweep / CORNER_STEP_RAD));\n const r = pad / Math.cos(sweep / steps / 2);\n for (let k = 0; k <= steps; k++) {\n const a = from + (sweep * k) / steps;\n out.push({ x: v.x + Math.cos(a) * r, y: v.y + Math.sin(a) * r });\n }\n };\n\n if (hull.length < 3) {\n // A single seat, or two seats (a collinear \"hull\"). The bounding circle grown\n // by `pad` covers the true offset in both cases — it over-covers a segment\n // slightly, which is harmless at the scale of one or two chairs and avoids a\n // second geometric path for a case a real table never hits.\n let cx = 0, cy = 0;\n for (const p of hull) { cx += p.x; cy += p.y; }\n cx /= hull.length || 1; cy /= hull.length || 1;\n let far = 0;\n for (const p of hull) far = Math.max(far, Math.hypot(p.x - cx, p.y - cy));\n const out: Point[] = [];\n const steps = Math.max(3, Math.ceil((Math.PI * 2) / CORNER_STEP_RAD));\n const r = (far + pad) / Math.cos(Math.PI / steps);\n for (let k = 0; k < steps; k++) {\n const a = (k / steps) * Math.PI * 2;\n out.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r });\n }\n return out;\n }\n\n const n = hull.length;\n // Outward normal of the edge leaving vertex i, for a CCW hull.\n const edgeAngle: number[] = [];\n for (let i = 0; i < n; i++) {\n const a = hull[i], b = hull[(i + 1) % n];\n edgeAngle.push(Math.atan2(-(b.x - a.x), b.y - a.y));\n }\n const out: Point[] = [];\n for (let i = 0; i < n; i++) {\n // At each vertex sweep from the previous edge's normal to this edge's, then\n // walk this edge offset outward. Consecutive vertices' runs join along the\n // shared edge, so the concatenation closes into one ring.\n arc(hull[i], edgeAngle[(i + n - 1) % n], edgeAngle[i], out);\n }\n return dedupeAdjacent(out);\n}\n\n/** Perpendicular distance from a point to a row's polyline, chart units. */\nfunction distToPolyline(pts: readonly Point[], x: number, y: number): number {\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** A row's SPATIAL neighbours: how wide its ribbon is, and what it steps down to. */\ninterface Neighbourhood {\n /** Perpendicular distance to the nearest other row, chart units. */\n pitch: number;\n /** World-metre level of the nearest row in FRONT, or null if this row is first. */\n belowY: number | null;\n}\n\n/**\n * Resolve each row's neighbours by POSITION, not by order in the array.\n *\n * `rows` arrives sorted by depth across the whole section, and a section\n * routinely holds several blocks. Two consecutive entries in that ordering are\n * usually the same row index in two DIFFERENT blocks, at almost the same depth —\n * so treating them as neighbours gave a row pitch of nearly zero. Measured, that\n * produced ribbons a couple of centimetres wide (so seat rims hung off the deck\n * in 394 of 400 samples) and risers 5 m long by 0.3 mm tall.\n *\n * The spatially nearest row is the real neighbour whatever block it is in, and\n * the nearest row that is closer to the focal is the one this ribbon steps down\n * onto.\n */\nexport function rowNeighbourhoods(rows: readonly BandRow[]): Neighbourhood[] {\n // Bounding circle per row, so the exact polyline test is skipped for rows that\n // cannot possibly be nearest. This is O(rows^2) at heart and a big venue has\n // thousands of rows in a section.\n const bounds = rows.map((r) => {\n let cx = 0, cy = 0;\n for (const p of r.pts) { cx += p.x; cy += p.y; }\n const n = r.pts.length || 1;\n cx /= n; cy /= n;\n let rad = 0;\n for (const p of r.pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > rad) rad = d;\n }\n return { cx, cy, rad };\n });\n // Probe from points ON the row, at a quarter, a half and three quarters along\n // it. The centroid was tried and broke the arena: its rows are strong arcs, and\n // an arc's centroid lies well inside the curve rather than on it, so distances\n // measured from there are not the row pitch at all (arena rims went 0 -> 272\n // off deck). A point on the polyline measures the true perpendicular gap, and\n // three of them keep one odd row end from setting the whole ribbon's width.\n const probesOf = (pts: readonly Point[]): Point[] => {\n const n = pts.length;\n if (n <= 2) return [...pts];\n return [pts[Math.floor(n * 0.25)], pts[Math.floor(n * 0.5)], pts[Math.floor(n * 0.75)]];\n };\n\n const out: Neighbourhood[] = [];\n for (let i = 0; i < rows.length; i++) {\n const probes = probesOf(rows[i].pts);\n const pitches: number[] = [];\n let bestFrontD = Infinity, belowY: number | null = null;\n for (const c of probes) {\n let nearest = Infinity;\n for (let j = 0; j < rows.length; j++) {\n // Neighbours are rows at a DIFFERENT LEVEL, not rows in the same block.\n //\n // \"Same block\" was tried and broke the concert hall: block detection\n // legitimately fragments one stand into several (sec-terr-0's 12 rows\n // resolved into 4), and a fragment's front neighbour then sits in another\n // block and was skipped — so the pitch came from a distant row, the\n // ribbon reached far forward, and it buried the seats of the row in front\n // by 0.20 m.\n //\n // Level is the property that actually distinguishes the two cases: a\n // lateral neighbour across an aisle shares this row's level (it is the\n // same row, split), while the row in front or behind does not.\n if (j === i || Math.abs(rows[j].y - rows[i].y) < 1e-3) continue;\n // Admissible lower bound: the true distance can never be smaller than\n // this, so a row that cannot beat EITHER accumulator can be skipped\n // outright. Guarding on only one of them left the prune almost never\n // firing, which is the whole cost of this function.\n const b = bounds[j];\n const lower = Math.hypot(c.x - b.cx, c.y - b.cy) - b.rad;\n if (lower >= nearest && lower >= bestFrontD) continue;\n const d = distToPolyline(rows[j].pts, c.x, c.y);\n if (d < nearest) nearest = d;\n // \"In front\" = nearer the focal, i.e. a smaller depth ordinate.\n if (rows[j].depth < rows[i].depth && d < bestFrontD) {\n bestFrontD = d;\n belowY = rows[j].y;\n }\n }\n if (Number.isFinite(nearest) && nearest > 0) pitches.push(nearest);\n }\n pitches.sort((a, b) => a - b);\n // Median: robust to one probe landing beside an aisle or a short row.\n const pitch = pitches.length ? pitches[Math.floor(pitches.length / 2)] : 1;\n out.push({ pitch, belowY });\n }\n return out;\n}\n\nexport interface BandColors {\n /** Ribbon (tread) top colour, AO already applied. */\n tread: RGB;\n /** Riser face colour, AO already applied. */\n riser: RGB;\n}\n\n\n/** One row's ribbon: the polyline it is built on, its normals, and its reaches. */\ninterface Ribbon {\n pts: Point[];\n nrm: Array<readonly [number, number]>;\n front: number;\n back: number;\n}\n\n/**\n * Resolve one row's ribbon geometry.\n *\n * Shared by the mesh emitter and the footprint builder so the drawn ribbons and\n * the block outline extruded beneath them are derived from ONE computation. They\n * disagreeing is the same class of bug as the cap and the seats disagreeing.\n */\nfunction ribbonOf(rows: readonly BandRow[], i: number, nbrs: Neighbourhood[], focal: Point): Ribbon | null {\n const row = rows[i];\n // A \"row\" of one seat is a free-standing seat, not a row — it still needs a\n // deck under it, so give it a short segment across the view direction and let\n // the normal ribbon path carry it. Skipping these left their dots hanging\n // over the landing.\n const rowPts = row.pts.length >= 2\n ? [...row.pts]\n : row.pts.length === 1\n ? ((): Point[] => {\n const p = row.pts[0];\n let dx = p.x - focal.x, dy = p.y - focal.y;\n const len = Math.hypot(dx, dy) || 1;\n dx /= len; dy /= len;\n // Across the line of sight, half a pitch each way.\n const h = Math.max(nbrs[i].pitch, 1e-3) * 0.5;\n return [{ x: p.x + dy * h, y: p.y - dx * h }, { x: p.x - dy * h, y: p.y + dx * h }];\n })()\n : [];\n if (rowPts.length < 2) return null;\n\n let seatSpan = 0, spanN = 0;\n for (let k = 1; k < rowPts.length; k++) {\n const d = Math.hypot(rowPts[k].x - rowPts[k - 1].x, rowPts[k].y - rowPts[k - 1].y);\n if (d > 1e-6) { seatSpan += d; spanN++; }\n }\n const spacing = spanN > 0 ? seatSpan / spanN : 0;\n const pts = extendEnds(rowPts, Math.max(spacing * END_REACH, MIN_REACH_U));\n return {\n pts,\n nrm: rowNormals(pts, focal),\n front: Math.max(nbrs[i].pitch * FRONT_REACH, MIN_REACH_U),\n back: Math.max(nbrs[i].pitch * BACK_REACH, MIN_REACH_U),\n };\n}\n\n/** A block: the footprint its ribbons cover, and the level its base sits at. */\nexport type { Neighbourhood };\n\nexport interface DeckFootprint {\n /** Outer ring, chart units. */\n outline: Point[];\n /** Any interior rings (an enclosed gap between rows). */\n holes: Point[][];\n /** World-metre height of this block's lowest ribbon — the top of its base. */\n topY: number;\n}\n\n/**\n * The footprint of each BLOCK in a section, as the union of its ribbons.\n *\n * This replaces extruding the section's whole authored outline under the deck.\n * That outline is drawn generously — it reaches past the last row and across the\n * gaps between blocks — so a single flat plate at the front row's level stuck out\n * around the seating as a large slab, and every block's end read as a square cut\n * through that slab rather than as the edge of a stand.\n *\n * Unioning the ribbons instead yields one polygon per block automatically (the\n * amphitheatre's three sections resolve to 4, 5 and 6 blocks — exactly their\n * wedge counts), each hugging its own seating, and each carrying its own base\n * level rather than sharing the section's. Costs 7–13 ms per section at build\n * time, which is paid once.\n */\n/**\n * Extra width given to a ribbon in the block footprint, chart units (~3 cm).\n *\n * The footprint is the base the ribbons stand on, so a small margin keeps its\n * edge from coinciding exactly with a tread edge and z-fighting it.\n */\nconst FOOTPRINT_MARGIN_U = 1.5;\n\n/**\n * Douglas-Peucker tolerance for a block outline, chart units (~7 mm).\n *\n * The base carries no detail at this scale — the SEATS are carried by the\n * ribbons above it — and long runs of near-collinear vertices are what earcut\n * fans into needle triangles.\n */\nconst FOOTPRINT_TOLERANCE_U = 0.3;\n\n/** Douglas-Peucker on an open point run. */\nfunction simplifyRun(pts: Point[], tol: number): Point[] {\n if (pts.length < 3) return pts;\n const a = pts[0], b = pts[pts.length - 1];\n const dx = b.x - a.x, dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n let worst = -1, worstI = -1;\n for (let i = 1; i < pts.length - 1; i++) {\n const p = pts[i];\n const d = len > 1e-12\n ? Math.abs((p.x - a.x) * dy - (p.y - a.y) * dx) / len\n : Math.hypot(p.x - a.x, p.y - a.y);\n if (d > worst) { worst = d; worstI = i; }\n }\n if (worst <= tol || worstI < 0) return [a, b];\n const left = simplifyRun(pts.slice(0, worstI + 1), tol);\n const right = simplifyRun(pts.slice(worstI), tol);\n return [...left.slice(0, -1), ...right];\n}\n\n/** Simplify a closed ring, keeping it closed. */\nfunction simplifyRing(ring: Point[], tol: number): Point[] {\n if (ring.length < 4) return ring;\n const out = simplifyRun([...ring, ring[0]], tol);\n out.pop();\n return out.length >= 3 ? out : ring;\n}\n\nexport function deckFootprints(rows: readonly BandRow[], focal: Point, shared?: Neighbourhood[]): DeckFootprint[] {\n if (rows.length < 2) return [];\n const nbrs = shared ?? rowNeighbourhoods(rows);\n\n // Group by the block the structure resolver already identified. The first\n // implementation instead recovered blocks by UNIONING every ribbon and reading\n // off the resulting polygons — correct, but it made a boolean solve the\n // question a resolved field already answers, and ribbons overlap each other by\n // design (FRONT_REACH is past the halfway line), which is the worst case for a\n // sweep-line union. Profiled at 48 % of scene build on a 50k venue and 58 % on\n // 99k, growing superlinearly: 1,024 ms -> 5,285 ms for twice the seats.\n const byBlock = new Map<number, number[]>();\n for (let i = 0; i < rows.length; i++) {\n const a = byBlock.get(rows[i].blockId);\n if (a) a.push(i); else byBlock.set(rows[i].blockId, [i]);\n }\n\n const out: DeckFootprint[] = [];\n for (const [, allIndices] of byBlock) {\n // A patch entry (a table) stands on its own base. It is not part of the\n // ribbon stack — including it in the front-to-back trace would drag the\n // block's outline through a ring of seats that has no front or back — and a\n // pedestal per table is what a raked table terrace physically is.\n const indices: number[] = [];\n for (const i of allIndices) {\n const patch = rows[i].patch;\n if (patch && patch.length >= 3) out.push({ outline: [...patch], holes: [], topY: rows[i].y });\n else indices.push(i);\n }\n if (!indices.length) continue;\n // Front to back, so the ring below traces the block's real outline.\n indices.sort((a, b) => rows[a].depth - rows[b].depth);\n const ribbons = indices.map((i) => ribbonOf(rows, i, nbrs, focal));\n const usable = ribbons.filter((r): r is Ribbon => r !== null);\n if (!usable.length) continue;\n\n const frontOf = (r: Ribbon, k: number): Point => ({\n x: r.pts[k].x - r.nrm[k][0] * (r.front + FOOTPRINT_MARGIN_U),\n y: r.pts[k].y - r.nrm[k][1] * (r.front + FOOTPRINT_MARGIN_U),\n });\n const backOf = (r: Ribbon, k: number): Point => ({\n x: r.pts[k].x + r.nrm[k][0] * (r.back + FOOTPRINT_MARGIN_U),\n y: r.pts[k].y + r.nrm[k][1] * (r.back + FOOTPRINT_MARGIN_U),\n });\n\n // Trace the boundary directly: the front row's front edge, down the right\n // ends, the back row's back edge reversed, and up the left ends. O(rows)\n // instead of a boolean over every ribbon.\n const first = usable[0], last = usable[usable.length - 1];\n const ring: Point[] = [];\n for (let k = 0; k < first.pts.length; k++) ring.push(frontOf(first, k));\n for (const r of usable) ring.push(backOf(r, r.pts.length - 1));\n for (let k = last.pts.length - 1; k >= 0; k--) ring.push(backOf(last, k));\n for (let i = usable.length - 1; i >= 0; i--) ring.push(frontOf(usable[i], 0));\n\n const deduped = dedupeAdjacent(ring);\n let topY = Infinity;\n for (const i of indices) if (rows[i].y < topY) topY = rows[i].y;\n if (!Number.isFinite(topY)) continue;\n\n // VERIFY the traced ring before trusting it. Tracing assumes a block's rows\n // stack cleanly front to back; a block that folds or wraps can trace a\n // self-intersecting ring, which still has positive area but covers the wrong\n // ground — on the West End theatre that left seats standing over a base that\n // had moved out from under them. The property that matters is that the base\n // covers its own ribbons, so test exactly that and fall back to the boolean\n // union for the rare block that fails.\n const covers = deduped.length >= 3\n && Math.abs(ringArea(deduped)) > 1e-6\n && usable.every((r) => {\n const mid = r.pts[Math.floor(r.pts.length / 2)];\n return pointInRing(deduped, mid.x, mid.y);\n });\n\n if (covers) {\n out.push({ outline: simplifyRing(deduped, FOOTPRINT_TOLERANCE_U), holes: [], topY });\n continue;\n }\n for (const poly of unionRibbons(usable)) out.push({ outline: poly, holes: [], topY });\n }\n return out;\n}\n\n/**\n * The original boolean union, kept as the fallback for blocks the direct trace\n * cannot describe. Correct but expensive, so it now runs for a handful of blocks\n * rather than for every one.\n */\nfunction unionRibbons(ribbons: readonly Ribbon[]): Point[][] {\n const rings: Array<[number, number][][]> = [];\n for (const r of ribbons) {\n const f: [number, number][] = [];\n const b: [number, number][] = [];\n const rf = r.front + FOOTPRINT_MARGIN_U;\n const rb = r.back + FOOTPRINT_MARGIN_U;\n for (let k = 0; k < r.pts.length; k++) {\n const p = r.pts[k], n = r.nrm[k];\n f.push([p.x - n[0] * rf, p.y - n[1] * rf]);\n b.push([p.x + n[0] * rb, p.y + n[1] * rb]);\n }\n const ring = [...f, ...b.reverse()];\n if (ring.length < 3) continue;\n ring.push(ring[0]);\n rings.push([ring]);\n }\n if (!rings.length) return [];\n try {\n const merged = polygonClipping.union(rings[0], ...rings.slice(1));\n const out: Point[][] = [];\n for (const poly of merged) {\n if (!poly.length || poly[0].length < 4) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n const first = pts[0], last = pts[pts.length - 1];\n if (pts.length > 1 && Math.abs(first.x - last.x) < 1e-9 && Math.abs(first.y - last.y) < 1e-9) pts.pop();\n if (pts.length >= 3) out.push(simplifyRing(pts, FOOTPRINT_TOLERANCE_U));\n }\n return out;\n } catch {\n return [];\n }\n}\n\n/** Shoelace area of a ring. */\nfunction ringArea(ring: readonly Point[]): number {\n let a = 0;\n for (let i = 0, n = ring.length; i < n; i++) {\n const p = ring[i], q = ring[(i + 1) % n];\n a += p.x * q.y - q.x * p.y;\n }\n return a / 2;\n}\n\n/** Drop consecutive duplicates, which the traced ring produces at its corners. */\nfunction dedupeAdjacent(ring: readonly Point[]): Point[] {\n const out: Point[] = [];\n for (const p of ring) {\n const last = out[out.length - 1];\n if (last && Math.hypot(p.x - last.x, p.y - last.y) < 1e-9) continue;\n out.push(p);\n }\n while (out.length > 2 && Math.hypot(out[0].x - out[out.length - 1].x, out[0].y - out[out.length - 1].y) < 1e-9) out.pop();\n return out;\n}\n\n/** Even-odd point-in-ring test. */\nfunction pointInRing(ring: readonly Point[], x: number, y: number): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const a = ring[i], b = ring[j];\n if ((a.y > y) !== (b.y > y) && x < ((b.x - a.x) * (y - a.y)) / (b.y - a.y) + a.x) inside = !inside;\n }\n return inside;\n}\n\n/**\n * Cheap containment test for the clip region, so the boolean is only run for the\n * quads that actually straddle a boundary.\n */\nclass ClipTest {\n private rings: Point[][];\n private minX = Infinity; private minY = Infinity;\n private maxX = -Infinity; private maxY = -Infinity;\n\n constructor(rings: Point[][]) {\n this.rings = rings;\n for (const r of rings) {\n for (const p of r) {\n if (p.x < this.minX) this.minX = p.x;\n if (p.y < this.minY) this.minY = p.y;\n if (p.x > this.maxX) this.maxX = p.x;\n if (p.y > this.maxY) this.maxY = p.y;\n }\n }\n }\n\n /** True when every point lies strictly inside ONE ring of the clip region. */\n containsAll(pts: readonly Point[]): boolean {\n for (const p of pts) {\n if (p.x < this.minX || p.x > this.maxX || p.y < this.minY || p.y > this.maxY) return false;\n }\n // A quad spanning two disjoint rings must go through the boolean, so the\n // test demands a SINGLE ring contain all of it.\n for (const ring of this.rings) {\n let all = true;\n for (const p of pts) {\n if (!pointInRing(ring, p.x, p.y)) { all = false; break; }\n }\n if (all) return true;\n }\n return false;\n }\n}\n\n/**\n * Emit a horizontal CONVEX polygon clipped to `clipRing`, triangulated.\n *\n * Treads are horizontal, so every piece shades with the same up normal whatever\n * shape the clip leaves behind — which is what makes clipping cheap here.\n *\n * Convex is all the callers produce: a ribbon segment is a quad and a table\n * patch is an offset convex hull, so the unclipped fast path can fan from one\n * vertex instead of running a triangulator.\n */\nfunction emitClippedPoly(\n builder: MeshBuilder,\n clipRing: [number, number][][],\n clipTest: ClipTest,\n poly: Point[],\n y: number,\n color: RGB,\n): void {\n if (poly.length < 3) return;\n // FAST PATH: a polygon wholly inside the clip contributes nothing to clip, and\n // the overwhelming majority are — only ribbons at a section's edge straddle it.\n // Running a polygon boolean per ribbon SEGMENT regardless cost ~100,000\n // intersections on a 99k-seat venue and dominated scene build (8.5 s of 11 s).\n if (clipTest.containsAll(poly)) {\n const UPF = [0, 1, 0] as const;\n const a = poly[0];\n for (let i = 1; i + 1 < poly.length; i++) {\n const b = poly[i], c = poly[i + 1];\n builder.tri([a.x * M, y, a.y * M], [b.x * M, y, b.y * M], [c.x * M, y, c.y * M], UPF, color);\n }\n return;\n }\n const ring: [number, number][] = poly.map((p) => [p.x, p.y]);\n ring.push(ring[0]);\n let pieces: ReturnType<typeof polygonClipping.intersection>;\n try {\n pieces = polygonClipping.intersection([ring], clipRing);\n } catch {\n return; // a degenerate quad simply contributes nothing\n }\n const UP = [0, 1, 0] as const;\n for (const poly of pieces) {\n if (!poly.length || poly[0].length < 4) continue;\n const outer = poly[0];\n const flat: number[] = [];\n const pts: Array<[number, number]> = [];\n for (let i = 0; i < outer.length - 1; i++) { // drop the repeated closing point\n flat.push(outer[i][0], outer[i][1]);\n pts.push([outer[i][0], outer[i][1]]);\n }\n if (pts.length < 3) continue;\n const tris = earcut(flat, undefined, 2);\n for (let i = 0; i < tris.length; i += 3) {\n const a = pts[tris[i]], b = pts[tris[i + 1]], c = pts[tris[i + 2]];\n builder.tri(\n [a[0] * M, y, a[1] * M],\n [b[0] * M, y, b[1] * M],\n [c[0] * M, y, c[1] * M],\n UP, color,\n );\n }\n }\n}\n\n/**\n * Emit one section's ribbons and risers.\n *\n * `rows` must be ordered front first (nearest the focal), which is the contract\n * `SectionRake.rows` provides.\n */\nexport function emitDeckBands(\n builder: MeshBuilder,\n rows: readonly BandRow[],\n focal: Point,\n landingY: number,\n colors: BandColors,\n clip?: readonly Point[],\n shared?: Neighbourhood[],\n): void {\n if (rows.length < 2) return;\n // Reused from the footprint pass when the caller has one: this is the O(rows^2)\n // step and computing it twice per section doubled it for nothing.\n const nbrs = shared ?? rowNeighbourhoods(rows);\n const UP = [0, 1, 0] as const;\n const clipRing: [number, number][][] | null = clip && clip.length >= 3\n ? [[...clip.map((p) => [p.x, p.y] as [number, number]), [clip[0].x, clip[0].y]]]\n : null;\n const clipTest = clip && clip.length >= 3 ? new ClipTest([[...clip]]) : null;\n // Row treads/risers are useful close-range structure, but their overlapping\n // centimetre-scale surfaces alias into a black radial comb at whole-venue\n // scale. Stamp them separately so the renderer can use the clean block cap in\n // overview, then restore the true stepped surface inside a section.\n const emitRiser = (draw: () => void): void => {\n builder.setMaterial(2);\n draw();\n builder.setMaterial(0);\n };\n const emitTread = (draw: () => void): void => {\n builder.setMaterial(3);\n draw();\n builder.setMaterial(0);\n };\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n\n // A table: draw its own footprint, not a ribbon. See `BandRow.patch`.\n if (row.patch && row.patch.length >= 3) {\n const patch = [...row.patch];\n const belowY = nbrs[i].belowY ?? landingY;\n emitTread(() => {\n if (clipRing && clipTest) emitClippedPoly(builder, clipRing, clipTest, patch, row.y, colors.tread);\n else {\n const a = patch[0];\n for (let k = 1; k + 1 < patch.length; k++) {\n const b = patch[k], c = patch[k + 1];\n builder.tri([a.x * M, row.y, a.y * M], [b.x * M, row.y, b.y * M], [c.x * M, row.y, c.y * M], UP, colors.tread);\n }\n }\n });\n // A skirt all the way round, not just across the front. A patch is a\n // free-standing pedestal rather than a step in a stack, so every side of\n // it is exposed; leaving the sides open showed the void under the deck\n // from any angle but dead ahead.\n if (row.y > belowY + MIN_RISER_M) {\n let cx = 0, cy = 0;\n for (const p of patch) { cx += p.x; cy += p.y; }\n cx /= patch.length; cy /= patch.length;\n emitRiser(() => {\n for (let k = 0; k < patch.length; k++) {\n const p = patch[k], q = patch[(k + 1) % patch.length];\n const dx = q.x - p.x, dy = q.y - p.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-6) continue;\n // Outward = the side of the edge the patch centre is NOT on.\n let nx = dy / len, ny = -dx / len;\n if ((p.x - cx) * nx + (p.y - cy) * ny < 0) { nx = -nx; ny = -ny; }\n const rn: readonly [number, number, number] = [nx, 0, ny];\n const pt: [number, number, number] = [p.x * M, row.y, p.y * M];\n const qt: [number, number, number] = [q.x * M, row.y, q.y * M];\n const pb: [number, number, number] = [p.x * M, belowY, p.y * M];\n const qb: [number, number, number] = [q.x * M, belowY, q.y * M];\n builder.tri(pt, qt, qb, rn, colors.riser);\n builder.tri(pt, qb, pb, rn, colors.riser);\n }\n });\n }\n continue;\n }\n\n const rib = ribbonOf(rows, i, nbrs, focal);\n if (!rib) continue;\n const { pts, nrm, front, back } = rib;\n\n // The level below this ribbon: the spatially nearest row in front of it, or\n // the landing when nothing is in front (this row is its block's first).\n const belowY = nbrs[i].belowY ?? landingY;\n\n for (let k = 0; k + 1 < pts.length; k++) {\n const p = pts[k], q = pts[k + 1];\n const np = nrm[k], nq = nrm[k + 1];\n // Skip degenerate input: a duplicated seat position, or a vertex whose\n // normal collapsed. Emitting these produced needle triangles with aspect\n // ratios to 17,776 — meaningless geometry that flat-shades as dark streaks,\n // the exact defect the ring cleanup removed from the cap path.\n if (Math.hypot(q.x - p.x, q.y - p.y) < 1e-6) continue;\n if ((np[0] === 0 && np[1] === 0) || (nq[0] === 0 && nq[1] === 0)) continue;\n\n const pF: [number, number, number] = [(p.x - np[0] * front) * M, row.y, (p.y - np[1] * front) * M];\n const qF: [number, number, number] = [(q.x - nq[0] * front) * M, row.y, (q.y - nq[1] * front) * M];\n const pB: [number, number, number] = [(p.x + np[0] * back) * M, row.y, (p.y + np[1] * back) * M];\n const qB: [number, number, number] = [(q.x + nq[0] * back) * M, row.y, (q.y + nq[1] * back) * M];\n\n // Tread: horizontal, so one shared up normal is exact.\n emitTread(() => {\n if (clipRing && clipTest) {\n // A ribbon reaches past its own row on all four sides, and near a section\n // boundary that reach crosses into the NEXT section. Where the neighbour\n // sits lower, the overhang covers its seats: 27 concert-hall seats were\n // buried by 0.20 m under the terrace beside them, which is 0.60 m higher.\n // Clipping to the section's own padded outline keeps a deck inside the\n // section that drew it.\n emitClippedPoly(builder, clipRing, clipTest!, [\n { x: p.x - np[0] * front, y: p.y - np[1] * front },\n { x: q.x - nq[0] * front, y: q.y - nq[1] * front },\n { x: q.x + nq[0] * back, y: q.y + nq[1] * back },\n { x: p.x + np[0] * back, y: p.y + np[1] * back },\n ], row.y, colors.tread);\n } else {\n builder.tri(pF, qF, qB, UP, colors.tread);\n builder.tri(pF, qB, pB, UP, colors.tread);\n }\n });\n\n // Riser: the vertical face under the ribbon's front edge. Skipped when it\n // would be inverted or degenerate (a level or descending step).\n if (row.y > belowY + MIN_RISER_M) {\n const pFd: [number, number, number] = [pF[0], belowY, pF[2]];\n const qFd: [number, number, number] = [qF[0], belowY, qF[2]];\n // Faces the focal — the direction the audience looks from.\n const rn: readonly [number, number, number] = [-np[0], 0, -np[1]];\n emitRiser(() => {\n builder.tri(pF, qF, qFd, rn, colors.riser);\n builder.tri(pF, qFd, pFd, rn, colors.riser);\n });\n }\n }\n }\n}\n","/**\n * THE seating surface — one function per section, consumed by everything.\n *\n * Before this module the venue had three independently-computed surfaces (tier\n * cap, row treads, seat dots) held apart by hardcoded offsets that had to agree,\n * and never did across chart shapes. Two measured defects came out of that:\n *\n * - The tier cap was a raw earcut triangulation of the section outline with a\n * *radial, nonlinear* height evaluated only at outline vertices. A 90 m chord\n * across a curved bowl is a flat plane through the seating: 23 % of arena\n * seats and 48 % of amphitheatre seats rendered UNDER their own tier cap\n * (worst overshoot 5.54 m).\n * - The cap took each section's front-edge datum from its OUTLINE vertices and\n * the doc focal, while `layout.ts`'s `assignEyeHeights` takes it from the\n * section's SEATS and each seat's own `focalPoint`. Different datum ⇒ a\n * per-section constant disagreement even where tessellation was exact.\n *\n * `deckAt(x, y)` here is the single definition of \"the height of the walkable\n * seating surface\". It mirrors `assignEyeHeights` exactly — same front-edge\n * datum, same drawn-radial-depth rise — so a seat dot and the deck it stands on\n * can no longer be computed from different models. The cap is tessellated\n * against this same function (see `extrudePrism`'s `maxCapError`), so the drawn\n * triangle mesh tracks it rather than chording across it.\n *\n * Pure: no OGL, no DOM. Unit-tested in workers/api/test/view3dSurface.test.ts.\n */\n\nimport type { ChartObject, ExpandedSeat, Point, SectionObject } from '../../core/types';\nimport { pointInPolygonWithHoles } from '../../core/layout';\nimport { buildSectionRake } from '../../core/rake';\nimport { resolveSection, distanceToPolyline, type ResolvedRow } from '../../core/venueStructure';\nimport { CHART_UNITS_PER_METRE, METRES_PER_CHART_UNIT, SEATED_EYE_HEIGHT_M, sectionGeometry } from '../../core/units';\nimport { outsetRing } from './geometry';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport { MIN_REACH_U, seatClusterPatch } from './deckBands';\n\n/** Top of the thin slab drawn for a section with no authored height/rake. Seats\n * on a flat chart rest on this, so it is part of the surface definition rather\n * than a constant duplicated by the seat builder. */\nexport const FLAT_SLAB_TOP_M = 0.05;\n\n/**\n * Subdivision TARGET for the drawn tier cap, metres — how closely it tries to\n * track `deckAt`. Consumed by `sceneModel` when it extrudes a tier.\n *\n * A target, not a guarantee: refinement is uniform per section (the only way to\n * stay watertight — see `emitCapUniform`) and bounded by a triangle budget, so a\n * section whose surface folds sharply stops refining before it reaches this.\n * {@link SEAT_CLEARANCE_M} is what actually protects the seats.\n */\nexport const CAP_MAX_ERROR_M = 0.05;\n\n/**\n * Clearance between a seat dot's origin and the deck it stands on, metres.\n *\n * The cap's error is ONE-SIDED and that is what makes a single clearance work.\n * `deckAt` is a cone — front-edge height plus a linear function of radial\n * distance — which is convex, so a triangle interpolating it lies at or above it\n * everywhere inside the triangle, never below. The cap can therefore only ever\n * float above the true surface, and only a seat needs protecting from it.\n *\n * The size, however, is MEASURED rather than proven. Refinement aims at\n * {@link CAP_MAX_ERROR_M} but is uniform per section and capped by a triangle\n * budget, and the residual peaks at the surface's fold, where a section's flat\n * front plateau turns into its rake. Measured worst cap error across all four\n * harness charts is 13 cm. 15 cm carries that while staying under the ~22 cm\n * seat-dot radius, so it is invisible. THIS is the number that must exceed the\n * cap's real error — the test suite asserts exactly that relationship.\n *\n * The test suite enforces the real invariant directly — zero buried seats on\n * every harness chart — so a regression in either number fails loudly rather\n * than silently eating the margin. If a chart ever needs a tighter cap, the fix\n * is fold-aware refinement (splitting triangles that straddle `frontU`), not a\n * bigger clearance.\n */\nexport const SEAT_CLEARANCE_M = 0.15;\n\n/**\n * How far outside its authored outline a section still owns seats, chart units.\n *\n * Matches the deck padding in `buildTier` exactly (1.5 seat-dot radii), because\n * it answers the same question: the deck is drawn at that padded ring, so a seat\n * standing on drawn deck must belong to the section that drew it. Any smaller and\n * boundary seats are orphaned onto the floor beneath their own tier.\n */\nexport const SEAT_OWNERSHIP_PAD_U = SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE;\n\n/** One section's resolved seating surface. */\nexport interface SectionSurface {\n readonly sectionId: string;\n /** World-metre height of the seating deck at a chart-unit point. */\n deckAt(x: number, y: number): number;\n /**\n * ANALYTIC unit normal of the deck at a chart-unit point, world axes (y up).\n *\n * The drawn cap is a piecewise-linear approximation of `deckAt`, so its FACE\n * normals are discontinuous across every shared edge — measured at 46 % of cap\n * edges breaking >2°, worst 110°, which flat-shades into the dark streaks\n * across a raked deck. Worse, the near-degenerate triangles earcut emits at a\n * densified boundary (aspect ratios to 8.7e6) have numerically meaningless\n * face normals, so they shade as random dark needles.\n *\n * `deckAt` is a cone and therefore differentiable everywhere except its fold,\n * so the true normal is available in closed form and costs nothing. Shading the\n * cap with it instead of the face normal makes the surface read as smooth\n * regardless of tessellation AND makes a sliver's shading harmless — it now\n * matches its neighbours even when its own geometry is degenerate.\n */\n normalAt(x: number, y: number): [number, number, number];\n /** True when the section carries no rake — the deck is a constant plane. */\n readonly flat: boolean;\n /** Bottom of the section prism (floor base), world metres. */\n readonly bottomY: number;\n /**\n * The section's rows with the world-metre LEVEL each one sits at, front first.\n *\n * This is what the deck is actually built from. A row is a level ribbon and\n * consecutive ribbons are joined by a riser, so the drawn surface passes\n * exactly through every row instead of approximating a field between them —\n * and because a ribbon is horizontal, its cap error against the seats it\n * carries is zero by construction rather than by measurement.\n *\n * Empty for a flat section, and for a section with fewer than two rows (which\n * keeps the old smooth-cone path, since there is no row structure to build on).\n */\n readonly rowLevels: readonly {\n readonly pts: readonly Point[];\n readonly y: number;\n readonly depth: number;\n readonly blockId: number;\n /** Deck footprint for an entry that is a TABLE rather than a row — see\n * `BandRow.patch` in deckBands.ts for why a ring of seats cannot be decked\n * by offsetting a polyline. */\n readonly patch?: readonly Point[];\n }[];\n /** The landing height for the parts of the outline that hold no rows. */\n readonly landingY: number;\n}\n\nexport interface VenueSurfaces {\n /** Surface by section object id (NOT logical id — heights are per object). */\n bySection: Map<string, SectionSurface>;\n /** Owning section object id per seat index, or null when a seat sits outside\n * every section (free-standing rows). Parallel to the input seat array. */\n seatOwner: Array<string | null>;\n /** Deck height in world metres for seat index i (falls back to the seat's own\n * resolved eye height when it owns no section). */\n seatDeckY(i: number): number;\n /**\n * Seat pitch in CHART UNITS for seat index i, or undefined when its section\n * has no resolved rows.\n *\n * The spacing a seat actually has to itself — the smaller of its own row's\n * seat spacing and the gap to the neighbouring row. Callers size a seat's\n * marker from this.\n *\n * It is emphatically NOT the distance to the nearest other seat. That measure\n * shrank 80 amphitheatre dots to as little as 0.089 m, every one of them at a\n * WEDGE BOUNDARY: the last seat of one wedge sits 0.212 m from the first seat\n * of the next across an aisle, far closer than the 0.55 m spacing inside either\n * row. Two seats being near each other across an aisle says nothing about how\n * much room either one has.\n */\n seatPitchU(i: number): number | undefined;\n}\n\ninterface Bbox { minX: number; minY: number; maxX: number; maxY: number }\n\nfunction bboxOf(pts: Point[]): Bbox {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const p of pts) {\n if (p.x < minX) minX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.x > maxX) maxX = p.x;\n if (p.y > maxY) maxY = p.y;\n }\n return { minX, minY, maxX, maxY };\n}\n\n/** Even-odd point-in-ring test, for the closed table patches above. */\nfunction pointInRing(ring: readonly Point[], x: number, y: number): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const a = ring[i], b = ring[j];\n if ((a.y > y) !== (b.y > y) && x < ((b.x - a.x) * (y - a.y)) / (b.y - a.y) + a.x) inside = !inside;\n }\n return inside;\n}\n\n/** Hard ceiling on rake rise so a mis-authored or focal-wrapping section can\n * never produce a runaway spike (defect guard, carried over from buildTier). */\nconst MAX_TIER_RISE_M = 25;\n\nexport interface SurfaceUnit {\n objects: ChartObject[];\n focal: Point;\n baseHeightM: number;\n}\n\n/**\n * Resolve every section's seating surface and each seat's owning section.\n *\n * The front-edge datum is taken from the section's member SEATS when it has any\n * (matching `assignEyeHeights`), and only falls back to the outline's nearest\n * vertex for a section with no seats — where nothing can disagree with it.\n */\nexport function buildVenueSurfaces(units: SurfaceUnit[], seats: ExpandedSeat[]): VenueSurfaces {\n const bySection = new Map<string, SectionSurface>();\n const seatOwner = new Array<string | null>(seats.length).fill(null);\n const seatDeck = new Float64Array(seats.length);\n /** Level of the seat's own row, when its section resolved into rows. */\n const seatRowLevel = new Array<number | undefined>(seats.length).fill(undefined);\n /** Pitch of the seat's own row (chart units), when resolved. */\n const seatPitch = new Array<number | undefined>(seats.length).fill(undefined);\n\n // --- Pass 1: ownership + per-section seat front distance -------------------\n interface Acc {\n section: SectionObject;\n unit: SurfaceUnit;\n /** Nearest drawn focal distance among member seats (chart units). */\n frontU: number;\n hasSeats: boolean;\n /** Member seat positions grouped by row — the input the rake axis is fitted from. */\n rows: Map<string, Point[]>;\n /** Indices of this section's member seats, for the structure resolver. */\n seatIndices: number[];\n }\n const acc = new Map<string, Acc>();\n const boxes: Array<{ id: string; box: Bbox; section: SectionObject; unit: SurfaceUnit; outline: Point[] }> = [];\n /**\n * Object ids that expand into a RING of seats rather than a run of them.\n *\n * `expandTable` stamps every chair with `rowId = table.id`, so the structure\n * resolver — which only ever sees seats — groups a whole table into what it\n * calls a row. Geometrically it is nothing of the kind, and the deck builder\n * has to know the difference. Reading it off the authored objects is exact;\n * inferring \"these seats look like a ring\" from the points would be a second\n * opinion about a fact the chart already states.\n */\n const tableRowIds = new Set<string>();\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'table') tableRowIds.add(o.id);\n if (o.type !== 'section' || !o.outline || o.outline.length < 3) continue;\n // Ownership is tested against the PADDED outline — the same ring the deck\n // is actually drawn with (see `buildTier`'s `outsetRing`). Testing the raw\n // outline instead left seats authored just outside it unowned, so they fell\n // to the floor while the drawn deck covered them: measured as 9 buried\n // cinema seats and a 0.20 m drop on the uber arena. A seat resting on drawn\n // deck belongs to the section that drew it.\n const owned = outsetRing(o.outline, SEAT_OWNERSHIP_PAD_U);\n acc.set(o.id, { section: o, unit, frontU: Infinity, hasSeats: false, rows: new Map(), seatIndices: [] });\n boxes.push({ id: o.id, box: bboxOf(owned), section: o, unit, outline: owned });\n }\n }\n\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n for (const b of boxes) {\n // Bbox prefilter keeps the 14k-seat charts cheap; the polygon test only\n // runs for genuine candidates.\n if (s.x < b.box.minX || s.x > b.box.maxX || s.y < b.box.minY || s.y > b.box.maxY) continue;\n if (!pointInPolygonWithHoles({ x: s.x, y: s.y }, b.outline, b.section.holes)) continue;\n seatOwner[i] = b.id;\n const a = acc.get(b.id)!;\n a.hasSeats = true;\n const f = s.focalPoint ?? b.unit.focal;\n const d = Math.hypot(s.x - f.x, s.y - f.y);\n if (d < a.frontU) a.frontU = d;\n // Group by row, exactly as assignEyeHeights does — the rake axis is fitted\n // from the same grouping in both files, or the two models diverge again.\n a.seatIndices.push(i);\n const rowKey = s.rowId || `__seat-${i}`;\n const arr = a.rows.get(rowKey);\n if (arr) arr.push({ x: s.x, y: s.y }); else a.rows.set(rowKey, [{ x: s.x, y: s.y }]);\n break; // first containing section wins, exactly as assignEyeHeights does\n }\n }\n\n // --- Pass 2: build one surface function per section ------------------------\n for (const [id, a] of acc) {\n const geo = sectionGeometry(a.section, { floorBaseHeightM: a.unit.baseHeightM });\n const bottomY = a.unit.baseHeightM;\n const rakeTan = geo.rake > 0 ? Math.tan((geo.rake * Math.PI) / 180) : 0;\n // An authored override wins over inference (see `SectionObject.surfaceKind`).\n // `rakedRows` cannot manufacture relief a section does not carry, so it only\n // prevents a raked section from being flattened, never the reverse.\n const inferredFlat = geo.rake <= 0.01 && geo.height <= bottomY + 0.001;\n const kind = a.section.surfaceKind;\n const flat = kind === 'flat' ? true : kind === 'rakedRows' ? geo.rake > 0.01 ? false : inferredFlat : inferredFlat;\n\n // Resolve the structure FIRST: when it yields row levels, the rake field is\n // never consulted and building it is pure waste. Profiled at 7 % of scene\n // build on a 50k venue — `sampleRows` doing inverse-distance work for a field\n // nothing would read.\n const structure = a.hasSeats\n ? resolveSection(id, seats, a.seatIndices, a.unit.focal)\n : { sectionId: id, rows: [] as ResolvedRow[], blockCount: 0 };\n const needsRake = structure.rows.length < 2;\n const rake = needsRake\n ? buildSectionRake([...a.rows.values()].map((points) => ({ points })), a.unit.focal)\n : null;\n\n let frontU = Infinity;\n if (rake) {\n if (a.hasSeats) {\n for (const pts of a.rows.values()) {\n for (const p of pts) {\n const d = rake.depthAt(p.x, p.y);\n if (d < frontU) frontU = d;\n }\n }\n }\n if (!a.hasSeats || !Number.isFinite(frontU)) {\n frontU = Infinity;\n for (const p of a.section.outline) {\n const d = rake.depthAt(p.x, p.y);\n if (d < frontU) frontU = d;\n }\n }\n }\n const baseFloor = bottomY + FLAT_SLAB_TOP_M;\n // A level deck sits at the section's AUTHORED height, not on the floor.\n //\n // This used to be `bottomY + FLAT_SLAB_TOP_M` outright, which meant asking\n // for `surfaceKind: 'flat'` silently threw the authored `height` away: a\n // 7.4 m loge box and a 2.6 m choir terrace both rendered with every seat at\n // 0.20 m (slab top plus seat clearance). It made `flat` unusable for the\n // exact things it exists for — boxes, suites and terraces, which are genuinely\n // LEVEL platforms — so both curated venues had to fake a shallow rake instead.\n //\n // The datum is now taken from the same expression the raked path uses\n // (`levelFor`/`levelForBlockDepth` below, at zero rise), so \"flat\" and\n // \"raked at 0°\" agree by construction rather than by coincidence. A section\n // with no authored height still lands on `baseFloor` exactly as before, which\n // is why legacy height-less charts do not move: `inferredFlat` only fires when\n // `geo.height <= bottomY + 0.001`, so this max can only bite when a height was\n // explicitly authored alongside `surfaceKind: 'flat'`.\n const flatTop = Math.max(baseFloor, geo.height);\n\n /** Height for a depth ordinate measured from the venue focal (fallback path). */\n const levelFor = (depthU: number): number => {\n const depthM = Math.max(0, depthU - frontU) * METRES_PER_CHART_UNIT;\n const rise = Math.min(depthM * rakeTan, MAX_TIER_RISE_M);\n return Math.max(baseFloor, geo.height + rise);\n };\n\n /** Height for a depth measured back from a BLOCK's own front row. */\n const levelForBlockDepth = (blockDepthU: number): number => {\n const depthM = Math.max(0, blockDepthU) * METRES_PER_CHART_UNIT;\n const rise = Math.min(depthM * rakeTan, MAX_TIER_RISE_M);\n return Math.max(baseFloor, geo.height + rise);\n };\n\n // One level per row, from the resolved structure. THIS is the deck.\n //\n // Depth is measured inside the row's own BLOCK, so every block of a tier\n // starts at the tier's authored height. Measuring it from the venue focal\n // (which is what `rake.depthAt` does, and what this used to do) raised a\n // bowl's side wedges as though they were further back: sec-gall's six blocks\n // are one tier authored at 6.20 m and started at 6.35 to 10.65 m.\n // A table's deck is its own footprint, grown past its outermost seat by the\n // same clearance a ribbon carries past its end seat (MIN_REACH_U). The\n // radius is taken from where the seats ACTUALLY are, not from the table's\n // authored `radius`: `expandTable` seats a round table at `radius + 16`\n // chart units, and per-chair `dx/dy` overrides move individual seats again,\n // so the authored radius understates the ring it has to cover. On the\n // nightclub that is 50 u of real reach against an authored 34.\n const rowLevels = flat\n ? []\n : structure.rows.map((r) => ({\n pts: r.pts,\n y: levelForBlockDepth(r.blockDepth),\n depth: r.blockDepth,\n blockId: r.blockId,\n ...(tableRowIds.has(r.id)\n ? { patch: seatClusterPatch(r.pts, MIN_REACH_U) }\n : {}),\n }));\n const landingY = rowLevels.length ? rowLevels[0].y : flatTop;\n\n // Bounding circles over the rows, so the nearest-row lookup below prunes\n // instead of walking every polyline in the section. A 50k venue has sections\n // with thousands of rows and this is called per cap sample.\n // Bounded over the entry's DRAWN extent — the patch when it has one, since\n // that is the ground it actually covers and a bound taken from the seat ring\n // alone would prune away the deck at the patch's rim.\n const rowBounds = rowLevels.map((r) => {\n const ext = r.patch && r.patch.length >= 3 ? r.patch : r.pts;\n let cx = 0, cy = 0;\n for (const p of ext) { cx += p.x; cy += p.y; }\n const n = ext.length || 1;\n cx /= n; cy /= n;\n let rad = 0;\n for (const p of ext) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > rad) rad = d;\n }\n return { cx, cy, rad };\n });\n\n const deckAt = flat\n ? (): number => flatTop\n : rowLevels.length >= 2\n ? (x: number, y: number): number => {\n // The deck is the level of the row you are standing on. A step\n // function, deliberately: it is the drawn geometry, not an\n // approximation of it, so a seat and its ribbon cannot disagree.\n let best = Infinity, bestY = landingY;\n for (let i = 0; i < rowLevels.length; i++) {\n const b = rowBounds[i];\n // Admissible lower bound — cannot discard a row that would have won.\n if (Math.hypot(x - b.cx, y - b.cy) - b.rad >= best) continue;\n // Over a table, the deck is the table's patch — and a point inside\n // the patch is ON it, at distance zero. Measuring to the seat ring's\n // polyline instead would hand the middle of a table to whichever\n // ROW happened to be nearest, so the deck height and the drawn patch\n // would disagree by a whole step.\n const patch = rowLevels[i].patch;\n const d = patch && patch.length >= 3\n ? (pointInRing(patch, x, y) ? 0 : distanceToPolyline(patch, x, y))\n : distanceToPolyline(rowLevels[i].pts, x, y);\n if (d < best) { best = d; bestY = rowLevels[i].y; }\n }\n return bestY;\n }\n : (x: number, y: number): number => (rake ? levelFor(rake.depthAt(x, y)) : flatTop);\n\n // The gradient of the same expression. Height rises with radial distance\n // only, so the surface tilts purely along the outward radial direction and\n // the slope is exactly `rakeTan` — except on the flat front plateau\n // (d <= frontU), past the runaway clamp, and where the baseFloor floor wins,\n // all of which are locally horizontal.\n const UP: [number, number, number] = [0, 1, 0];\n // Every ribbon and every landing is HORIZONTAL, so the deck's normal is up\n // everywhere and the analytic-normal machinery has nothing left to correct.\n // The only non-horizontal deck surfaces are the risers, and those carry their\n // own normals from the band builder.\n const normalAt = rowLevels.length >= 2 || flat\n ? (): [number, number, number] => UP\n : (x: number, y: number): [number, number, number] => {\n if (!rake) return UP;\n const d = rake.depthAt(x, y);\n if (d <= frontU) return UP;\n const depthM = (d - frontU) * METRES_PER_CHART_UNIT;\n if (depthM * rakeTan >= MAX_TIER_RISE_M) return UP; // clamped: flat again\n if (geo.height + depthM * rakeTan <= baseFloor) return UP; // floored: flat\n const [gx, gy] = rake.gradientAt(x, y);\n if (gx === 0 && gy === 0) return UP;\n const inv = 1 / Math.hypot(rakeTan, 1);\n return [-gx * rakeTan * inv, inv, -gy * rakeTan * inv];\n };\n\n // A seat takes its own row's level DIRECTLY. Searching for the nearest row\n // would give the same answer almost always and the wrong one occasionally\n // (where two blocks interleave), and it would cost a polyline search per seat\n // on a chart that may hold 50,000 of them. Row membership is already known.\n if (!flat) {\n for (const r of structure.rows) {\n const y = levelForBlockDepth(r.blockDepth);\n for (const si of r.seatIndices) seatRowLevel[si] = y;\n }\n }\n\n // Per-row pitch: the median spacing ALONG the row (immune to the aisle, which\n // falls between rows, not inside one) capped by the gap to the nearest other\n // row, so a dot cannot grow into the row in front either.\n for (const r of structure.rows) {\n const gaps: number[] = [];\n for (let k = 1; k < r.pts.length; k++) {\n const d = Math.hypot(r.pts[k].x - r.pts[k - 1].x, r.pts[k].y - r.pts[k - 1].y);\n if (d > 1e-6) gaps.push(d);\n }\n gaps.sort((x, y) => x - y);\n const along = gaps.length ? gaps[Math.floor(gaps.length / 2)] : Infinity;\n\n // Measured against rows in the SAME BLOCK only. A row in a neighbouring\n // block runs BESIDE this one, not in front of it, and near a block boundary\n // it can pass within a hair of the probe: measured across the catalog that\n // collapsed the pitch to 0.04 mm on the opera house and 0.8 mm on the\n // esports arena, which then drove every dot there to the visibility floor.\n // This is the same mistake as sizing a seat against a neighbour across an\n // aisle, one level up.\n let across = Infinity;\n const probe = r.pts[Math.floor(r.pts.length / 2)];\n if (probe) {\n for (const other of structure.rows) {\n if (other === r || other.blockId !== r.blockId) continue;\n const d = distanceToPolyline(other.pts, probe.x, probe.y);\n if (d > 1e-6 && d < across) across = d;\n }\n }\n // Never below half the in-row spacing. Two rows can genuinely be authored\n // on top of each other (chairs around a table cross their neighbours'\n // arcs), and a seat's own row spacing is still real when that happens.\n const pitch = Math.min(along, Math.max(across, along * 0.5));\n if (Number.isFinite(pitch) && pitch > 0) {\n for (const si of r.seatIndices) seatPitch[si] = pitch;\n }\n }\n\n bySection.set(id, { sectionId: id, deckAt, normalAt, flat, bottomY, rowLevels, landingY });\n }\n\n // --- Pass 3: bake each seat's deck height from its owner's surface ---------\n for (let i = 0; i < seats.length; i++) {\n const ownerId = seatOwner[i];\n const s = seats[i];\n if (ownerId) {\n const own = seatRowLevel[i];\n seatDeck[i] = (own ?? bySection.get(ownerId)!.deckAt(s.x, s.y)) + SEAT_CLEARANCE_M;\n continue;\n }\n // Seat outside every section: fall back to its own resolved eye height, the\n // only surface information it carries.\n const eye = s.eyeHeightM;\n seatDeck[i] = Number.isFinite(eye) ? Math.max(0, (eye as number) - SEATED_EYE_HEIGHT_M) : 0;\n }\n\n return {\n bySection,\n seatOwner,\n seatDeckY: (i: number): number => seatDeck[i],\n seatPitchU: (i: number): number | undefined => seatPitch[i],\n };\n}\n","/**\n * The DOM half of venue labels: one absolutely-positioned element per visible\n * label, repositioned from projected 3D anchors as the camera moves.\n *\n * Kept out of `index.ts` so the render loop stays about rendering, and out of\n * `labels.ts` so the anchor/LOD logic stays pure and testable.\n *\n * ## Accessibility\n *\n * These are real DOM text nodes, which is the point. A screen reader can read the\n * venue's structure — zones, sections, booth names — where a GPU-drawn glyph is\n * invisible to it. The overlay itself is `aria-hidden` only for the decorative\n * pointer-events layer; the labels are a live region-free list of static text,\n * announced in document order.\n *\n * Pointer events pass straight through: a label must never eat a seat tap.\n */\n\nimport {\n DENSE_LABEL_BUDGET, cullOverlapping, focusScore, pickDenseLabels, projectToScreen,\n visibleLabelKinds, type SceneLabel,\n} from './labels';\n\n/**\n * Minimum gap before the farther label is dropped, CSS px — wide and short,\n * matching the shape of a line of text rather than a disc around it.\n */\nconst SEPARATION_X_PX = 88;\nconst SEPARATION_Y_PX = 20;\n\n/** Per-kind styling. Sizes are CSS px at a nominal viewport. */\nconst KIND_STYLE: Record<SceneLabel['kind'], { size: number; weight: string; opacity: number }> = {\n zone: { size: 15, weight: '600', opacity: 0.95 },\n section: { size: 12, weight: '500', opacity: 0.88 },\n ga: { size: 12, weight: '500', opacity: 0.88 },\n booth: { size: 11, weight: '500', opacity: 0.85 },\n annotation: { size: 11, weight: '400', opacity: 0.75 },\n // Row and seat identity are quieter than the structure they sit inside: at\n // this range the venue is already understood and the label is a detail, so it\n // must not compete with the seating it is printed over.\n row: { size: 10.5, weight: '600', opacity: 0.8 },\n seat: { size: 9.5, weight: '500', opacity: 0.72 },\n};\n\n/**\n * Per-kind declutter spacing, CSS px. Seat and row labels are short strings sat\n * on a tight grid, so the wide section-name box would throw away almost all of\n * them — a row of seat numbers needs to survive side by side.\n */\nconst DENSE_KINDS = new Set<SceneLabel['kind']>(['row', 'seat']);\n/**\n * Per-kind, because the two are different shapes of text. A seat number is 1–3\n * characters and wants to survive shoulder to shoulder along a row; a row name\n * is a word (\"ORCH-L\") and needs roughly twice the width before the next one is\n * legible. One shared value crowded the row names into each other.\n */\nconst DENSE_SEPARATION: Record<'row' | 'seat', { x: number; y: number }> = {\n row: { x: 62, y: 16 },\n // Widened from 24: at close range seat labels stopped overlapping at all, so\n // the separation had no work left to do and the budget was carrying the whole\n // load. A wider box means the few labels that ARE kept are spread across the\n // seating instead of clustering into one stack.\n seat: { x: 46, y: 18 },\n};\n\nexport interface LabelOverlayOptions {\n /** `ChartTheme.fontFamily`, when the chart authors one. */\n fontFamily?: string;\n /** Ink colour for labels that carry no authored colour. */\n ink?: string;\n}\n\nexport class LabelOverlay {\n private root: HTMLDivElement;\n private nodes = new Map<string, HTMLDivElement>();\n private labels: SceneLabel[] = [];\n private opts: LabelOverlayOptions;\n private forcedDense = false;\n private selectedSeatLabelIds = new Set<string>();\n\n constructor(container: HTMLElement, opts: LabelOverlayOptions = {}) {\n this.opts = opts;\n this.root = document.createElement('div');\n this.root.setAttribute('data-view3d-labels', '');\n const s = this.root.style;\n s.position = 'absolute';\n s.inset = '0';\n // Never intercept a seat tap — the canvas below owns all pointer input.\n s.pointerEvents = 'none';\n s.overflow = 'hidden';\n if (opts.fontFamily) s.fontFamily = opts.fontFamily;\n container.appendChild(this.root);\n }\n\n /**\n * Hide or show the whole overlay without disturbing which labels exist.\n *\n * The in-scene panorama sphere is GL, so it draws BEHIND every DOM label —\n * row and seat labels floated on top of a 360 photo until this existed. The\n * old DOM panorama never needed it because its opaque div covered them.\n *\n * Deliberately not `setLabels([])`: that destroys the nodes and the declutter\n * state, so closing the panorama would rebuild and re-rank the whole overlay\n * and flash. Visibility is a view concern, not a data one.\n */\n setVisible(visible: boolean): void {\n this.root.style.visibility = visible ? '' : 'hidden';\n }\n\n setLabels(labels: SceneLabel[]): void {\n this.labels = labels;\n for (const [id, node] of this.nodes) {\n if (!labels.some((l) => l.id === id)) { node.remove(); this.nodes.delete(id); }\n }\n }\n\n /** Section/row drill-in makes their dense labels explicit, regardless of the\n * whole-venue distance heuristic used during free orbit. */\n setForcedDense(enabled: boolean): void {\n this.forcedDense = enabled;\n }\n\n /** Keep the authoritative selected identities pinned above nearby dense\n * labels. The seat mesh and its DOM label must never describe different\n * rows merely because collision suppression preferred the neighbour. */\n setSelectedSeatIds(ids: Iterable<string>): void {\n this.selectedSeatLabelIds = new Set(Array.from(ids, (id) => `seat:${id}`));\n }\n\n /**\n * Reposition every label for the current camera.\n *\n * `viewProjection` is column-major, as OGL supplies it.\n */\n update(\n viewProjection: ArrayLike<number>,\n width: number,\n height: number,\n cameraDistance: number,\n venueRadius: number,\n /**\n * Camera world position, for ranking the dense rungs by real distance.\n * Optional so callers that only need structure labels need not supply it;\n * without it the dense rungs fall back to ranking by screen centre alone.\n */\n cameraWorld?: readonly [number, number, number],\n ): void {\n if (!this.labels.length) return;\n const kinds = visibleLabelKinds(cameraDistance, venueRadius);\n if (this.forcedDense) {\n kinds.add('row');\n kinds.add('seat');\n }\n\n const candidates: Array<{\n label: SceneLabel;\n screen: ReturnType<typeof projectToScreen>;\n focus: number;\n priority?: number;\n }> = [];\n for (const label of this.labels) {\n if (!kinds.has(label.kind)) continue;\n const screen = projectToScreen(viewProjection, label.anchor, width, height);\n if (!screen.visible) continue;\n const world = cameraWorld\n ? Math.hypot(\n label.anchor[0] - cameraWorld[0],\n label.anchor[1] - cameraWorld[1],\n label.anchor[2] - cameraWorld[2],\n )\n : 1;\n candidates.push({\n label,\n screen,\n focus: focusScore(screen, world, width, height),\n ...(this.selectedSeatLabelIds.has(label.id) ? { priority: 1 } : {}),\n });\n }\n\n // Nearest-wins declutter, then paint. Everything not kept is hidden rather\n // than removed, so a small camera move does not thrash the DOM. Dense kinds\n // declutter against their OWN spacing and separately from the structure\n // labels, so a section name never suppresses the seat numbers under it.\n const structure = candidates.filter((c) => !DENSE_KINDS.has(c.label.kind));\n const kept = [\n ...cullOverlapping(structure, SEPARATION_X_PX, SEPARATION_Y_PX),\n // The dense rungs are BUDGETED, not merely deduplicated — see\n // DENSE_LABEL_BUDGET for why the overlap test alone gets worse the closer\n // the camera gets.\n ...(['row', 'seat'] as const).flatMap((kind) => pickDenseLabels(\n candidates.filter((c) => c.label.kind === kind),\n DENSE_SEPARATION[kind].x,\n DENSE_SEPARATION[kind].y,\n DENSE_LABEL_BUDGET[kind],\n )),\n ];\n const keptIds = new Set(kept.map((k) => k.label.id));\n\n for (const { label, screen } of kept) {\n const node = this.nodeFor(label);\n const st = node.style;\n st.display = '';\n st.transform = `translate(-50%, -50%) translate(${screen.x.toFixed(1)}px, ${screen.y.toFixed(1)}px)`;\n }\n for (const [id, node] of this.nodes) {\n if (!keptIds.has(id)) node.style.display = 'none';\n }\n }\n\n private nodeFor(label: SceneLabel): HTMLDivElement {\n let node = this.nodes.get(label.id);\n if (!node) {\n node = document.createElement('div');\n const s = node.style;\n s.position = 'absolute';\n s.left = '0';\n s.top = '0';\n s.whiteSpace = 'nowrap';\n s.textShadow = '0 1px 3px rgba(0,0,0,0.85), 0 0 8px rgba(0,0,0,0.55)';\n s.display = 'none';\n this.root.appendChild(node);\n this.nodes.set(label.id, node);\n }\n node.textContent = label.text;\n node.setAttribute('data-label-kind', label.kind);\n const selected = this.selectedSeatLabelIds.has(label.id);\n node.toggleAttribute('data-selected-seat', selected);\n const style = KIND_STYLE[label.kind];\n const s = node.style;\n s.fontSize = `${style.size}px`;\n s.fontWeight = selected ? '800' : style.weight;\n s.opacity = selected ? '1' : String(style.opacity);\n s.color = selected ? '#ecf8ff' : (label.color ?? this.opts.ink ?? '#e8edf5');\n s.background = selected ? 'rgba(10,28,45,.9)' : '';\n s.border = selected ? '1px solid rgba(89,199,255,.92)' : '';\n s.borderRadius = selected ? '999px' : '';\n s.padding = selected ? '3px 7px' : '';\n s.boxShadow = selected ? '0 0 0 2px rgba(41,176,255,.18), 0 5px 14px rgba(0,0,0,.35)' : '';\n s.letterSpacing = label.kind === 'zone' ? '0.08em' : '0.02em';\n if (label.kind === 'zone') s.textTransform = 'uppercase';\n return node;\n }\n\n dispose(): void {\n this.root.remove();\n this.nodes.clear();\n }\n}\n","/**\n * Builds (and rebuilds) all GPU resources from a SceneModel. Kept separate from\n * the model so a context-loss restore can throw the old GpuScene away and call\n * `buildGpuScene(gl, model)` again — the model never changes.\n *\n * Draw calls: background (1) + merged solids (1) + instanced seats (1) = 3, plus\n * ONE more instanced draw for the near-field chairs whenever the camera is close\n * enough that any seat qualifies. That mesh is attached and detached from the\n * scene graph rather than drawn empty, so an overview frame still costs exactly\n * the three calls it always did.\n */\n\nimport { Geometry, Mesh, Program, Transform, type OGLRenderingContext } from 'ogl';\nimport { SEAT_STATES, upholsteryTone, type RGB } from '../palette';\nimport { createBackgroundProgram, createChairProgram, createSeatProgram, createSolidProgram } from './materials';\nimport { CHAIR_MAX_INSTANCES } from '../lod';\nimport { buildChairMesh } from './seatChair';\nimport type { SceneModel } from './sceneModel';\nimport { seatOccupantSeed, type DirtyRun } from './seatInstances';\n\n/**\n * Fill an iColor buffer range from the current iState values (state → colour).\n *\n * Reads the THEME's colours, not the module palette, so an organizer's brand\n * selection colour applies to the 3D seats as it already does in the picker.\n */\nfunction writeSeatColors(\n iColor: Float32Array,\n iState: Float32Array,\n start: number,\n count: number,\n states: readonly RGB[],\n /** Per-instance category colours; an AVAILABLE seat wears its own. */\n iCategory?: Float32Array,\n): void {\n for (let i = start; i < start + count; i++) {\n // State index 0 is 'available'. It is the only state that defers to the\n // category, exactly as 2D does: `paintSeat` fills a free seat with\n // `seatBaseColor(categoryKey)` and only overrides once it is held, sold or\n // selected. Those overrides carry information the category cannot, so they\n // must keep winning.\n const useCategory = iCategory && iState[i] === 0;\n const c = useCategory ? null : (states[iState[i]] ?? states[0]);\n iColor[i * 3] = c ? c[0] : iCategory![i * 3];\n iColor[i * 3 + 1] = c ? c[1] : iCategory![i * 3 + 1];\n iColor[i * 3 + 2] = c ? c[2] : iCategory![i * 3 + 2];\n }\n}\n\n// Two-triangle quad in [-1,1] (billboard base).\nconst SEAT_QUAD = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);\n// Fullscreen triangle.\nconst BG_TRI = new Float32Array([-1, -1, 3, -1, -1, 3]);\n\nexport interface GpuScene {\n /** Main scene (solids + seats), drawn with the camera. */\n main: Transform;\n /** Background scene, drawn first without depth. */\n background: Transform;\n seatProgram: Program;\n solidProgram: Program;\n /** Near-field chair program (shares uFocusFloor / fade-band uniforms). */\n chairProgram: Program;\n /** Shared instanced seat geometry (reused by the pick pass — no buffer copy). */\n seatGeometry: Geometry;\n /** Shared near-field chair/person geometry, including global pick ids. */\n chairGeometry: Geometry;\n /** Merged solid geometry (reused as the pick occluder). */\n solidGeometry: Geometry;\n drawCalls: number;\n /** Upload only the changed instance-state ranges (never the whole buffer). */\n uploadSeatStateRuns(runs: DirtyRun[]): void;\n /**\n * Hand the chair mesh the seat indices the near-field gather selected.\n *\n * `indices[0…count)` are instance indices into the seat cloud — the SAME\n * indices the dots and the pick pass use, which is what guarantees a chair and\n * its dot are always the same seat. Passing count 0 detaches the mesh.\n */\n setNearSeats(indices: Int32Array, count: number): void;\n /** How many chairs are currently instanced (0 = the near field is off). */\n nearSeatCount(): number;\n dispose(): void;\n}\n\nexport function buildGpuScene(gl: OGLRenderingContext, model: SceneModel): GpuScene {\n const main = new Transform();\n const background = new Transform();\n\n // --- Background ---\n const bgGeo = new Geometry(gl, { position: { size: 2, data: BG_TRI } });\n // The chart's own background, not the library's — see theme.ts.\n const bgProg = createBackgroundProgram(gl, model.theme.background.top as number[], model.theme.background.bottom as number[]);\n const bgMesh = new Mesh(gl, { geometry: bgGeo, program: bgProg });\n bgMesh.frustumCulled = false;\n bgMesh.setParent(background);\n\n // --- Solids (floor + tiers + stage + décor + GA, merged) ---\n const solidGeo = new Geometry(gl, {\n position: { size: 3, data: model.solids.position },\n normal: { size: 3, data: model.solids.normal },\n color: { size: 3, data: model.solids.color },\n floorIndex: { size: 1, data: model.solids.floor },\n materialIndex: { size: 1, data: model.solids.material },\n });\n const solidProg = createSolidProgram(gl);\n const solidMesh = new Mesh(gl, { geometry: solidGeo, program: solidProg });\n solidMesh.frustumCulled = false;\n solidMesh.setParent(main);\n\n // --- Seats (one instanced billboard mesh) ---\n // Per-instance colour resolved CPU-side from iState (no dynamically-indexed\n // array uniform — OGL only binds an array uniform whose value is a plain\n // Array, and a dynamic LUT index is best avoided anyway).\n const seatProg = createSeatProgram(gl);\n const iColor = new Float32Array(model.seats.count * 3);\n const stateColors: RGB[] = SEAT_STATES.map((st) => model.theme.seatStates[st]);\n writeSeatColors(iColor, model.seats.iState, 0, model.seats.count, stateColors, model.seats.iCategory);\n const seatGeo = new Geometry(gl, {\n position: { size: 2, data: SEAT_QUAD },\n iOffset: { size: 3, data: model.seats.iPosition, instanced: 1 },\n iColor: { size: 3, data: iColor, instanced: 1 },\n // Per-seat world-radius ceiling: what stops distant rows merging into one\n // mass when the shader grows a dot to hold its minimum pixel size.\n iMaxRadius: { size: 1, data: model.seats.iMaxRadius, instanced: 1 },\n iPhysicalSeat: { size: 1, data: model.seats.iPhysicalSeat, instanced: 1 },\n // Accommodation ring colour; (0,0,0) means the seat carries no access type.\n iRing: { size: 3, data: model.seats.iRing, instanced: 1 },\n iFloor: { size: 1, data: model.seats.iFloor, instanced: 1 },\n });\n const seatMesh = new Mesh(gl, { geometry: seatGeo, program: seatProg });\n seatMesh.frustumCulled = false;\n if (model.seats.count > 0) seatMesh.setParent(main);\n\n const colorAttr = seatGeo.attributes.iColor;\n\n // --- Near-field chairs (one extra instanced draw, only when in range) ------\n // Fixed-capacity instance buffers: the near set is bounded by\n // CHAIR_MAX_INSTANCES whatever the venue holds, so these are allocated once at\n // ~200 KB total and only ever refilled — never grown, never reallocated per\n // frame. `instancedCount` is what varies.\n const chairBase = buildChairMesh();\n const chairProg = createChairProgram(gl);\n const CAP = CHAIR_MAX_INSTANCES;\n const cOffset = new Float32Array(CAP * 3);\n const cColor = new Float32Array(CAP * 3);\n const cRadius = new Float32Array(CAP);\n const cYaw = new Float32Array(CAP);\n const cRing = new Float32Array(CAP * 3);\n const cFloor = new Float32Array(CAP);\n const cPhysicalSeat = new Float32Array(CAP);\n const cPickIndex = new Float32Array(CAP);\n // Occupancy + a stable per-person variation, in one attribute.\n //\n // Negative means the seat is EMPTY and the occupant geometry collapses. A\n // non-negative value is a per-seat hash in [0,1) that varies build and skin\n // tone, so a full row is not a rank of identical mannequins — the same trick\n // the generated panorama used on its silhouettes.\n const cSeed = new Float32Array(CAP);\n const chairGeo = new Geometry(gl, {\n position: { size: 3, data: chairBase.position },\n normal: { size: 3, data: chairBase.normal },\n part: { size: 1, data: chairBase.part },\n index: { data: chairBase.index },\n iOffset: { size: 3, data: cOffset, instanced: 1 },\n iColor: { size: 3, data: cColor, instanced: 1 },\n iRadius: { size: 1, data: cRadius, instanced: 1 },\n iYaw: { size: 1, data: cYaw, instanced: 1 },\n iRing: { size: 3, data: cRing, instanced: 1 },\n iFloor: { size: 1, data: cFloor, instanced: 1 },\n iPhysicalSeat: { size: 1, data: cPhysicalSeat, instanced: 1 },\n iSeed: { size: 1, data: cSeed, instanced: 1 },\n iPickIndex: { size: 1, data: cPickIndex, instanced: 1 },\n });\n const chairMesh = new Mesh(gl, { geometry: chairGeo, program: chairProg });\n chairMesh.frustumCulled = false;\n let nearCount = 0;\n /** The gathered set, kept so a state change can recolour it without a re-gather. */\n let nearIndices: Int32Array = new Int32Array(0);\n\n const writeChairColors = (): void => {\n const src = model.seats;\n for (let k = 0; k < nearCount; k++) {\n const i = nearIndices[k];\n cSeed[k] = seatOccupantSeed(src.iState[i], i);\n // An available chair wears its category as UPHOLSTERY, not as the map's\n // full-strength tier colour. Only the category case is muted: held, sold\n // and selected are saying something the buyer needs to see from where they\n // are sitting, and quietening those would lose real information.\n const useCategory = src.iCategory && src.iState[i] === 0;\n const c = useCategory ? null : (stateColors[src.iState[i]] ?? stateColors[0]);\n if (c) {\n cColor[k * 3] = c[0];\n cColor[k * 3 + 1] = c[1];\n cColor[k * 3 + 2] = c[2];\n } else {\n const u = upholsteryTone([src.iCategory[i * 3], src.iCategory[i * 3 + 1], src.iCategory[i * 3 + 2]]);\n cColor[k * 3] = u[0];\n cColor[k * 3 + 1] = u[1];\n cColor[k * 3 + 2] = u[2];\n }\n }\n chairGeo.attributes.iColor.needsUpdate = true;\n // Occupancy is live inventory too. A held seat that becomes available must\n // lose both its displayed person and that person's exact pick silhouette.\n chairGeo.attributes.iSeed.needsUpdate = true;\n };\n\n const scene: GpuScene = {\n main,\n background,\n seatProgram: seatProg,\n solidProgram: solidProg,\n chairProgram: chairProg,\n seatGeometry: seatGeo,\n chairGeometry: chairGeo,\n solidGeometry: solidGeo,\n drawCalls: 3,\n setNearSeats(indices: Int32Array, count: number): void {\n const n = Math.min(count, CAP);\n nearIndices = indices;\n nearCount = n;\n if (n === 0) {\n // Detached rather than drawn with instancedCount 0: an overview frame\n // must cost the same three draw calls it did before the near field\n // existed, and a zero-instance draw is still a state change + a call.\n if (chairMesh.parent) chairMesh.setParent(null);\n chairGeo.instancedCount = 0;\n scene.drawCalls = 3;\n return;\n }\n const src = model.seats;\n for (let k = 0; k < n; k++) {\n const i = indices[k];\n cOffset[k * 3] = src.iPosition[i * 3];\n cOffset[k * 3 + 1] = src.iPosition[i * 3 + 1];\n cOffset[k * 3 + 2] = src.iPosition[i * 3 + 2];\n // The chair's OWN pitch rule, not the dot's ceiling — see iChairWidth.\n cRadius[k] = src.iChairWidth[i];\n cYaw[k] = src.iYaw[i];\n cRing[k * 3] = src.iRing[i * 3];\n cRing[k * 3 + 1] = src.iRing[i * 3 + 1];\n cRing[k * 3 + 2] = src.iRing[i * 3 + 2];\n cFloor[k] = src.iFloor[i];\n cPhysicalSeat[k] = src.iPhysicalSeat[i];\n cPickIndex[k] = i + 1;\n }\n writeChairColors();\n chairGeo.attributes.iOffset.needsUpdate = true;\n chairGeo.attributes.iRadius.needsUpdate = true;\n chairGeo.attributes.iYaw.needsUpdate = true;\n chairGeo.attributes.iRing.needsUpdate = true;\n chairGeo.attributes.iFloor.needsUpdate = true;\n chairGeo.attributes.iPhysicalSeat.needsUpdate = true;\n chairGeo.attributes.iSeed.needsUpdate = true;\n chairGeo.attributes.iPickIndex.needsUpdate = true;\n chairGeo.instancedCount = n;\n if (!chairMesh.parent) chairMesh.setParent(main);\n scene.drawCalls = 4;\n },\n nearSeatCount(): number {\n return nearCount;\n },\n uploadSeatStateRuns(runs: DirtyRun[]): void {\n if (!runs.length) return;\n // A selection or availability change has to reach the chairs too, or a\n // seat picked at close range stays available-green under the buyer. The\n // gathered set is small and bounded, so it is simply rewritten rather than\n // diffed against the runs.\n if (nearCount) writeChairColors();\n // Refresh only the changed instance colours from the (already-mutated)\n // iState, then upload just those contiguous ranges — never the whole buffer.\n // iCategory must be passed HERE too, not just on the initial fill: a seat\n // whose hold expires goes back to 'available', and without it that seat\n // would repaint flat green while every untouched neighbour kept its\n // category colour.\n for (const run of runs) {\n writeSeatColors(iColor, model.seats.iState, run.start, run.length, stateColors, model.seats.iCategory);\n }\n const buffer = colorAttr.buffer;\n if (!buffer) {\n // Not uploaded yet (no draw has happened) — full upload on next draw.\n colorAttr.needsUpdate = true;\n return;\n }\n // Direct bufferSubData: OGL's render-state boundBuffer cache is not touched\n // here, which is safe because OGL rebinds attribute buffers per draw via the\n // geometry's VAO; if a future dynamic attribute relies on the cache, rebind\n // through OGL instead. 3 floats per instance (vec3 iColor).\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n for (const run of runs) {\n const sub = iColor.subarray(run.start * 3, (run.start + run.length) * 3);\n gl.bufferSubData(gl.ARRAY_BUFFER, run.start * 3 * Float32Array.BYTES_PER_ELEMENT, sub);\n }\n },\n dispose(): void {\n // OGL geometries/programs delete their GL resources on remove().\n bgGeo.remove();\n bgProg.remove();\n solidGeo.remove();\n solidProg.remove();\n seatGeo.remove();\n seatProg.remove();\n chairGeo.remove();\n chairProg.remove();\n },\n };\n return scene;\n}\n","/**\n * Inline GLSL (WebGL2 / GLSL ES 3.00) for the three scene programs. Zero\n * textures, zero shadow maps, zero post: a procedural matcap-style hemisphere +\n * warm key + fresnel rim on solids, a soft top-lit round dot for seats, and a\n * vertical-gradient + vignette background. OGL injects the built-in matrix\n * uniforms (modelViewMatrix / projectionMatrix / normalMatrix) by name.\n */\n\nimport { Program } from 'ogl';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport {\n CHAIR_FULL_M, CHAIR_NONE_M, OCCUPANT_FULL_M, OCCUPANT_NONE_M,\n OCCUPANT_NEAR_FULL_M, OCCUPANT_NEAR_NONE_M, SEAT_MIN_PIXELS_NEAR,\n} from '../lod';\nimport { BACK_BASE_M, BACK_RAKE_SLOPE } from './seatChair';\nimport type { OGLRenderingContext } from 'ogl';\n\n/**\n * The near-field weight, shared verbatim by the chair and the dot so the two\n * always sum to one at every depth and no seat can be drawn twice or not at all.\n * Mirrors `chairWeight()` in lod.ts.\n */\nconst CHAIR_WEIGHT_GLSL = /* glsl */ `\nfloat chairWeight(float depth) {\n return 1.0 - smoothstep(uChairFull, uChairNone, depth);\n}`;\n\n/** Mirrors `occupantWeight()` in lod.ts without adding per-program uniforms. */\nconst OCCUPANT_WEIGHT_GLSL = /* glsl */ `\nfloat occupantWeight(float depth) {\n float nearWeight = smoothstep(${OCCUPANT_NEAR_NONE_M.toFixed(1)}, ${OCCUPANT_NEAR_FULL_M.toFixed(1)}, depth);\n float farWeight = 1.0 - smoothstep(${OCCUPANT_FULL_M.toFixed(1)}, ${OCCUPANT_NONE_M.toFixed(1)}, depth);\n return nearWeight * farWeight;\n}`;\n\nconst SOLID_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nin vec3 normal;\nin vec3 color;\nin float floorIndex;\nin float materialIndex;\nuniform mat4 modelMatrix;\nuniform float uFocusFloor; // -1 = show every floor\nuniform float uStructureDetail; // 0 = venue overview, 1 = section/row/seat\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform mat3 normalMatrix;\nout vec3 vColor;\nout vec3 vNormalWorld;\nout vec3 vNormalView;\nout vec3 vPosView;\nout vec3 vPosWorld;\nout float vDim;\nout float vMaterial;\nvoid main() {\n // Per-floor isolation without splitting the merged mesh into a draw call per\n // floor: a floor that is not the focused one is dimmed, not hidden, so the\n // buyer keeps the whole venue as context while looking at one level.\n vDim = (uFocusFloor < -0.5 || abs(floorIndex - uFocusFloor) < 0.5) ? 0.0 : 1.0;\n vMaterial = materialIndex;\n vec4 world = modelMatrix * vec4(position, 1.0);\n vec4 mv = modelViewMatrix * vec4(position, 1.0);\n vPosView = mv.xyz;\n vPosWorld = world.xyz;\n vNormalView = normalize(normalMatrix * normal);\n // World normal drives the key + hemisphere so the lighting stays welded to the\n // venue as the camera orbits (the scene has no non-uniform scale, so mat3 of\n // the model matrix is the correct normal transform).\n vNormalWorld = normalize(mat3(modelMatrix) * normal);\n vColor = color;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst SOLID_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 vColor;\nin vec3 vNormalWorld;\nin vec3 vNormalView;\nin vec3 vPosView;\nin vec3 vPosWorld;\nin float vDim;\nin float vMaterial;\nuniform vec3 uKeyDir; // WORLD space, unit, points at the light\nuniform float uStructureDetail;\nout vec4 fragColor;\nvoid main() {\n // Row treads/risers are honest and useful when the buyer is inside a stand.\n // At whole-venue scale their overlapping centimetre-scale surfaces quantise\n // and alias into camera-dependent radial combs. The clean structural block\n // cap remains, so overview keeps every stand without pretending to show row\n // detail below a useful pixel size.\n float rowDetail = step(1.5, vMaterial);\n if (rowDetail > 0.5 && uStructureDetail < 0.5) discard;\n vec3 N = normalize(vNormalWorld);\n vec3 V = normalize(-vPosView);\n float hemi = 0.5 + 0.5 * N.y; // sky/ground gradient about WORLD up\n float key = max(dot(N, uKeyDir), 0.0); // fixed key — does not orbit with you\n // Low opposite fill so faces turned away from the key keep their form instead\n // of crushing to a single flat value.\n vec3 fillDir = normalize(vec3(-uKeyDir.x, 0.25, -uKeyDir.z));\n float fill = max(dot(N, fillDir), 0.0);\n vec3 base = vColor * (0.52 + 0.34 * hemi) + vColor * key * 0.34 + vColor * fill * 0.10;\n float fres = pow(1.0 - max(dot(normalize(vNormalView), V), 0.0), 3.0);\n base += vec3(0.26, 0.31, 0.38) * fres * 0.35; // cool rim, restrained (view-dependent by design)\n // Bright, saturated authored screen faces should read as powered displays,\n // not painted slabs. Vertex colour is deliberately the material signal here:\n // muted tiers and structure never cross these thresholds. The broad waves\n // supply a restrained procedural LED treatment without textures, downloads,\n // solid fake-light geometry or post-processing.\n float maxChannel = max(max(vColor.r, vColor.g), vColor.b);\n float minChannel = min(min(vColor.r, vColor.g), vColor.b);\n float poweredDisplay = (1.0 - step(0.5, abs(vMaterial - 1.0)))\n * smoothstep(0.58, 0.72, maxChannel)\n * smoothstep(0.28, 0.48, maxChannel - minChannel);\n float verticalFace = 1.0 - smoothstep(0.20, 0.62, abs(N.y));\n float wave = 0.5 + 0.5 * sin(vPosWorld.x * 0.44 + vPosWorld.y * 0.28);\n float localX = vPosWorld.x;\n float localY = vPosWorld.y;\n float beamWest = exp(-pow((localX + 2.6 - localY * 0.22) * 0.44, 2.0));\n float beamEast = exp(-pow((localX - 2.6 + localY * 0.22) * 0.44, 2.0));\n float band = smoothstep(0.02, 0.16, 0.18 - abs(localY - 3.9));\n float focalHalo = exp(-pow(localX * 0.26, 2.0) - pow((localY - 3.9) * 0.38, 2.0));\n vec3 display = vColor * (0.74 + wave * 0.26)\n + vec3(0.10, 0.30, 0.52) * beamWest\n + vec3(0.34, 0.13, 0.42) * beamEast\n + vec3(0.62, 0.46, 0.20) * band * 0.28\n + vec3(0.20, 0.10, 0.34) * focalHalo;\n base = mix(base, display, poweredDisplay * verticalFace * 0.92);\n // Unfocused floors fall back toward the background rather than vanishing.\n base = mix(base, base * 0.45, vDim);\n fragColor = vec4(base, 1.0);\n}`;\n\nconst SEAT_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 position; // quad corner in [-1,1]\nin vec3 iOffset; // per-instance world position\nin vec3 iColor; // per-instance state colour (resolved CPU-side)\nin float iMaxRadius; // per-instance world-radius ceiling (seat pitch derived)\nin float iPhysicalSeat;// 1 = chair; 0 = empty wheelchair bay\nin vec3 iRing; // accommodation ring colour; (0,0,0) = not accessible\nin float iFloor; // owning floor index\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uSeatRadius;\nuniform float uSeatScale;\nuniform float uMinPixels;\nuniform float uPixelToWorld; // (2*tan(fovY/2)) / viewportHeightPx\nuniform float uFocusFloor; // -1 = show every floor\nuniform float uChairFull; // view depth at which the chair mesh is full size\nuniform float uChairNone; // ...and at which it has scaled away entirely\nout vec2 vUv;\nout vec3 vColor;\nout float vBudget; // 1 = dot holds its minimum pixel size, <1 = it cannot\nout vec3 vRing;\nout float vDim;\nout float vDotWeight; // 1 = the dot IS this seat, 0 = the chair has taken over\nout float vPhysicalSeat;\nout float vPixelRadius;\n${CHAIR_WEIGHT_GLSL}\nvoid main() {\n vec4 mv = modelViewMatrix * vec4(iOffset, 1.0);\n float depth = max(-mv.z, 0.001);\n // Hand the seat over to the chair mesh as it comes into range. Derived from\n // this instance's OWN depth rather than from a global uniform, so a row two\n // metres away and the far side of the bowl resolve differently in the same\n // frame — which is the entire point of a ladder over a switch.\n vDotWeight = 1.0 - chairWeight(depth);\n float minR = uMinPixels * depth * uPixelToWorld; // screen-space floor\n // Grow to hold the pixel floor, but never past this seat's own pitch ceiling:\n // unbounded growth is what merges neighbouring rows into one mass at range.\n float r = min(max(uSeatRadius * uSeatScale, minR), iMaxRadius);\n vPixelRadius = r / max(depth * uPixelToWorld, 0.000001);\n // How much of the requested pixel floor the dot could actually afford. Below 1\n // it is losing legibility to distance, and the fragment stage dissolves it\n // toward the tier top rather than letting a sub-pixel dot alias and shimmer.\n vBudget = minR > 0.0 ? clamp(r / minR, 0.0, 1.0) : 1.0;\n mv.xy += position * r; // camera-facing billboard\n // Seat the dot ON the deck instead of centring it in the deck. iOffset is the\n // exact surface point, so half the billboard would otherwise sit below the cap\n // and be clipped by it — and because uMinPixels grows r with distance, no\n // constant world-space lift can prevent that at every range. Offsetting by r\n // along the screen projection of WORLD up is self-correcting: it is full at a\n // grazing view (where slicing happens) and vanishes looking straight down\n // (where the dot must stay centred on its seat).\n mv.xy += normalize(vec3(modelViewMatrix * vec4(0.0, 1.0, 0.0, 0.0))).xy * r;\n vUv = position;\n vColor = iColor;\n vRing = iRing;\n vPhysicalSeat = iPhysicalSeat;\n vDim = (uFocusFloor < -0.5 || abs(iFloor - uFocusFloor) < 0.5) ? 0.0 : 1.0;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst SEAT_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nin vec3 vColor;\nin float vBudget;\nin vec3 vRing;\nin float vDim;\nin float vDotWeight;\nin float vPhysicalSeat;\nin float vPixelRadius;\nuniform float uSeatFade; // fade toward tier colour with distance (LOD)\nuniform float uSeatDetail; // 0 for venue/area overview; 1 in section/row/seat\nuniform vec3 uFadeColor;\nout vec4 fragColor;\nvoid main() {\n if (uSeatDetail < 0.5) discard;\n // Empty wheelchair provision is a square bay, never a round chair marker.\n float d = mix(max(abs(vUv.x), abs(vUv.y)), length(vUv), vPhysicalSeat);\n if (d > 1.0) discard;\n float alpha = smoothstep(1.0, 0.72, d);\n float shade = 0.80 + 0.28 * (0.5 - vUv.y * 0.5); // subtle top-lit\n vec3 c = vColor * shade;\n c = mix(c, uFadeColor, uSeatFade);\n // Accommodation ring — the 3D echo of the coloured ring 2D draws around every\n // accessible seat. Painted INSIDE the dot's own radius rather than outside it,\n // so an accessible seat still respects the row-pitch ceiling and cannot grow\n // into its neighbour just for carrying a ring.\n float ringMask = step(0.001, dot(vRing, vRing));\n float ring = smoothstep(0.58, 0.70, d) * (1.0 - smoothstep(0.90, 1.0, d));\n c = mix(c, vRing, ring * ringMask * 0.95);\n // A dot that can no longer afford its pixel floor dissolves instead of\n // aliasing; the tier cap underneath already carries the section's category\n // tint, so the block reads as coloured seating rather than empty concrete.\n alpha *= smoothstep(0.35, 1.0, vBudget);\n // Do not rasterise inventory states until a dot is large enough to read as a\n // seat rather than a sample in a dense screen-space pattern. This is based on\n // actual projected pixels, so it remains correct for unusually large or small\n // venues where a radius-normalised camera distance is misleading.\n alpha *= smoothstep(2.4, 3.2, vPixelRadius);\n // Inventory states are a section-level decision aid. At whole-venue/area\n // scale the category-tinted structural surface is the honest, stable signal;\n // thousands of enlarged status dots only form a moiré pattern. The journey\n // state enables them when the buyer enters a section, row or exact seat.\n // Whole-venue status detail is below a useful pixel size. Drawing every dot\n // there turns concentric rows into camera-dependent moiré streaks, especially\n // while zooming. uSeatFade already measures this exact LOD transition: let\n // the category-tinted tier carry the overview, then reveal individual live\n // availability as the buyer approaches a section.\n // Seats on an unfocused floor recede with their structure.\n c = mix(c, uFadeColor, vDim * 0.75);\n alpha *= mix(1.0, 0.30, vDim);\n // Yield to the chair. The chair grows out of this exact point, so through the\n // band the dot is always at least as big as the chair inside it and the seat\n // never thins out to nothing in between.\n alpha *= vDotWeight;\n if (alpha <= 0.0) discard;\n fragColor = vec4(c, alpha);\n}`;\n\n// --- Near-field seat chairs ------------------------------------------------\n// One instanced draw over the 72-vertex base mesh in ./seatChair.ts, carrying\n// only the seats the CPU gathered as near (see ./nearField.ts). Lit by the same\n// rig as the solids so a chair reads as part of the room and not as a decal.\nconst CHAIR_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position; // local: x/z in units of the seat radius, y in METRES\nin vec3 normal;\nin float part; // 0 = pedestal, 1 = pad, 2 = back, 3 = body, 4 = head\nin vec3 iOffset; // per-instance world deck point (identical to the dot's)\nin vec3 iColor; // per-instance state colour\nin float iRadius; // per-instance horizontal half-width, world metres\nin float iYaw; // per-instance facing, radians (local +Z -> facing dir)\nin vec3 iRing; // accommodation ring colour; (0,0,0) = not accessible\nin float iFloor;\nin float iSeed; // <0 = seat is empty; else per-person hash in [0,1)\nin float iPhysicalSeat;// 1 = chair; 0 = empty wheelchair bay\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uChairFull;\nuniform float uChairNone;\nuniform float uFocusFloor;\nuniform float uBackRake; // metres of z per metre of rise, above uBackBase\nuniform float uBackBase; // local height at which the back starts\nout vec3 vColor;\nout vec3 vNormalWorld;\nout vec3 vNormalView;\nout vec3 vPosView;\nout float vPart;\nout float vHeight; // local height in metres, for the vertical occlusion ramp\nout vec3 vRing;\nout float vDim;\nout float vOccupant; // 1 = this vertex belongs to a person, not to the chair\nout vec3 vOccupantTint;\n${CHAIR_WEIGHT_GLSL}\n${OCCUPANT_WEIGHT_GLSL}\nvoid main() {\n vec4 anchor = modelViewMatrix * vec4(iOffset, 1.0);\n float viewDepth = -anchor.z;\n float depth = max(viewDepth, 0.001);\n // A stale near-field gather can contain seats now behind the camera after a\n // row→overview move. Treating negative depth as 0.001 made those chairs full\n // size across the near plane, producing the long black zoom streaks.\n float w = viewDepth > 0.05 ? chairWeight(depth) : 0.0;\n // 1. Local units -> world metres. Only x/z scale: narrow rows get narrow\n // chairs, but nobody gets a short one (people are the same height at every\n // seat pitch).\n vec3 p = position;\n // The occupant. Parts 3 and 4 are a person; everything below is furniture.\n //\n // One instanced mesh draws both an empty seat and a taken one, because the\n // alternative — a second geometry and a second draw call gathered per frame —\n // would double the near-field cost to show what is already known per instance.\n // An unoccupied seat collapses its person to a point at the seat, which the\n // rasteriser discards, exactly as the chair itself collapses at w=0.\n float occupant = step(2.5, part);\n float taken = step(0.0, iSeed);\n vOccupant = occupant;\n if (iPhysicalSeat < 0.5) {\n p = vec3(0.0); // sellable space, but no physical chair/person\n } else if (occupant > 0.5) {\n if (taken < 0.5) {\n p = vec3(0.0); // empty seat: no person\n } else {\n // Vary the build so a sold-out row is people rather than a rank of\n // identical mannequins: +/-6% height and +/-8% width off the hash.\n p.y *= 0.94 + 0.12 * iSeed;\n p.xz *= 0.92 + 0.16 * fract(iSeed * 7.13);\n // People only appear once they are large enough to read as people. At\n // section/overview distance their tiny dark geometry aliases into radial\n // streaks; seat dots already communicate held/booked state there.\n p *= occupantWeight(depth);\n }\n }\n p.xz *= iRadius;\n // 2. Lean the back. Done here rather than in the base mesh so the lean is a\n // real angle in METRES — baked into the mesh it would scale with the seat's\n // width and the same chair would lean 20 degrees on a wide stadium row and\n // 6 on a tight theatre one.\n // Only the BACK panel rakes. The old test (part > 1.5) meant \"the back\", and\n // now also catches the occupant — leaning a person by the panel's rule would\n // translate their head backwards by a rake measured from the panel's base.\n float rake = (part > 1.5 && part < 2.5) ? max(p.y - uBackBase, 0.0) * uBackRake : 0.0;\n p.z -= rake;\n // 3. Scale-in. At w=0 the chair is a point at the seat, under a dot at full\n // opacity — which is what makes the handover invisible. sqrt front-loads\n // the growth so the chair is already near full size while the dot is still\n // half there; see chairScale() in lod.ts.\n p *= sqrt(w);\n float c = cos(iYaw), s = sin(iYaw);\n vec3 rp = vec3(p.x * c + p.z * s, p.y, -p.x * s + p.z * c);\n // Normals under the same two transforms, in reverse and inverted-transposed.\n // The xz scale is non-uniform, so an axis-aligned normal does NOT survive it\n // unchanged; and the rake is a shear, whose normal transform adds a y term.\n // Skipping either lights the raked back as though it were still vertical.\n vec3 n = vec3(normal.x / iRadius, normal.y, normal.z / iRadius);\n if (part > 1.5 && part < 2.5) n.y += uBackRake * n.z;\n n = normalize(n);\n vec3 rn = vec3(n.x * c + n.z * s, n.y, -n.x * s + n.z * c);\n vec4 mv = modelViewMatrix * vec4(iOffset + rp, 1.0);\n vPosView = mv.xyz;\n vNormalWorld = rn;\n vNormalView = normalize(mat3(modelViewMatrix) * rn);\n vColor = iColor;\n // Occupant colour, resolved here so the fragment stage needs no extra\n // varyings beyond this one. A person is NOT painted in the seat's state\n // colour: a sold seat is red, and a hall of red people reads as a warning,\n // not an audience. Hair/clothing for the body, a warm tone for the head,\n // both varied by the same per-person hash.\n float t = fract(iSeed * 3.71);\n vec3 clothes = mix(vec3(0.18, 0.22, 0.30), vec3(0.42, 0.29, 0.34), t);\n vec3 skin = mix(vec3(0.52, 0.38, 0.29), vec3(0.86, 0.70, 0.58), fract(iSeed * 11.3));\n vOccupantTint = (part > 3.5) ? skin : clothes;\n vPart = part;\n vHeight = position.y;\n vRing = iRing;\n vDim = (uFocusFloor < -0.5 || abs(iFloor - uFocusFloor) < 0.5) ? 0.0 : 1.0;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst CHAIR_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 vColor;\nin vec3 vNormalWorld;\nin vec3 vNormalView;\nin vec3 vPosView;\nin float vPart;\nin float vHeight;\nin vec3 vRing;\nin float vDim;\nin float vOccupant;\nin vec3 vOccupantTint;\nuniform vec3 uKeyDir;\nuniform vec3 uFadeColor;\nout vec4 fragColor;\nvoid main() {\n vec3 N = normalize(vNormalWorld);\n vec3 V = normalize(-vPosView);\n // The solids' rig, unchanged, so the chairs sit in the venue's light.\n float hemi = 0.5 + 0.5 * N.y;\n float key = max(dot(N, uKeyDir), 0.0);\n vec3 fillDir = normalize(vec3(-uKeyDir.x, 0.25, -uKeyDir.z));\n float fill = max(dot(N, fillDir), 0.0);\n vec3 tint = vColor;\n if (vOccupant > 0.5) tint = vOccupantTint;\n // The accommodation ring, kept legible once the dot (which drew it) is gone:\n // an accessible seat's PEDESTAL is painted in the ring colour, so the marker\n // survives to close range instead of vanishing exactly when the buyer arrives.\n float ringMask = step(0.001, dot(vRing, vRing));\n if (vPart < 0.5 && ringMask > 0.5) tint = vRing;\n // Pad brightest, back a step below it, pedestal darkest. Three untextured\n // boxes only read as one object if they are separated tonally — with a single\n // flat colour the chair silhouettes as a crate.\n // A person is lit as a person, not as upholstery: no part shading, and less\n // of the pad's sheen, so a head does not read as a polished box.\n float partShade = vOccupant > 0.5 ? 0.95 : (vPart < 0.5 ? 0.50 : (vPart < 1.5 ? 1.10 : 0.80));\n // Cheap vertical occlusion: a chair is in a dense row, so the closer a surface\n // sits to the deck the less sky it can actually see. This is the depth cue —\n // without it the pad top, the back and the deck all resolve to the same flat\n // value and the row loses its form entirely.\n // Occupants rise above the 0.92 m chair back, so their ramp uses their own\n // height or every head would clamp to full brightness and float.\n float ao = mix(0.58, 1.0, clamp(vHeight / (vOccupant > 0.5 ? 1.30 : 0.92), 0.0, 1.0));\n vec3 base = tint * partShade * ao * (0.52 + 0.40 * hemi) + tint * key * 0.38 + tint * fill * 0.10;\n float fres = pow(1.0 - max(dot(normalize(vNormalView), V), 0.0), 3.0);\n // A brighter rim than the solids get: it picks out every chair's own edge,\n // which is what stops a block of them merging into one mass up close.\n base += vec3(0.26, 0.31, 0.38) * fres * 0.55;\n base = mix(base, uFadeColor, vDim * 0.75);\n fragColor = vec4(base, 1.0);\n}`;\n\nconst BG_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 position;\nout vec2 vUv;\nvoid main() {\n vUv = position * 0.5 + 0.5;\n gl_Position = vec4(position, 0.999, 1.0);\n}`;\n\nconst BG_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform vec3 uTop;\nuniform vec3 uBottom;\nout vec4 fragColor;\nvoid main() {\n vec3 col = mix(uBottom, uTop, vUv.y);\n vec2 c = vUv - 0.5;\n float vig = 1.0 - dot(c, c) * 0.85; // soft vignette\n fragColor = vec4(col * vig, 1.0);\n}`;\n\n// --- GPU pick pass ---------------------------------------------------------\n// Seats encode gl_InstanceID+1 as an RGB colour (no extra per-instance buffer);\n// solids write pure black + depth first so a seat occluded by a tier reads as\n// \"no hit\". Same billboard maths as the display seat program.\nconst SEAT_PICK_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec3 iOffset;\nin float iMaxRadius;\nin float iPhysicalSeat;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uSeatRadius;\nuniform float uSeatScale;\nuniform float uMinPixels;\nuniform float uPixelToWorld;\nuniform float uChairFull;\nuniform float uChairNone;\nout vec2 vUv;\nout float vPhysicalSeat;\nout float vDotWeight;\nflat out vec3 vPick;\n${CHAIR_WEIGHT_GLSL}\nvoid main() {\n int id = gl_InstanceID + 1; // 0 reserved for no-hit\n vPick = vec3(float(id & 255), float((id >> 8) & 255), float((id >> 16) & 255)) / 255.0;\n vec4 mv = modelViewMatrix * vec4(iOffset, 1.0);\n float depth = max(-mv.z, 0.001);\n vDotWeight = 1.0 - chairWeight(depth);\n float minR = uMinPixels * depth * uPixelToWorld;\n float r = min(max(uSeatRadius * uSeatScale, minR), iMaxRadius);\n mv.xy += position * r;\n // Must match SEAT_VERT exactly, or the hit mask drifts off the drawn dot.\n mv.xy += normalize(vec3(modelViewMatrix * vec4(0.0, 1.0, 0.0, 0.0))).xy * r;\n vUv = position;\n vPhysicalSeat = iPhysicalSeat;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst SEAT_PICK_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nin float vPhysicalSeat;\nin float vDotWeight;\nflat in vec3 vPick;\nout vec4 fragColor;\nvoid main() {\n if (vDotWeight <= 0.02) discard; // invisible dot cannot steal a chair tap\n float d = mix(max(abs(vUv.x), abs(vUv.y)), length(vUv), vPhysicalSeat);\n if (d > 1.0) discard; // hit-mask matches chair/bay shape\n fragColor = vec4(vPick, 1.0);\n}`;\n\n// The chair/person pick pass uses the exact same base geometry and transforms\n// as CHAIR_VERT. Each gathered instance carries its GLOBAL seat index because\n// gl_InstanceID is only the index inside the bounded near-field subset.\nconst CHAIR_PICK_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nin float part;\nin vec3 iOffset;\nin float iRadius;\nin float iYaw;\nin float iSeed;\nin float iPhysicalSeat;\nin float iPickIndex;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uChairFull;\nuniform float uChairNone;\nuniform float uBackRake;\nuniform float uBackBase;\nflat out vec3 vPick;\n${CHAIR_WEIGHT_GLSL}\n${OCCUPANT_WEIGHT_GLSL}\nvoid main() {\n int id = int(iPickIndex + 0.5); // already global index + 1\n vPick = vec3(float(id & 255), float((id >> 8) & 255), float((id >> 16) & 255)) / 255.0;\n vec4 anchor = modelViewMatrix * vec4(iOffset, 1.0);\n float viewDepth = -anchor.z;\n float depth = max(viewDepth, 0.001);\n float w = viewDepth > 0.05 ? chairWeight(depth) : 0.0;\n vec3 p = position;\n float occupant = step(2.5, part);\n float taken = step(0.0, iSeed);\n if (iPhysicalSeat < 0.5) {\n p = vec3(0.0);\n } else if (occupant > 0.5) {\n if (taken < 0.5) {\n p = vec3(0.0);\n } else {\n p.y *= 0.94 + 0.12 * iSeed;\n p.xz *= 0.92 + 0.16 * fract(iSeed * 7.13);\n p *= occupantWeight(depth);\n }\n }\n p.xz *= iRadius;\n float rake = (part > 1.5 && part < 2.5) ? max(p.y - uBackBase, 0.0) * uBackRake : 0.0;\n p.z -= rake;\n p *= sqrt(w);\n float c = cos(iYaw), s = sin(iYaw);\n vec3 rp = vec3(p.x * c + p.z * s, p.y, -p.x * s + p.z * c);\n gl_Position = projectionMatrix * modelViewMatrix * vec4(iOffset + rp, 1.0);\n}`;\n\nconst CHAIR_PICK_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nflat in vec3 vPick;\nout vec4 fragColor;\nvoid main() { fragColor = vec4(vPick, 1.0); }`;\n\nconst PICK_DEPTH_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst PICK_DEPTH_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nout vec4 fragColor;\nvoid main() { fragColor = vec4(0.0, 0.0, 0.0, 1.0); }`;\n\nexport function createSeatPickProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: SEAT_PICK_VERT,\n fragment: SEAT_PICK_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n uniforms: {\n uSeatRadius: { value: SEAT_DOT_RADIUS_M },\n uSeatScale: { value: 1 },\n uMinPixels: { value: SEAT_MIN_PIXELS_NEAR },\n uPixelToWorld: { value: 0.002 },\n uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n },\n });\n}\n\nexport function createChairPickProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: CHAIR_PICK_VERT,\n fragment: CHAIR_PICK_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n uniforms: {\n uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n uBackRake: { value: BACK_RAKE_SLOPE },\n uBackBase: { value: BACK_BASE_M },\n },\n });\n}\n\n/** Occluder pass: solids to black + depth so occluded seats read as no-hit. */\nexport function createPickDepthProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: PICK_DEPTH_VERT,\n fragment: PICK_DEPTH_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n });\n}\n\nexport function createSolidProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n // No backface culling: free-hand section polygons are stored in raw click\n // order (either winding), so a culled solid would render see-through. The\n // shader lights both faces and closed opaque prisms + depth test keep\n // overdraw negligible; extrudePrism also normalises winding as a belt.\n vertex: SOLID_VERT,\n fragment: SOLID_FRAG,\n cullFace: false,\n depthTest: true,\n depthWrite: true,\n uniforms: {\n // High and off-axis, in world space: reads as a house rig rather than a\n // headlamp welded to the camera.\n uKeyDir: { value: new Float32Array([0.38, 0.86, 0.34]) },\n uFocusFloor: { value: -1 },\n uStructureDetail: { value: 0 },\n },\n });\n}\n\nexport function createSeatProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: SEAT_VERT,\n fragment: SEAT_FRAG,\n transparent: true,\n depthTest: true,\n depthWrite: false,\n cullFace: false,\n uniforms: {\n uSeatRadius: { value: SEAT_DOT_RADIUS_M },\n uSeatScale: { value: 1 },\n uMinPixels: { value: SEAT_MIN_PIXELS_NEAR },\n uPixelToWorld: { value: 0.002 },\n uSeatFade: { value: 0 },\n uSeatDetail: { value: 0 },\n uFocusFloor: { value: -1 },\n uFadeColor: { value: new Float32Array([0.32, 0.37, 0.43]) },\n uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n },\n });\n}\n\n/**\n * The near-field chair program.\n *\n * OPAQUE and depth-writing, unlike the dots. That is the reason the transition\n * is a scale-in and not a cross-fade: a translucent chair either writes depth\n * and punches its own silhouette through the chairs behind it, or does not and\n * shows its own back panel through its own seat pad. Growing an opaque chair out\n * of the seat point avoids both, and it composes correctly with the dot, which\n * still draws afterwards in the transparent pass.\n *\n * `cullFace` stays off to match the rest of the renderer — the meshes are closed\n * opaque boxes, so the depth test already suppresses the back faces' cost, and a\n * winding mistake here would silently blank the chairs rather than fail loudly.\n */\nexport function createChairProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: CHAIR_VERT,\n fragment: CHAIR_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n uniforms: {\n uKeyDir: { value: new Float32Array([0.38, 0.86, 0.34]) },\n uFadeColor: { value: new Float32Array([0.32, 0.37, 0.43]) },\n uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n uBackRake: { value: BACK_RAKE_SLOPE },\n uBackBase: { value: BACK_BASE_M },\n uFocusFloor: { value: -1 },\n },\n });\n}\n\nexport function createBackgroundProgram(gl: OGLRenderingContext, top: number[], bottom: number[]): Program {\n return new Program(gl, {\n vertex: BG_VERT,\n fragment: BG_FRAG,\n depthTest: false,\n depthWrite: false,\n cullFace: false,\n uniforms: {\n uTop: { value: new Float32Array(top) },\n uBottom: { value: new Float32Array(bottom) },\n },\n });\n}\n","/**\n * GPU color-pick (Slice 2). On a TAP (not hover, not drag) the seat instance\n * index is rendered as RGB into a small scissored offscreen target and a single\n * pixel is read back → O(1) regardless of seat count. Solids are drawn first as\n * black + depth so a seat occluded by a tier reads as \"no hit\".\n *\n * The pick meshes reuse the display geometry buffers (iOffset / position), so no\n * per-seat data is duplicated on the GPU.\n */\n\nimport { Geometry, Mesh, Program, RenderTarget, Transform } from 'ogl';\nimport type { Camera, OGLRenderingContext, Renderer } from 'ogl';\nimport { createChairPickProgram, createPickDepthProgram, createSeatPickProgram } from '../scene/materials';\n\nimport { pickNearestFromBuffer } from './encode';\n\nconst SYNC_KEYS = ['uSeatRadius', 'uSeatScale', 'uMinPixels', 'uPixelToWorld'] as const;\n\nexport class PickPipeline {\n private gl: OGLRenderingContext;\n private renderer: Renderer;\n private seatProg: Program;\n private chairProg: Program;\n private depthProg: Program;\n private seatScene = new Transform();\n private chairScene = new Transform();\n private solidScene = new Transform();\n private target: RenderTarget | null = null;\n private maxIndex: number;\n\n /** Display clear colour to restore after the pick pass (theme-dependent). */\n private restoreClear: readonly number[] = [0, 0, 0];\n\n /** Follow the theme's background when the scene is (re)built. */\n setRestoreClear(rgb: readonly number[]): void {\n this.restoreClear = [rgb[0], rgb[1], rgb[2]];\n }\n\n constructor(renderer: Renderer, seatGeo: Geometry, chairGeo: Geometry, solidGeo: Geometry, seatCount: number) {\n this.renderer = renderer;\n this.gl = renderer.gl;\n this.maxIndex = seatCount;\n this.seatProg = createSeatPickProgram(this.gl);\n this.chairProg = createChairPickProgram(this.gl);\n this.depthProg = createPickDepthProgram(this.gl);\n const seatMesh = new Mesh(this.gl, { geometry: seatGeo, program: this.seatProg });\n seatMesh.frustumCulled = false;\n seatMesh.setParent(this.seatScene);\n const chairMesh = new Mesh(this.gl, { geometry: chairGeo, program: this.chairProg });\n chairMesh.frustumCulled = false;\n chairMesh.setParent(this.chairScene);\n const solidMesh = new Mesh(this.gl, { geometry: solidGeo, program: this.depthProg });\n solidMesh.frustumCulled = false;\n solidMesh.setParent(this.solidScene);\n }\n\n /** Match the display seat sizing so the pick mask lines up with the dots. */\n syncFromSeatProgram(seatProgram: Program): void {\n for (const k of SYNC_KEYS) this.seatProg.uniforms[k].value = seatProgram.uniforms[k].value;\n }\n\n private ensureTarget(): RenderTarget {\n const w = this.gl.drawingBufferWidth;\n const h = this.gl.drawingBufferHeight;\n if (this.target && (this.target.width !== w || this.target.height !== h)) {\n this.destroyTarget();\n }\n if (!this.target) {\n this.target = new RenderTarget(this.gl, { width: w, height: h, depth: true });\n }\n return this.target;\n }\n\n private destroyTarget(): void {\n if (!this.target) return;\n const gl = this.gl;\n if (this.target.buffer) gl.deleteFramebuffer(this.target.buffer);\n for (const t of this.target.textures ?? []) if (t.texture) gl.deleteTexture(t.texture);\n if (this.target.depthBuffer) gl.deleteRenderbuffer(this.target.depthBuffer);\n this.target = null;\n }\n\n /**\n * Read back the seat instance index NEAREST framebuffer pixel (px, py), or -1.\n * `radius` is the tap tolerance in buffer px: a box of side (2·radius+1) is\n * rendered + read so a tap that lands between the ~2px overview dots still\n * finds the closest seat. px/py/radius are bottom-left-origin buffer pixels.\n */\n pick(camera: Camera, px: number, py: number, radius: number): number {\n const gl = this.gl;\n const target = this.ensureTarget();\n const bw = gl.drawingBufferWidth;\n const bh = gl.drawingBufferHeight;\n const x0 = Math.max(0, px - radius);\n const y0 = Math.max(0, py - radius);\n const boxW = Math.max(1, Math.min(bw, px + radius + 1) - x0);\n const boxH = Math.max(1, Math.min(bh, py + radius + 1) - y0);\n\n gl.enable(gl.SCISSOR_TEST);\n gl.scissor(x0, y0, boxW, boxH);\n // Clear the pick target to TRUE BLACK so empty + occluded pixels decode to\n // no-hit structurally (not by a range guard). Occluders drawn black + depth\n // first, then seats (pick colours) depth-tested.\n // Restore whatever the display clear colour currently is — the theme may have\n // replaced it, and restoring the library default would flash grey on a\n // white-labelled chart every time the pointer moves.\n const [br, bg, bb] = this.restoreClear;\n gl.clearColor(0, 0, 0, 1);\n this.renderer.render({ scene: this.solidScene, camera, target, clear: true });\n this.renderer.render({ scene: this.seatScene, camera, target, clear: false });\n // Exact visible chair/person geometry wins over a projected seat behind it.\n // The shared geometry carries global ids for the bounded gathered subset.\n this.renderer.render({ scene: this.chairScene, camera, target, clear: false });\n gl.clearColor(br, bg, bb, 1); // restore the display clear colour\n gl.disable(gl.SCISSOR_TEST);\n\n const buf = new Uint8Array(boxW * boxH * 4);\n this.renderer.bindFramebuffer(target);\n gl.readPixels(x0, y0, boxW, boxH, gl.RGBA, gl.UNSIGNED_BYTE, buf);\n this.renderer.bindFramebuffer();\n\n return pickNearestFromBuffer(buf, boxW, boxH, px - x0, py - y0, this.maxIndex);\n }\n\n dispose(): void {\n this.destroyTarget();\n this.seatProg.remove();\n this.chairProg.remove();\n this.depthProg.remove();\n }\n}\n","/**\n * GPU color-pick id encoding — pure, DOM-free, so the round-trip and the tap →\n * framebuffer pixel maths are unit-testable. The seat instance index is offset\n * by +1 so id 0 is reserved for \"no hit\" (the cleared black background).\n */\n\n/** instanceIndex → normalised RGB (0..1) the pick shader writes. */\nexport function encodePickId(instanceIndex: number): [number, number, number] {\n const id = instanceIndex + 1;\n return [(id & 255) / 255, ((id >> 8) & 255) / 255, ((id >> 16) & 255) / 255];\n}\n\n/** RGB bytes (0..255) read back → instanceIndex, or -1 for the no-hit clear. */\nexport function decodePickRGB(r: number, g: number, b: number): number {\n const id = r + (g << 8) + (b << 16);\n return id === 0 ? -1 : id - 1;\n}\n\n/**\n * Scan a readback window (RGBA, bottom-left origin, row-major) for the seat hit\n * NEAREST the tap centre. A single tap on a low-res overview lands between ~2px\n * dots, so we read a small box and pick the closest non-empty seat instead of a\n * single pixel. `centerI/centerJ` are the tap's box-local pixel coords.\n * `maxIndex` bounds valid indices (defence against a stray decode).\n */\nexport function pickNearestFromBuffer(\n pixels: Uint8Array,\n boxW: number,\n boxH: number,\n centerI: number,\n centerJ: number,\n maxIndex: number,\n): number {\n let best = -1;\n let bestDist = Infinity;\n for (let j = 0; j < boxH; j++) {\n for (let i = 0; i < boxW; i++) {\n const o = (j * boxW + i) * 4;\n const idx = decodePickRGB(pixels[o], pixels[o + 1], pixels[o + 2]);\n if (idx < 0 || idx >= maxIndex) continue;\n const di = i - centerI;\n const dj = j - centerJ;\n const d = di * di + dj * dj;\n if (d < bestDist) { bestDist = d; best = idx; }\n }\n }\n return best;\n}\n\n/**\n * Map a tap in CSS pixels (relative to the canvas bounding rect) to a\n * bottom-left-origin framebuffer pixel, clamped in range. `rect` is the canvas\n * getBoundingClientRect; `dpr` the renderer device-pixel-ratio.\n */\nexport function pickPixelCoords(\n clientX: number,\n clientY: number,\n rect: { left: number; top: number; width: number; height: number },\n dpr: number,\n bufferWidth: number,\n bufferHeight: number,\n): { x: number; y: number } {\n const cssX = clientX - rect.left;\n const cssY = clientY - rect.top;\n const x = Math.round(cssX * dpr);\n // WebGL framebuffer origin is bottom-left → flip Y.\n const y = Math.round((rect.height - cssY) * dpr);\n return {\n x: Math.max(0, Math.min(bufferWidth - 1, x)),\n y: Math.max(0, Math.min(bufferHeight - 1, y)),\n };\n}\n","/**\n * Pure selection-state diffing. Selection is a colour layer over availability:\n * a selected seat shows the 'selected' colour and, on deselect, restores the\n * base availability state it had when it was selected (remembered in `prev`).\n * Kept DOM/GPU-free so the transitions are unit-testable.\n */\n\nimport { SEAT_STATES, seatStateIndex, type SeatState3D } from '../palette';\n\nexport interface SelectionUpdate {\n seatId: string;\n state: SeatState3D;\n}\n\n/**\n * Reconcile an availability update against the current selection. A selected\n * seat that changes availability must STAY 'selected' on screen while its\n * remembered base state is updated (so a later deselect restores the CURRENT\n * availability, not the pre-change one). Mutates `selection` in place and\n * returns the updates that should actually be written to iState (the\n * non-selected ones — selected seats keep their 'selected' colour).\n */\nexport function mergeAvailabilityIntoSelection(\n selection: Map<string, number>,\n updates: SelectionUpdate[],\n): SelectionUpdate[] {\n const passthrough: SelectionUpdate[] = [];\n for (const u of updates) {\n if (selection.has(u.seatId)) selection.set(u.seatId, seatStateIndex(u.state));\n else passthrough.push(u);\n }\n return passthrough;\n}\n\nexport interface SelectionDiff {\n updates: SelectionUpdate[];\n /** New seatId → remembered base-state index map. */\n next: Map<string, number>;\n}\n\n/**\n * Diff the current selection (`prev`: seatId → base-state index) against the\n * desired seat ids. `baseStateIndex` reads the seat's CURRENT state index (used\n * only for newly-selected seats, which are not yet recoloured).\n */\nexport function diffSelection(\n prev: Map<string, number>,\n desiredIds: string[],\n baseStateIndex: (seatId: string) => number | undefined,\n): SelectionDiff {\n const desired = new Set<string>();\n for (const id of desiredIds) {\n if (prev.has(id) || baseStateIndex(id) !== undefined) desired.add(id);\n }\n const next = new Map(prev);\n const updates: SelectionUpdate[] = [];\n\n // Deselect: restore base availability for ids leaving the selection.\n for (const [id, base] of prev) {\n if (!desired.has(id)) {\n updates.push({ seatId: id, state: SEAT_STATES[base] ?? 'available' });\n next.delete(id);\n }\n }\n // Select: remember base state, then recolour as selected.\n for (const id of desired) {\n if (next.has(id)) continue; // already selected → unchanged\n const base = baseStateIndex(id);\n if (base === undefined) continue;\n next.set(id, base);\n updates.push({ seatId: id, state: 'selected' });\n }\n return { updates, next };\n}\n","/**\n * Slice 3 — the purchase-moment fly-to-seat cinematic controller. One continuous\n * shot from the venue overview into the picked seat: a catmull-rom position\n * spline (smootherstep timing) + a look-at quaternion slerp whose orientation\n * slightly LEADS the position, with a gentle FOV push-in. The pure maths lives\n * in cinematicMath.ts (tested); this drives the OGL camera with it.\n *\n * Technique locked in docs/3d-usp-strategy-2026-07-23.md §3.\n */\n\nimport { Quat, Vec3 } from 'ogl';\nimport type { Camera } from 'ogl';\nimport {\n FLIGHT_DURATION_MS, FOV_END, FOV_START, ORIENTATION_LEAD,\n sampleFlight, orientationLeadT, type Vec3Arr,\n} from './cinematicMath';\n\nexport {\n FLIGHT_DURATION_MS, FOV_START, FOV_END, ORIENTATION_LEAD,\n smootherstep, orientationLeadT, catmullRom, buildWaypoints, sampleFlight,\n type Vec3Arr, type FlightSample,\n} from './cinematicMath';\n\n/** Look-at quaternion from `from` toward `to`, computed via the OGL camera\n * (save/restore) — no manual quat maths. Synchronous, no render between. */\nexport function lookAtQuat(camera: Camera, from: Vec3Arr, to: Vec3Arr): Quat {\n const savedPos = camera.position.clone();\n const savedQuat = new Quat().copy(camera.quaternion);\n camera.position.set(from[0], from[1], from[2]);\n camera.lookAt(new Vec3(to[0], to[1], to[2]));\n const q = new Quat().copy(camera.quaternion);\n camera.position.copy(savedPos);\n camera.quaternion.copy(savedQuat);\n return q;\n}\n\n/** Drives the OGL camera along a flight. Integrated with the render loop: the\n * loop calls update() each frame while active. */\nexport class Cinematic {\n active = false;\n private camera: Camera;\n private waypoints: Vec3Arr[] = [];\n private startQuat = new Quat();\n private endQuat = new Quat();\n private outQuat = new Quat();\n private startTime = 0;\n private duration = FLIGHT_DURATION_MS;\n private endFov = FOV_END;\n private resolveFn: (() => void) | null = null;\n\n constructor(camera: Camera) {\n this.camera = camera;\n }\n\n /** Begin (or retarget) a flight. Resolves when it lands or is cancelled. */\n start(\n waypoints: Vec3Arr[],\n startQuat: Quat,\n endQuat: Quat,\n duration = FLIGHT_DURATION_MS,\n endFov = FOV_END,\n ): Promise<void> {\n this.settle(); // resolve any in-flight promise before retargeting\n this.waypoints = waypoints;\n this.startQuat.copy(startQuat);\n this.endQuat.copy(endQuat);\n this.duration = duration;\n this.endFov = endFov;\n this.startTime = performance.now();\n this.active = true;\n return new Promise<void>((res) => { this.resolveFn = res; });\n }\n\n /** Advance the flight, mutating the camera. Returns true while still flying. */\n update(now: number): boolean {\n if (!this.active) return false;\n const u = Math.min(1, (now - this.startTime) / this.duration);\n const { pos, fov, eased } = sampleFlight(this.waypoints, u, FOV_START, this.endFov);\n this.camera.position.set(pos[0], pos[1], pos[2]);\n this.outQuat.copy(this.startQuat).slerp(this.endQuat, orientationLeadT(eased, ORIENTATION_LEAD));\n this.camera.quaternion.copy(this.outQuat);\n this.camera.fov = fov;\n this.camera.updateProjectionMatrix();\n if (u >= 1) { this.settle(); return false; }\n return true;\n }\n\n /** Stop where we are (no snap) — the camera keeps its current pose. */\n cancel(): void {\n this.settle();\n }\n\n private settle(): void {\n this.active = false;\n const r = this.resolveFn;\n this.resolveFn = null;\n if (r) r();\n }\n}\n","/**\n * Pure, DOM/GPU-free maths for the fly-to-seat cinematic — spline, timing, and\n * waypoint construction. Split from cinematic.ts (which imports OGL) so this is\n * unit-testable in a plain runtime.\n */\n\nexport const FLIGHT_DURATION_MS = 2500;\nexport const FOV_START = 35;\nexport const FOV_END = 28;\n/** Orientation t leads position t by this much (clamped) — aim before arrival. */\nexport const ORIENTATION_LEAD = 0.15;\n/** Final approach: this far behind the seat (≈2–3 rows) and above its eye. */\nconst BACK_M = 2.5;\nconst ABOVE_EYE_M = 1.5;\n\nexport type Vec3Arr = [number, number, number];\n\nfunction sub(a: Vec3Arr, b: Vec3Arr): Vec3Arr { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; }\nfunction add(a: Vec3Arr, b: Vec3Arr): Vec3Arr { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; }\nfunction scale(a: Vec3Arr, k: number): Vec3Arr { return [a[0] * k, a[1] * k, a[2] * k]; }\nfunction norm(a: Vec3Arr): Vec3Arr {\n const l = Math.hypot(a[0], a[1], a[2]);\n return l > 1e-6 ? [a[0] / l, a[1] / l, a[2] / l] : [0, 0, 0];\n}\n\n/** Smootherstep (Ken Perlin) — zero 1st & 2nd derivatives at the ends. */\nexport function smootherstep(t: number): number {\n const x = Math.max(0, Math.min(1, t));\n return x * x * x * (x * (x * 6 - 15) + 10);\n}\n\n/** Orientation t = position t nudged ahead by `lead`, clamped to [0,1]. */\nexport function orientationLeadT(posT: number, lead: number): number {\n return Math.max(0, Math.min(1, posT + lead));\n}\n\n/**\n * Uniform multi-segment Catmull-Rom passing THROUGH every waypoint. `u` in\n * [0,1] spans the whole path; endpoints are duplicated for tangents.\n */\nexport function catmullRom(points: Vec3Arr[], u: number): Vec3Arr {\n const n = points.length;\n if (n === 0) return [0, 0, 0];\n if (n === 1) return [...points[0]];\n const cu = Math.max(0, Math.min(1, u));\n const segCount = n - 1;\n let seg = Math.floor(cu * segCount);\n if (seg >= segCount) seg = segCount - 1;\n const t = cu * segCount - seg;\n const p0 = points[Math.max(0, seg - 1)];\n const p1 = points[seg];\n const p2 = points[seg + 1];\n const p3 = points[Math.min(n - 1, seg + 2)];\n const t2 = t * t;\n const t3 = t2 * t;\n const out: Vec3Arr = [0, 0, 0];\n for (let i = 0; i < 3; i++) {\n out[i] = 0.5 * (\n 2 * p1[i]\n + (-p0[i] + p2[i]) * t\n + (2 * p0[i] - 5 * p1[i] + 4 * p2[i] - p3[i]) * t2\n + (-p0[i] + 3 * p1[i] - 3 * p2[i] + p3[i]) * t3\n );\n }\n return out;\n}\n\n/**\n * Build the flight waypoints from the current camera position to a seat. The\n * mid arc is pushed OUTSIDE the venue bounds sphere and high up, so the swoop\n * never clips through the tier solids; the final anchor sits behind + above the\n * seat, looking toward the focal point.\n */\nexport function buildWaypoints(\n start: Vec3Arr,\n seatEye: Vec3Arr,\n focal: Vec3Arr,\n center: Vec3Arr,\n radius: number,\n): { waypoints: Vec3Arr[]; finalPos: Vec3Arr } {\n let away = norm(sub(seatEye, focal)); // \"behind\" the seat (away from stage)\n if (away[0] === 0 && away[1] === 0 && away[2] === 0) away = [0, 0, 1];\n const finalPos = add(add(seatEye, scale(away, BACK_M)), [0, ABOVE_EYE_M, 0]);\n\n const horiz: Vec3Arr = [seatEye[0] - center[0], 0, seatEye[2] - center[2]];\n let hn = norm(horiz);\n if (hn[0] === 0 && hn[2] === 0) hn = [away[0], 0, away[2]];\n const r = Math.max(1, radius);\n const arc: Vec3Arr = [\n center[0] + hn[0] * r * 1.3,\n center[1] + r * 0.75,\n center[2] + hn[2] * r * 1.3,\n ];\n return { waypoints: [start, arc, finalPos], finalPos };\n}\n\nexport interface FlightSample {\n pos: Vec3Arr;\n fov: number;\n /** Eased position parameter (drives the orientation lead). */\n eased: number;\n}\n\n/** Sample the flight at raw parameter `u` in [0,1]. */\nexport function sampleFlight(waypoints: Vec3Arr[], u: number, fovStart = FOV_START, fovEnd = FOV_END): FlightSample {\n const eased = smootherstep(u);\n const pos = catmullRom(waypoints, eased);\n const fovT = smootherstep(Math.max(0, Math.min(1, (u - 0.66) / 0.34))); // push-in over final third\n return { pos, fov: fovStart + (fovEnd - fovStart) * fovT, eased };\n}\n","import type { Point } from '../../core/types';\nimport { M } from '../scene/geometry';\nimport type { Vec3Arr } from './cinematicMath';\n\n/** Seated eye height above the rendered seat deck. */\nexport const SEAT_EYE_ABOVE_DECK_M = 1.02;\n\n/** The authored focal is a floor point; aim at show/stage eye level. */\nexport const FOCAL_LOOK_HEIGHT_M = 1.5;\n\n/**\n * Resolve the truthful pose for an in-scene view from a selected seat.\n *\n * The cinematic deliberately arrives behind and above this pose. Entering 360\n * is the point at which the camera must move to the buyer's actual seated eye,\n * while still looking at the focal resolved for that seat's zone/floor.\n */\nexport function seatViewPose(\n seatDeckWorld: Vec3Arr,\n focalPoint: Point,\n floorBaseHeightM = 0,\n authoredEyeHeightM?: number,\n): { eye: Vec3Arr; focal: Vec3Arr } {\n // ExpandedSeat.eyeHeightM is measured from the event/floor datum and is the\n // authoritative chart contract when present. The rendered seat instance has\n // a small visual clearance baked into its Y, so adding the legacy eye offset\n // to that decorated position would silently shift a verified camera.\n const eyeY = Number.isFinite(authoredEyeHeightM)\n ? floorBaseHeightM + authoredEyeHeightM!\n : seatDeckWorld[1] + SEAT_EYE_ABOVE_DECK_M;\n return {\n eye: [seatDeckWorld[0], eyeY, seatDeckWorld[2]],\n focal: [\n focalPoint.x * M,\n floorBaseHeightM + FOCAL_LOOK_HEIGHT_M,\n focalPoint.y * M,\n ],\n };\n}\n","/** Progressive delivery policy shared by the WebGL and DOM panorama viewers. */\n\nexport interface PanoramaDeliverySource {\n url: string;\n previewUrl?: string;\n sourceWidth?: number;\n sourceHeight?: number;\n previewWidth?: number;\n previewHeight?: number;\n}\n\nexport interface PanoramaDeliveryConstraints {\n saveData?: boolean;\n maxTextureSize?: number;\n maxDecodedBytes?: number;\n}\n\nexport interface PanoramaDeliveryPlan {\n initialUrl: string;\n upgradeUrl?: string;\n initialWidth?: number;\n initialHeight?: number;\n reason: 'full-only' | 'progressive' | 'save-data' | 'texture-limit' | 'memory-limit';\n}\n\n/** Approximate GPU allocation including a complete mip chain (4/3 overhead). */\nexport function estimatePanoramaTextureBytes(width: number, height: number): number {\n if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return 0;\n return Math.ceil(width * height * 4 * (4 / 3));\n}\n\n/** Conservative per-panorama budget, raised only on devices reporting ample RAM. */\nexport function panoramaTextureBudgetBytes(deviceMemoryGb?: number): number {\n const mib = deviceMemoryGb !== undefined && deviceMemoryGb <= 2\n ? 32\n : deviceMemoryGb !== undefined && deviceMemoryGb >= 8\n ? 192\n : 96;\n return mib * 1024 * 1024;\n}\n\nexport function browserPanoramaConstraints(maxTextureSize?: number): PanoramaDeliveryConstraints {\n const nav = typeof navigator === 'undefined'\n ? undefined\n : navigator as Navigator & { deviceMemory?: number; connection?: { saveData?: boolean } };\n return {\n saveData: nav?.connection?.saveData === true,\n maxTextureSize,\n maxDecodedBytes: panoramaTextureBudgetBytes(nav?.deviceMemory),\n };\n}\n\n/** Select the first image and whether a later sharp-image upgrade is safe. */\nexport function planPanoramaDelivery(\n source: PanoramaDeliverySource,\n constraints: PanoramaDeliveryConstraints = {},\n): PanoramaDeliveryPlan {\n const previewUrl = source.previewUrl?.trim();\n if (!previewUrl || previewUrl === source.url) {\n return {\n initialUrl: source.url,\n initialWidth: source.sourceWidth,\n initialHeight: source.sourceHeight,\n reason: 'full-only',\n };\n }\n\n const initial = {\n initialUrl: previewUrl,\n initialWidth: source.previewWidth,\n initialHeight: source.previewHeight,\n };\n if (constraints.saveData) return { ...initial, reason: 'save-data' };\n if (constraints.maxTextureSize !== undefined && source.sourceWidth !== undefined && source.sourceHeight !== undefined\n && Math.max(source.sourceWidth, source.sourceHeight) > constraints.maxTextureSize) {\n return { ...initial, reason: 'texture-limit' };\n }\n if (constraints.maxDecodedBytes !== undefined && source.sourceWidth !== undefined && source.sourceHeight !== undefined\n && estimatePanoramaTextureBytes(source.sourceWidth, source.sourceHeight) > constraints.maxDecodedBytes) {\n return { ...initial, reason: 'memory-limit' };\n }\n return { ...initial, upgradeUrl: source.url, reason: 'progressive' };\n}\n\nfunction abortError(): DOMException {\n return new DOMException('Panorama image load aborted', 'AbortError');\n}\n\n/** Decode before resolving so callers never upload or reveal a partial image. */\nexport function loadPanoramaImage(url: string, signal?: AbortSignal): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) { reject(abortError()); return; }\n const image = new Image();\n image.crossOrigin = 'anonymous';\n let settled = false;\n const cleanup = (): void => {\n image.onload = null;\n image.onerror = null;\n signal?.removeEventListener('abort', onAbort);\n };\n const finish = (error?: unknown): void => {\n if (settled) return;\n settled = true;\n cleanup();\n if (error) reject(error);\n else resolve(image);\n };\n const onAbort = (): void => {\n image.src = '';\n finish(abortError());\n };\n image.onload = () => {\n const decoded = typeof image.decode === 'function' ? image.decode() : Promise.resolve();\n void decoded.then(() => finish(), () => finish());\n };\n image.onerror = () => finish(new Error('panorama_image_failed'));\n signal?.addEventListener('abort', onAbort, { once: true });\n image.src = url;\n });\n}\n\n/** Let the preview paint before starting a potentially large decode. */\nexport function schedulePanoramaUpgrade(work: () => void): () => void {\n const host = globalThis as typeof globalThis & {\n requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => number;\n cancelIdleCallback?: (id: number) => void;\n };\n if (host.requestIdleCallback) {\n const id = host.requestIdleCallback(work, { timeout: 1200 });\n return () => host.cancelIdleCallback?.(id);\n }\n const id = globalThis.setTimeout(work, 32);\n return () => globalThis.clearTimeout(id);\n}\n","/**\n * Slice 3 hand-off — the DOM panorama overlay the fly-to-seat cinematic\n * dissolves into. Decoupled by design: the CALLER supplies the equirectangular\n * image (via mountVenue3D's getSeatView), so view3d never imports the app's\n * panorama generator and the chunk stays lean.\n *\n * Technique (mirrors SeatPicker.openSeatView, reimplemented small): an equirect\n * image panned with `repeat-x`; the initial horizontal offset is set so the\n * panorama's bearing matches the final camera yaw — the dissolve reads as the\n * same view sharpening, not a cut. CSS opacity fade is compositor-only.\n */\n\nimport type { SeatViewCoverage } from '../../core/types';\nimport {\n browserPanoramaConstraints,\n loadPanoramaImage,\n planPanoramaDelivery,\n schedulePanoramaUpgrade,\n} from '../../view/panoramaDelivery';\n\nexport interface SeatView {\n url: string;\n /** Lightweight equirect used for first paint before `url` replaces it. */\n previewUrl?: string;\n /**\n * Optional authenticated byte-to-object-URL resolver. The buyer picker uses\n * this for a deferred sharp-image upgrade so progressive view media never\n * needs a bearer in its URL.\n */\n resolveUrl?: (url: string) => Promise<string | null>;\n sourceWidth?: number;\n sourceHeight?: number;\n previewWidth?: number;\n previewHeight?: number;\n /**\n * True when `url` is a SYNTHESISED panorama rather than a real photograph.\n *\n * It decides which of two very different things the buyer gets, so the caller\n * must say rather than the renderer guess. A generated equirect is a picture\n * of a 3D scene the renderer already has: at 2048×1024 it covers 106° of\n * horizontal view in ~600 source pixels, which a 3072-device-pixel viewport\n * magnifies FIVE times, and no amount of art survives that. So a generated\n * view is not drawn as an image at all — the camera simply looks around the\n * real scene, which has no resolution ceiling.\n *\n * A real photo has no such substitute and goes on the sphere, where a 5760 or\n * 8192-wide capture has three to four times the detail to give.\n */\n generated?: boolean;\n /** Explicit media provenance; avoids inferring buyer truth from source copy. */\n mediaKind?: 'photo' | 'demo-render' | 'model';\n /** Bearing (deg, 0 = facing the focal/stage) the panorama should open centred\n * on, to match the camera's final yaw. Default 0 (both face the stage). */\n initialBearingDeg?: number;\n /** Initial vertical orientation. Positive looks up; clamped to the viewer's\n * safe pitch range. Uploaded captures use this to match the live seat pose. */\n initialPitchDeg?: number;\n /** How specifically this image represents the selected inventory unit. */\n coverage?: SeatViewCoverage;\n /** ISO capture date/time when known. Unknown is preferable to invented age. */\n capturedAt?: string;\n /** Optional buyer-safe provenance, for example \"Provided by the venue\". */\n sourceLabel?: string;\n}\n\n/** Honest, compact buyer disclosure for the panorama currently on screen. */\nexport function seatViewDisclosure(view: SeatView): string {\n const demo = view.mediaKind === 'demo-render';\n const coverage = view.generated || view.mediaKind === 'model'\n ? 'Live 3D · chart-derived seat-eye'\n : demo\n ? view.coverage === 'exact-seat'\n ? 'AI-generated exact-seat demo'\n : view.coverage === 'row-representative'\n ? 'AI-generated representative row demo'\n : view.coverage === 'section-representative'\n ? 'AI-generated representative section demo'\n : 'AI-generated illustrative venue demo'\n : view.coverage === 'exact-seat'\n ? 'Exact seat photo'\n : view.coverage === 'row-representative'\n ? 'Representative row view'\n : view.coverage === 'section-representative'\n ? 'Representative section view'\n : view.coverage === 'venue-representative'\n ? 'Representative venue view'\n : 'Venue photo';\n const year = view.capturedAt && /^\\d{4}/.test(view.capturedAt) ? view.capturedAt.slice(0, 4) : '';\n return [coverage, year ? `${demo ? 'created' : 'captured'} ${year}` : '', view.sourceLabel ?? ''].filter(Boolean).join(' · ');\n}\n\n/** True only when the panorama should be presented as organizer-authored media.\n * Representative AI demo assets travel through the same URL field as venue\n * photos, so provenance—not URL presence—must decide the buyer badge. */\nexport function isAuthoredSeatView(view: SeatView): boolean {\n if (view.generated || view.mediaKind === 'model' || view.mediaKind === 'demo-render') return false;\n const source = view.sourceLabel?.trim() ?? '';\n return !/^(?:AI[- ]generated|generated\\s+(?:demo|preview))/i.test(source);\n}\n\nexport interface PanoramaHandle {\n /** Fade out and return to the (frozen) 3D view; calls opts.onClose after. */\n close(): void;\n /** Immediate teardown (dispose) — no fade, no onClose. */\n dispose(): void;\n}\n\n/**\n * Vertical field of view (deg) the windowed panorama shows. The source image is\n * a full 180° equirect sphere; showing it raw wastes ~⅔ of the frame on dead sky\n * and black floor, with the horizon content band squished into the middle. We\n * instead scale the image so only this central slice fills the viewport height,\n * horizon-centred, and let the user drag pitch within ±`MAX_PITCH_DEG`.\n */\nexport const VFOV_DEG = 70;\nexport const MIN_VFOV_DEG = 35;\nexport const MAX_VFOV_DEG = 90;\n\n/** Keep wheel, keyboard and pinch zoom inside a useful, non-disorienting range. */\nexport function clampPanoramaFov(fovDeg: number): number {\n return Math.max(MIN_VFOV_DEG, Math.min(MAX_VFOV_DEG, fovDeg));\n}\n/** Users may look this far up/down from the horizon; well inside the image so\n * the clamp never reveals past its top/bottom edge. */\nexport const MAX_PITCH_DEG = 35;\n\n/**\n * Horizontal background-position (px) that centres `bearingDeg` in the viewport,\n * assuming the equirect image's yaw 0 sits at its horizontal centre. `bgW` is the\n * full scaled image width representing 360° — so this is invariant to the vertical\n * FOV windowing (which scales width and height by the same factor). `repeat-x`\n * handles the wrap, so any real value is valid.\n */\nexport function bearingToOffsetPx(bearingDeg: number, viewportW: number, bgW: number): number {\n const col = (0.5 + bearingDeg / 360) * bgW; // image column (px) for the bearing\n return viewportW / 2 - col;\n}\n\n/**\n * Full scaled image height (px) so that a `vfovDeg`-tall slice fills `viewportH`.\n * The image spans 180° vertically, so height = viewportH · 180/vfov.\n */\nexport function windowedBgHeight(viewportH: number, vfovDeg: number = VFOV_DEG): number {\n return viewportH * (180 / vfovDeg);\n}\n\n/**\n * background-position Y (px) that centres the image's horizon (its vertical\n * centre) in the viewport, offset by `pitchPx` (deviation from the horizon,\n * clamped to ±`MAX_PITCH_DEG`). Positive `pitchPx` looks up.\n */\nexport function horizonOffsetPy(viewportH: number, bgH: number, pitchPx: number): number {\n return (viewportH - bgH) / 2 + clampPitchPx(pitchPx, bgH);\n}\n\n/** Clamp a pitch drag (px) to ±MAX_PITCH_DEG of image travel, and never past the\n * image edge. `bgH` px map the full 180°, so a degree is `bgH/180` px. */\nexport function clampPitchPx(pitchPx: number, bgH: number): number {\n const limit = (MAX_PITCH_DEG / 180) * bgH;\n return Math.max(-limit, Math.min(limit, pitchPx));\n}\n\nexport interface PanoramaOptions {\n fadeMs?: number;\n seatLabel?: string;\n disclosure?: string;\n /** Prefix for the disclosure chip; generated live geometry is not a panorama. */\n disclosurePrefix?: string;\n onClose?: () => void;\n /** Cancels stale preview/full-resolution image work when a flight is retargeted. */\n signal?: AbortSignal;\n}\n\nexport function mountPanorama(container: HTMLElement, view: SeatView, opts: PanoramaOptions = {}): PanoramaHandle {\n const fadeMs = opts.fadeMs ?? 400;\n const bearing = view.initialBearingDeg ?? 0;\n\n const delivery = planPanoramaDelivery(view, browserPanoramaConstraints());\n const loadAbort = new AbortController();\n const abortFromCaller = (): void => loadAbort.abort();\n opts.signal?.addEventListener('abort', abortFromCaller, { once: true });\n const root = document.createElement('div');\n const priorFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-label', opts.seatLabel ? `View from ${opts.seatLabel}` : 'View from seat');\n root.tabIndex = -1;\n Object.assign(root.style, {\n position: 'absolute', inset: '0', zIndex: '10', opacity: '0',\n transition: `opacity ${fadeMs}ms ease`, background: '#05070c',\n overflow: 'hidden', touchAction: 'none',\n } as CSSStyleDeclaration);\n\n const pano = document.createElement('div');\n Object.assign(pano.style, {\n position: 'absolute', inset: '0',\n backgroundImage: `url(\"${delivery.initialUrl}\")`, backgroundRepeat: 'repeat-x',\n cursor: 'grab',\n } as CSSStyleDeclaration);\n root.appendChild(pano);\n\n // Close affordance.\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.textContent = '✕';\n Object.assign(closeBtn.style, {\n position: 'absolute', top: '12px', right: '12px', zIndex: '2',\n width: '44px', height: '44px', borderRadius: '999px', cursor: 'pointer',\n border: '1px solid rgba(255,255,255,0.25)', background: 'rgba(8,12,18,0.6)',\n color: '#e6edf3', fontSize: '15px', lineHeight: '1',\n } as CSSStyleDeclaration);\n root.appendChild(closeBtn);\n\n if (opts.disclosure) {\n const disclosure = document.createElement('div');\n disclosure.textContent = `${opts.disclosurePrefix ?? '360° panorama'} · ${opts.disclosure}`;\n Object.assign(disclosure.style, {\n position: 'absolute', top: '12px', left: '12px', zIndex: '2',\n maxWidth: 'calc(100% - 88px)', padding: '8px 11px', borderRadius: '999px',\n overflow: 'hidden', color: '#dce6f8', background: 'rgba(8,12,18,0.68)',\n font: '600 11px/1.2 ui-sans-serif, system-ui, sans-serif', textOverflow: 'ellipsis',\n whiteSpace: 'nowrap', pointerEvents: 'none', backdropFilter: 'blur(6px)',\n } as CSSStyleDeclaration);\n root.appendChild(disclosure);\n }\n\n const hint = document.createElement('div');\n hint.textContent = 'Drag to look · pinch or scroll to zoom · Esc to close';\n Object.assign(hint.style, {\n position: 'absolute', bottom: '12px', left: '0', right: '0', textAlign: 'center',\n color: 'rgba(230,237,243,0.7)', font: '12px ui-sans-serif, system-ui, sans-serif',\n pointerEvents: 'none',\n } as CSSStyleDeclaration);\n root.appendChild(hint);\n\n container.appendChild(root);\n closeBtn.focus({ preventScroll: true });\n\n // Layout: window a ~70° vertical slice of the sphere (horizon-centred) so the\n // venue fills the frame instead of floating in dead sky + black floor. The\n // image is scaled so that slice is exactly the viewport height; width scales by\n // the same factor, so `bearingToOffsetPx` stays correct. `pitchPx` is the\n // vertical drag deviation from the horizon, clamped to ±35°.\n let bgW = 0;\n let bgH = 0;\n let posX = 0;\n let pitchPx = 0;\n let viewFov = VFOV_DEG;\n const layout = (): void => {\n const vh = root.clientHeight || 1;\n const vw = root.clientWidth || 1;\n const natW = img.naturalWidth || vw * 2;\n const natH = img.naturalHeight || vh;\n const centredImageRatio = bgW > 0 ? (vw / 2 - posX) / bgW : 0;\n const pitchRatio = bgH > 0 ? pitchPx / bgH : 0;\n bgH = windowedBgHeight(vh, viewFov);\n bgW = bgH * (natW / natH);\n pano.style.backgroundSize = `${bgW}px ${bgH}px`;\n if (!posInitialised) {\n posX = bearingToOffsetPx(bearing, vw, bgW);\n pitchPx = clampPitchPx(((view.initialPitchDeg ?? 0) / 180) * bgH, bgH);\n posInitialised = true;\n }\n else posX = vw / 2 - centredImageRatio * bgW;\n pitchPx = clampPitchPx(pitchRatio * bgH, bgH);\n pano.style.backgroundPosition = `${posX}px ${horizonOffsetPy(vh, bgH, pitchPx)}px`;\n };\n let posInitialised = false;\n\n const applyPos = (): void => {\n const vh = root.clientHeight || 1;\n pitchPx = clampPitchPx(pitchPx, bgH);\n pano.style.backgroundPosition = `${posX}px ${horizonOffsetPy(vh, bgH, pitchPx)}px`;\n };\n\n let img = new Image();\n img.onload = layout;\n img.src = delivery.initialUrl;\n let cancelUpgrade = (): void => {};\n if (delivery.upgradeUrl) {\n cancelUpgrade = schedulePanoramaUpgrade(() => {\n const resolved = view.resolveUrl\n ? view.resolveUrl(delivery.upgradeUrl!)\n : Promise.resolve(delivery.upgradeUrl!);\n void resolved.then((url) => {\n if (!url || loadAbort.signal.aborted) return null;\n return loadPanoramaImage(url, loadAbort.signal).then((full) => ({ full, url }));\n }).then((loaded) => {\n if (!loaded) return;\n if (disposed || loadAbort.signal.aborted) return;\n img = loaded.full;\n pano.style.backgroundImage = `url(\"${loaded.url}\")`;\n layout();\n }).catch(() => { /* the preview remains usable */ });\n });\n }\n // If it's already cached, onload may not fire — lay out on next frame too.\n requestAnimationFrame(layout);\n\n // Pan: horizontal (repeat-x wraps seamlessly) + vertical pitch (clamped ±35°).\n let dragging = false;\n let lastX = 0;\n let lastY = 0;\n let pinchDistance = 0;\n const pointers = new Map<number, { x: number; y: number }>();\n const distanceBetweenPointers = (): number => {\n const [a, b] = [...pointers.values()];\n return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;\n };\n const setViewFov = (nextFov: number): void => {\n const clamped = clampPanoramaFov(nextFov);\n if (clamped === viewFov) return;\n viewFov = clamped;\n layout();\n };\n const onDown = (e: PointerEvent): void => {\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n dragging = pointers.size === 1;\n lastX = e.clientX; lastY = e.clientY;\n if (pointers.size === 2) pinchDistance = distanceBetweenPointers();\n pano.style.cursor = 'grabbing';\n try { pano.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onMove = (e: PointerEvent): void => {\n if (!pointers.has(e.pointerId)) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (pointers.size >= 2) {\n const nextDistance = distanceBetweenPointers();\n if (pinchDistance > 0 && nextDistance > 0) setViewFov(viewFov * (pinchDistance / nextDistance));\n pinchDistance = nextDistance;\n dragging = false;\n return;\n }\n if (!dragging) return;\n posX += e.clientX - lastX;\n pitchPx += e.clientY - lastY;\n lastX = e.clientX;\n lastY = e.clientY;\n applyPos();\n };\n const onUp = (e: PointerEvent): void => {\n pointers.delete(e.pointerId);\n pinchDistance = pointers.size === 2 ? distanceBetweenPointers() : 0;\n const remaining = pointers.values().next().value as { x: number; y: number } | undefined;\n dragging = pointers.size === 1;\n if (remaining) { lastX = remaining.x; lastY = remaining.y; }\n else pano.style.cursor = 'grab';\n try { pano.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n setViewFov(viewFov + e.deltaY * 0.04);\n };\n pano.addEventListener('pointerdown', onDown);\n pano.addEventListener('pointermove', onMove);\n pano.addEventListener('pointerup', onUp);\n pano.addEventListener('pointercancel', onUp);\n pano.addEventListener('wheel', onWheel, { passive: false });\n\n let closed = false;\n let disposed = false;\n let fadeTimer = 0;\n const removeListeners = (): void => {\n pano.removeEventListener('pointerdown', onDown);\n pano.removeEventListener('pointermove', onMove);\n pano.removeEventListener('pointerup', onUp);\n pano.removeEventListener('pointercancel', onUp);\n pano.removeEventListener('wheel', onWheel);\n window.removeEventListener('keydown', onKey);\n };\n const teardown = (): void => {\n cancelUpgrade();\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n if (fadeTimer) { window.clearTimeout(fadeTimer); fadeTimer = 0; }\n removeListeners();\n if (root.parentNode) root.parentNode.removeChild(root);\n if (priorFocus?.isConnected) priorFocus.focus({ preventScroll: true });\n };\n const close = (): void => {\n if (closed) return;\n closed = true;\n root.style.opacity = '0';\n // Guard the fade callback: a dispose() (or a retarget that disposes us) inside\n // the fade window clears the timer AND flips `disposed`, so a stray fire can\n // never call onClose into a newer flight/panorama.\n const done = (): void => {\n fadeTimer = 0;\n if (disposed) return;\n teardown();\n opts.onClose?.();\n };\n fadeTimer = window.setTimeout(done, fadeMs);\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === 'Tab') { e.preventDefault(); closeBtn.focus({ preventScroll: true }); return; }\n if (e.key === 'Escape') { e.stopPropagation(); close(); return; }\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n posX += e.key === 'ArrowLeft' ? -32 : 32;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n pitchPx += (e.key === 'ArrowUp' ? 4 : -4) * (bgH / 180);\n } else if (e.key === '+' || e.key === '=') {\n setViewFov(viewFov - 5);\n e.preventDefault();\n return;\n } else if (e.key === '-') {\n setViewFov(viewFov + 5);\n e.preventDefault();\n return;\n } else {\n return;\n }\n e.preventDefault();\n applyPos();\n };\n window.addEventListener('keydown', onKey);\n closeBtn.addEventListener('click', close);\n\n // Fade in on the next frame (0 → 1).\n requestAnimationFrame(() => { root.style.opacity = '1'; });\n\n return {\n close,\n dispose(): void { closed = true; disposed = true; teardown(); },\n };\n}\n","/**\n * The equirectangular panorama, as real geometry inside the venue scene.\n *\n * What this replaces, and why it had to change:\n *\n * `crossfade/panorama.ts` draws the 360 as a DOM div with `background-repeat:\n * repeat-x`, panned by hand and windowed to a 70° vertical slice. To show it,\n * `openPanorama` calls `loop.stop()` — it FREEZES the WebGL venue and fades a\n * flat image over the corpse. So the immersive payoff of the whole 3D flight is\n * the moment the product stops using its renderer.\n *\n * A `repeat-x` background is also not a sphere. It is a cylinder at best: pan is\n * linear in longitude (correct), but pitch is a vertical translation of a flat\n * bitmap, so verticals bow and the image stretches away from the horizon. A real\n * 360 photo looks right in the 2D popover — which uses an actual sphere via\n * Photo Sphere Viewer — and subtly wrong here, on the surface the marketing\n * story leads with.\n *\n * The fix is not to import Photo Sphere Viewer. That is Three.js-based, and this\n * chunk already owns a WebGL stack (OGL) and is deliberately kept at ~71 KB\n * gzipped because it loads only when a buyer opens 3D. Pulling in a second\n * renderer to draw one textured sphere would roughly triple it.\n *\n * So: a unit sphere, inward-facing, sampled equirectangularly, drawn by the\n * scene's own renderer with the scene's own camera. The panorama stops being an\n * overlay and becomes somewhere the camera can be, which is also what makes the\n * fly-to-seat handoff a continuous move rather than a cut between two different\n * rendering technologies.\n *\n * Everything below the `mount` function is pure and unit-tested; the GL parts\n * are a thin wrapper over it.\n */\n\nimport { Geometry, Mesh, Program, Texture } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\n\n/**\n * Longitude/latitude tessellation of the sphere.\n *\n * The sphere is viewed from its exact centre, so silhouette smoothness never\n * matters — only how much the equirect sampling is distorted by interpolating\n * UVs across a triangle. 64×32 keeps the worst-case angular error under a third\n * of a degree at the horizon, which is far below a pixel at any sane FOV, for\n * 4,096 triangles. Going finer costs vertices to fix an error nothing can see.\n */\nexport const PANO_SEGMENTS_LON = 64;\nexport const PANO_SEGMENTS_LAT = 32;\n\n/**\n * Radius in metres. Arbitrary in principle — the camera sits at the centre and\n * an equirect sphere has no parallax — but it must be large enough to enclose\n * the near clip plane and small enough to stay inside the far plane, or the\n * panorama clips against its own geometry. 500 m clears both for every venue in\n * the catalog, including the 50k-seat stadium.\n */\nexport const PANO_RADIUS_M = 500;\n\nexport interface SphereVertexData {\n position: Float32Array;\n uv: Float32Array;\n index: Uint16Array;\n}\n\n/**\n * A UV sphere with INWARD-facing triangles and equirectangular UVs.\n *\n * Inward-facing is done by winding, not by disabling culling: the scene draws\n * with back-face culling on, and turning it off for one mesh would leak that\n * state onto everything drawn after it in the same pass. Reversing the triangle\n * winding makes the inside surface the front face, so the sphere obeys exactly\n * the same pipeline state as the venue.\n *\n * Mapping, matching `generatePanorama.ts` and the 2D viewer so one image reads\n * identically in all three:\n * u = 0.5 + yaw/360 (u 0.5 is yaw 0, the direction the camera faces)\n * v = (90 - pitch)/180 (v 0 is the top of the image)\n *\n * The seam at u=0/1 is duplicated rather than wrapped — a shared vertex would\n * have to carry both u=0 and u=1 and would interpolate the whole image backwards\n * across that one column of triangles.\n */\nexport function buildPanoSphere(\n segmentsLon: number = PANO_SEGMENTS_LON,\n segmentsLat: number = PANO_SEGMENTS_LAT,\n radius: number = PANO_RADIUS_M,\n): SphereVertexData {\n const lonCount = segmentsLon + 1; // +1 duplicates the seam column\n const latCount = segmentsLat + 1; // +1 closes both poles\n const vertexCount = lonCount * latCount;\n const position = new Float32Array(vertexCount * 3);\n const uv = new Float32Array(vertexCount * 2);\n\n for (let iLat = 0; iLat < latCount; iLat++) {\n // v runs 0 (top) → 1 (bottom); polar angle 0 at +Y.\n const v = iLat / segmentsLat;\n const polar = v * Math.PI;\n const sinPolar = Math.sin(polar);\n const cosPolar = Math.cos(polar);\n for (let iLon = 0; iLon < lonCount; iLon++) {\n const u = iLon / segmentsLon;\n // u 0.5 must land on yaw 0. The scene's yaw 0 faces -Z (the camera looks\n // down -Z by default), so offset the azimuth by half a turn.\n const azimuth = (u - 0.5) * Math.PI * 2;\n const i = iLat * lonCount + iLon;\n position[i * 3] = radius * sinPolar * Math.sin(azimuth);\n position[i * 3 + 1] = radius * cosPolar;\n position[i * 3 + 2] = -radius * sinPolar * Math.cos(azimuth);\n uv[i * 2] = u;\n uv[i * 2 + 1] = v;\n }\n }\n\n // Two triangles per quad, wound so the INSIDE is the front face.\n const index = new Uint16Array(segmentsLon * segmentsLat * 6);\n let w = 0;\n for (let iLat = 0; iLat < segmentsLat; iLat++) {\n for (let iLon = 0; iLon < segmentsLon; iLon++) {\n const a = iLat * lonCount + iLon;\n const b = a + lonCount;\n index[w++] = a; index[w++] = b; index[w++] = a + 1;\n index[w++] = a + 1; index[w++] = b; index[w++] = b + 1;\n }\n }\n return { position, uv, index };\n}\n\n/**\n * Direction (unit vector, scene axes) the camera looks along for a given\n * bearing and pitch in degrees. Bearing 0 faces the focal/stage, matching\n * `SeatView.initialBearingDeg`; positive pitch looks up.\n *\n * Exported because the transition from the flight has to hand the sphere the\n * camera's final yaw, and a panorama that opens on a different bearing than the\n * flight ended on reads as a cut.\n */\nexport function bearingPitchToDirection(bearingDeg: number, pitchDeg: number): [number, number, number] {\n const yaw = (bearingDeg * Math.PI) / 180;\n const pitch = (pitchDeg * Math.PI) / 180;\n const cosPitch = Math.cos(pitch);\n return [cosPitch * Math.sin(yaw), Math.sin(pitch), -cosPitch * Math.cos(yaw)];\n}\n\nconst PANO_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nin vec2 uv;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst PANO_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform sampler2D uPano;\nuniform float uOpacity;\nout vec4 fragColor;\nvoid main() {\n vec3 rgb = texture(uPano, vUv).rgb;\n fragColor = vec4(rgb, uOpacity);\n}`;\n\nexport interface PanoSphereHandle {\n mesh: Mesh;\n /** Cross-fade control: 0 hides the sphere, 1 fully replaces the venue. */\n setOpacity(value: number): void;\n /** Replace the decoded source on the existing GPU texture. */\n setImage(image: HTMLImageElement | HTMLCanvasElement): void;\n dispose(): void;\n}\n\n/**\n * Build the textured sphere mesh. The caller owns adding it to a scene graph and\n * driving `setOpacity` — this module deliberately knows nothing about the flight\n * or the overlay chrome.\n *\n * `depthTest: false` and `depthWrite: false`: the sphere encloses the entire\n * venue, so with depth on it would be occluded by every piece of geometry\n * between the camera and its far wall. It is a backdrop that replaces the view,\n * not a solid in it — so it is drawn without consulting depth, and the caller\n * draws it after the venue when fading in.\n */\nexport function createPanoSphere(\n gl: OGLRenderingContext,\n // Narrower than `TexImageSource` on purpose: the panorama always arrives as a\n // decoded <img> (a URL or the generator's data URL) or a canvas. OGL's texture\n // type does not accept ImageBitmap, and widening here would only move the\n // failure to runtime.\n image: HTMLImageElement | HTMLCanvasElement,\n): PanoSphereHandle {\n const { position, uv, index } = buildPanoSphere();\n const geometry = new Geometry(gl, {\n position: { size: 3, data: position },\n uv: { size: 2, data: uv },\n index: { data: index },\n });\n const texture = new Texture(gl, {\n image,\n // The seam column is duplicated in geometry, so CLAMP is correct and avoids\n // a wrapped bilinear tap bleeding the far edge of the image into it.\n wrapS: gl.CLAMP_TO_EDGE,\n wrapT: gl.CLAMP_TO_EDGE,\n generateMipmaps: true,\n // MUST be false, and this is the one line that decides whether the whole\n // panorama is upside down.\n //\n // OGL defaults `flipY` to true for 2D textures, which is right for the usual\n // case: a UV of 0 means the BOTTOM of a quad, an image's first row is its\n // TOP, and flipping on upload reconciles the two. This sphere is the other\n // case. Its `v` is authored in IMAGE space — v=0 is the top of the equirect\n // and maps to the top of the sphere (`buildPanoSphere`, and the mapping\n // `generatePanorama` and the 2D viewer both use). Flipping on upload as well\n // applies the correction twice: the stadium pitch renders on the ceiling and\n // the audience sits overhead.\n //\n // The geometry tests state the v mapping and pass either way — they never\n // touch GL — so this is not something a unit test can defend. It was caught\n // by looking at a stadium.\n flipY: false,\n });\n const program = new Program(gl, {\n vertex: PANO_VERT,\n fragment: PANO_FRAG,\n uniforms: { uPano: { value: texture }, uOpacity: { value: 0 } },\n transparent: true,\n depthTest: false,\n depthWrite: false,\n });\n const mesh = new Mesh(gl, { geometry, program });\n return {\n mesh,\n setOpacity(value: number): void {\n program.uniforms.uOpacity.value = Math.max(0, Math.min(1, value));\n },\n setImage(nextImage: HTMLImageElement | HTMLCanvasElement): void {\n texture.image = nextImage;\n texture.needsUpdate = true;\n },\n dispose(): void {\n mesh.setParent(null);\n geometry.remove();\n if (texture.texture) gl.deleteTexture(texture.texture);\n program.remove();\n },\n };\n}\n","/**\n * The panorama, mounted as scene geometry instead of as a div over a corpse.\n *\n * `panorama.ts` (still here, still used as the fallback) freezes the render loop\n * and fades a `repeat-x` bitmap over the last WebGL frame. This mounts the same\n * image on the inward-facing sphere from `scene/panoSphere.ts`, leaves the loop\n * running, and moves the camera instead of a background-position. Three things\n * follow from that, and they are the reason for the rewrite:\n *\n * - It is a real sphere. Pitch is a camera rotation, not a vertical translation\n * of a flat bitmap, so verticals stay vertical and nothing stretches away\n * from the horizon.\n * - The venue is still being drawn underneath, so the arrival is a CROSS-FADE\n * between two views of the same place from the same camera, not a cut\n * between two rendering technologies.\n * - Escape/close costs nothing to reverse: the camera is already where it needs\n * to be, because it never went anywhere.\n *\n * The DOM chrome (close button, hint, dialog semantics) is kept deliberately —\n * it is the accessible surface, and none of it belongs in GL.\n */\n\nimport { createPanoSphere, bearingPitchToDirection, type PanoSphereHandle } from '../scene/panoSphere';\nimport type { PanoramaHandle, PanoramaOptions, SeatView } from './panorama';\nimport { clampPanoramaFov, MAX_PITCH_DEG, VFOV_DEG } from './panorama';\nimport type { OGLRenderingContext, Transform, Camera } from 'ogl';\nimport {\n browserPanoramaConstraints,\n loadPanoramaImage,\n planPanoramaDelivery,\n schedulePanoramaUpgrade,\n} from '../../view/panoramaDelivery';\n\n/**\n * Degrees of look per pixel dragged. Matched to the DOM viewer's feel: a drag\n * across the full width of a 1200 px viewport turns roughly 180°.\n */\nconst DEG_PER_PX = 0.15;\n\nexport interface PanoramaSceneDeps {\n gl: OGLRenderingContext;\n /** The scene the venue is drawn into; the sphere joins it and leaves on close. */\n scene: Transform;\n camera: Camera;\n /** Ask for another frame — the loop is render-on-demand, not free-running. */\n requestRender(): void;\n /**\n * Exact world-space eye point for scene mode. The camera is moved here while\n * the buyer looks around, then restored to the cinematic arrival on close.\n */\n cameraOriginWorld?: readonly [number, number, number];\n /**\n * The venue focal (stage/pitch centre) in world metres.\n *\n * Only scene mode needs it, and it genuinely cannot do without it. A generated\n * panorama is DRAWN with the stage at bearing 0, so opening at bearing 0 faces\n * the stage for free. The real scene has no such convention — bearing 0 is\n * world −Z, which points wherever the venue happened to be authored — so\n * opening there stares at whatever is behind the buyer. The bearing is derived\n * from the seat's own line to the focal instead.\n */\n focalWorld?: readonly [number, number, number];\n}\n\n/**\n * Mount the panorama sphere. Returns null if the image cannot be decoded, so the\n * caller can fall back to the DOM viewer rather than stranding the buyer on a\n * black screen — a 360 that fails to load must never be worse than no 360.\n */\nexport async function mountPanoramaSphere(\n container: HTMLElement,\n // NULL means \"look around the real scene\": no image, no sphere, just the\n // camera at the seat. That is the right answer for a generated panorama,\n // which is only ever a low-resolution picture of the geometry already on\n // screen — see `SeatView.generated`. Everything else below (framing,\n // look-around, Escape, restoring the camera) is identical, which is why the\n // two modes share one code path rather than diverging into two viewers that\n // drift apart.\n view: SeatView | null,\n deps: PanoramaSceneDeps,\n opts: PanoramaOptions = {},\n): Promise<PanoramaHandle | null> {\n const fadeMs = opts.fadeMs ?? 400;\n let bearing = view?.initialBearingDeg ?? 0;\n let pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, view?.initialPitchDeg ?? 0));\n let viewFov = VFOV_DEG;\n let disposed = false;\n const loadAbort = new AbortController();\n const abortFromCaller = (): void => loadAbort.abort();\n opts.signal?.addEventListener('abort', abortFromCaller, { once: true });\n let cancelUpgrade = (): void => {};\n\n // Scene mode is the real venue viewed from the selected seat, not from the\n // deliberately flattering cinematic arrival behind and above it. Preserve\n // that arrival so closing 360 returns to the exact pre-view camera.\n const priorPosition: [number, number, number] = [\n deps.camera.position.x,\n deps.camera.position.y,\n deps.camera.position.z,\n ];\n if (!view && deps.cameraOriginWorld) {\n deps.camera.position.set(\n deps.cameraOriginWorld[0],\n deps.cameraOriginWorld[1],\n deps.cameraOriginWorld[2],\n );\n }\n\n // Scene mode opens on the stage, derived from where this seat actually is.\n // The pitch matters as much as the yaw: from a balcony the stage is well below\n // eye level, and opening level puts the whole venue in the bottom of the frame\n // with dead air above it.\n if (!view && deps.focalWorld) {\n const p = deps.camera.position;\n const dx = deps.focalWorld[0] - p.x;\n const dy = deps.focalWorld[1] - p.y;\n const dz = deps.focalWorld[2] - p.z;\n const flat = Math.hypot(dx, dz);\n if (flat > 1e-3) {\n bearing = (Math.atan2(dx, -dz) * 180) / Math.PI;\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, (Math.atan2(dy, flat) * 180) / Math.PI));\n }\n }\n\n let sphere: PanoSphereHandle | null = null;\n if (view) {\n const maxTextureSize = deps.gl.getParameter(deps.gl.MAX_TEXTURE_SIZE) as number;\n const delivery = planPanoramaDelivery(view, browserPanoramaConstraints(maxTextureSize));\n let image: HTMLImageElement;\n try {\n const initialUrl = view.resolveUrl\n ? await view.resolveUrl(delivery.initialUrl)\n : delivery.initialUrl;\n if (!initialUrl) throw new Error('panorama_asset_unavailable');\n image = await loadPanoramaImage(initialUrl, loadAbort.signal);\n } catch {\n opts.signal?.removeEventListener('abort', abortFromCaller);\n return null;\n }\n try {\n sphere = createPanoSphere(deps.gl, image);\n } catch {\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n return null;\n }\n // Drawn last and without depth, so it composites over the venue during the\n // fade instead of being occluded by whatever sits between the camera and\n // the far wall of the sphere.\n sphere.mesh.renderOrder = 999;\n sphere.mesh.setParent(deps.scene);\n if (delivery.upgradeUrl) {\n cancelUpgrade = schedulePanoramaUpgrade(() => {\n const resolved = view.resolveUrl\n ? view.resolveUrl(delivery.upgradeUrl!)\n : Promise.resolve(delivery.upgradeUrl!);\n void resolved.then((url) => {\n if (!url || loadAbort.signal.aborted) return null;\n return loadPanoramaImage(url, loadAbort.signal);\n }).then((full) => {\n if (!full) return;\n if (disposed || loadAbort.signal.aborted) return;\n sphere?.setImage(full);\n deps.requestRender();\n }).catch(() => { /* retain the decoded preview */ });\n });\n }\n }\n\n // A cinematic flight ENDS pushed in — it narrows the FOV to close the last of\n // the distance to the seat, and `resumeAfterFlight` is what normally restores\n // it. The DOM viewer never noticed because it never used the camera; drawing\n // the panorama through that same camera inherits the zoom and magnifies the\n // equirect to a few unreadable degrees of arc.\n //\n // So the sphere sets its own field of view — the same 70° the DOM viewer\n // windowed to, which is what makes the two paths frame a panorama alike — and\n // puts back whatever it found on the way out, so closing hands the flight's\n // camera back exactly as it was.\n const priorFov = deps.camera.fov;\n deps.camera.perspective({ fov: viewFov, aspect: deps.camera.aspect });\n\n // Looking around REPLACES the camera's orientation, and orbit only re-aims on\n // a frame it considers \"moving\". So closing the panorama used to hand back a\n // camera still pointing wherever the buyer had dragged, at a venue that was no\n // longer in front of it — a black screen until the first orbit drag snapped it\n // home. Capture the orientation on the way in and put it back on the way out,\n // exactly as the FOV is handled.\n const priorQuat = deps.camera.quaternion.slice() as unknown as number[];\n\n /** Keep the camera at the sphere's exact centre — an equirect has no parallax,\n * and any offset would shear the image. */\n const aim = (): void => {\n const p = deps.camera.position;\n sphere?.mesh.position.set(p.x, p.y, p.z);\n const [dx, dy, dz] = bearingPitchToDirection(bearing, pitch);\n deps.camera.lookAt([p.x + dx, p.y + dy, p.z + dz]);\n deps.requestRender();\n };\n aim();\n\n // --- chrome -------------------------------------------------------------\n const root = document.createElement('div');\n const priorFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-label', opts.seatLabel ? `View from ${opts.seatLabel}` : 'View from seat');\n root.tabIndex = -1;\n Object.assign(root.style, {\n position: 'absolute', inset: '0', zIndex: '10',\n cursor: 'grab', touchAction: 'none',\n } as CSSStyleDeclaration);\n\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.textContent = '✕';\n Object.assign(closeBtn.style, {\n position: 'absolute', top: '12px', right: '12px', zIndex: '2',\n width: '44px', height: '44px', borderRadius: '999px', cursor: 'pointer',\n border: '1px solid rgba(255,255,255,0.25)', background: 'rgba(8,12,18,0.6)',\n color: '#e6edf3', fontSize: '15px', lineHeight: '1',\n } as CSSStyleDeclaration);\n root.appendChild(closeBtn);\n\n if (opts.disclosure) {\n const disclosure = document.createElement('div');\n disclosure.textContent = `${opts.disclosurePrefix ?? '360° panorama'} · ${opts.disclosure}`;\n Object.assign(disclosure.style, {\n position: 'absolute', top: '12px', left: '12px', zIndex: '2',\n maxWidth: 'calc(100% - 88px)', padding: '8px 11px', borderRadius: '999px',\n overflow: 'hidden', color: '#dce6f8', background: 'rgba(8,12,18,0.68)',\n font: '600 11px/1.2 ui-sans-serif, system-ui, sans-serif', textOverflow: 'ellipsis',\n whiteSpace: 'nowrap', pointerEvents: 'none', backdropFilter: 'blur(6px)',\n } as CSSStyleDeclaration);\n root.appendChild(disclosure);\n }\n\n const hint = document.createElement('div');\n hint.textContent = 'Drag to look · pinch or scroll to zoom · Esc to close';\n Object.assign(hint.style, {\n position: 'absolute', bottom: '12px', left: '0', right: '0', textAlign: 'center',\n color: 'rgba(230,237,243,0.7)', font: '12px ui-sans-serif, system-ui, sans-serif',\n pointerEvents: 'none',\n } as CSSStyleDeclaration);\n root.appendChild(hint);\n container.appendChild(root);\n closeBtn.focus({ preventScroll: true });\n\n // --- look-around --------------------------------------------------------\n let dragging = false;\n let lastX = 0;\n let lastY = 0;\n let pinchDistance = 0;\n const pointers = new Map<number, { x: number; y: number }>();\n const distanceBetweenPointers = (): number => {\n const [a, b] = [...pointers.values()];\n return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;\n };\n const setViewFov = (nextFov: number): void => {\n const clamped = clampPanoramaFov(nextFov);\n if (clamped === viewFov) return;\n viewFov = clamped;\n deps.camera.perspective({ fov: viewFov, aspect: deps.camera.aspect });\n aim();\n };\n const onDown = (e: PointerEvent): void => {\n if (e.target === closeBtn) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n dragging = pointers.size === 1;\n lastX = e.clientX; lastY = e.clientY;\n if (pointers.size === 2) pinchDistance = distanceBetweenPointers();\n root.setPointerCapture?.(e.pointerId);\n root.style.cursor = 'grabbing';\n };\n const onMove = (e: PointerEvent): void => {\n if (!pointers.has(e.pointerId)) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (pointers.size >= 2) {\n const nextDistance = distanceBetweenPointers();\n if (pinchDistance > 0 && nextDistance > 0) setViewFov(viewFov * (pinchDistance / nextDistance));\n pinchDistance = nextDistance;\n dragging = false;\n return;\n }\n if (!dragging) return;\n // Horizontal follows the HAND: drag right, turn right. The vertical below\n // stays grab-style (drag down to look up), which is the mix the owner asked\n // for after using it — and worth stating because it looks inconsistent in\n // the source and is not. The DOM viewer this replaces dragged the image on\n // both axes; the yaw was the one that read backwards in a real venue.\n bearing += (e.clientX - lastX) * DEG_PER_PX;\n // Clamped to the same ±35° the DOM viewer allowed. A generated panorama has\n // nothing meaningful at the poles, and letting the buyer tumble to straight\n // up reads as a broken control rather than a feature.\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, pitch + (e.clientY - lastY) * DEG_PER_PX));\n lastX = e.clientX; lastY = e.clientY;\n aim();\n };\n const onUp = (e: PointerEvent): void => {\n pointers.delete(e.pointerId);\n pinchDistance = pointers.size === 2 ? distanceBetweenPointers() : 0;\n const remaining = pointers.values().next().value as { x: number; y: number } | undefined;\n dragging = pointers.size === 1;\n if (remaining) { lastX = remaining.x; lastY = remaining.y; }\n else root.style.cursor = 'grab';\n try { root.releasePointerCapture?.(e.pointerId); } catch { /* pointer already released */ }\n };\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n setViewFov(viewFov + e.deltaY * 0.04);\n };\n root.addEventListener('pointerdown', onDown);\n root.addEventListener('pointermove', onMove);\n root.addEventListener('pointerup', onUp);\n root.addEventListener('pointercancel', onUp);\n root.addEventListener('wheel', onWheel, { passive: false });\n\n // Escape closes. On `window`, not on `root`, because the hint promises it\n // unconditionally and a div only receives keys while focused — the DOM viewer\n // this replaces bound it the same way, and dropping it made the hint lie.\n // `stopPropagation` so the host page's own Escape handling (closing a modal,\n // leaving 3D) does not also fire on the same press.\n const onKey = (e: KeyboardEvent): void => {\n if (disposed) return;\n if (e.key === 'Tab') {\n e.preventDefault();\n closeBtn.focus({ preventScroll: true });\n return;\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n handle.close();\n return;\n }\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n bearing += e.key === 'ArrowLeft' ? -4 : 4;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, pitch + (e.key === 'ArrowUp' ? 4 : -4)));\n } else if (e.key === '+' || e.key === '=') {\n setViewFov(viewFov - 5);\n e.preventDefault();\n return;\n } else if (e.key === '-') {\n setViewFov(viewFov + 5);\n e.preventDefault();\n return;\n } else {\n return;\n }\n e.preventDefault();\n aim();\n };\n window.addEventListener('keydown', onKey);\n\n // --- fade ---------------------------------------------------------------\n let raf = 0;\n const fadeTo = (target: number, done?: () => void): void => {\n // Scene mode has nothing to fade — the buyer is already looking at the\n // venue — so the transition is the FOV widening, not a dissolve.\n if (!sphere) { done?.(); return; }\n const from = target === 1 ? 0 : 1;\n const start = performance.now();\n const step = (): void => {\n if (disposed) return;\n const t = fadeMs <= 0 ? 1 : Math.min(1, (performance.now() - start) / fadeMs);\n sphere?.setOpacity(from + (target - from) * t);\n deps.requestRender();\n if (t < 1) raf = requestAnimationFrame(step);\n else done?.();\n };\n raf = requestAnimationFrame(step);\n };\n fadeTo(1);\n\n const teardown = (): void => {\n if (disposed) return;\n disposed = true;\n cancelUpgrade();\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n cancelAnimationFrame(raf);\n root.removeEventListener('pointerdown', onDown);\n root.removeEventListener('pointermove', onMove);\n root.removeEventListener('pointerup', onUp);\n root.removeEventListener('pointercancel', onUp);\n root.removeEventListener('wheel', onWheel);\n window.removeEventListener('keydown', onKey);\n root.remove();\n if (priorFocus?.isConnected) priorFocus.focus({ preventScroll: true });\n sphere?.dispose();\n deps.camera.position.set(priorPosition[0], priorPosition[1], priorPosition[2]);\n deps.camera.perspective({ fov: priorFov, aspect: deps.camera.aspect });\n deps.camera.quaternion.set(priorQuat[0], priorQuat[1], priorQuat[2], priorQuat[3]);\n deps.requestRender();\n };\n\n const handle: PanoramaHandle = {\n close(): void {\n if (disposed) return;\n const finish = (): void => { teardown(); opts.onClose?.(); };\n cancelAnimationFrame(raf);\n fadeTo(0, finish);\n },\n dispose: teardown,\n };\n closeBtn.addEventListener('click', () => handle.close());\n return handle;\n}\n","/**\n * view3d analytics — a tiny, decoupled event emitter for the venue view. The\n * caller (app/harness) supplies `onAnalytics`; this class owns the per-mount\n * state (first-orbit latch, panorama dwell timing) and, crucially, wraps EVERY\n * callback invocation in try/catch so a throwing analytics sink can never break\n * rendering. No DOM, no GL — unit-testable in isolation.\n */\n\nexport type Analytics3DCallback = (event: string, props?: Record<string, unknown>) => void;\n\n/**\n * Where the CPU scene was compiled, as a closed enum (never free text).\n * - `worker` — built off the UI thread by `prepareVenue3D`.\n * - `main` — `prepareVenue3D` ran but the worker was unavailable or failed\n * (no `Worker`, a host CSP blocking the module worker, a throw, or\n * the 15s timeout) and the same pure compiler ran on the UI thread.\n * - `inline` — the caller never used `prepareVenue3D`; the scene was compiled\n * synchronously inside `mountVenue3D`. Kept distinct from `main`\n * so worker uptake is not diluted by callers that never asked.\n */\nexport type Scene3DPrepSource = 'worker' | 'main' | 'inline';\n\nconst now = (): number =>\n (typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now());\n\nexport class Analytics3D {\n private cb?: Analytics3DCallback;\n private orbitLatched = false;\n private panoramaOpenedAt = 0;\n\n constructor(cb?: Analytics3DCallback) {\n this.cb = cb;\n }\n\n /** The single guarded emit point — analytics must never throw into the loop. */\n private emit(event: string, props?: Record<string, unknown>): void {\n if (!this.cb) return;\n try {\n this.cb(event, props);\n } catch {\n /* analytics sink threw — swallow so rendering is never affected */\n }\n }\n\n opened(\n seats: number,\n hasHeights: boolean,\n buildMs?: number,\n prepSource?: Scene3DPrepSource,\n ): void {\n this.emit('3d_opened', {\n seats,\n hasHeights,\n ...(buildMs === undefined ? {} : { buildMs: Math.round(buildMs) }),\n ...(prepSource === undefined ? {} : { prepSource }),\n });\n }\n\n /** First user-driven orbit/dolly per mount only (the intro ease is not user\n * input, so callers must gate this on real pointer/wheel gestures). */\n orbitEngaged(): void {\n if (this.orbitLatched) return;\n this.orbitLatched = true;\n this.emit('3d_orbit_engaged');\n }\n\n seatPicked(seatId: string, sectionId: string | undefined): void {\n this.emit('3d_seat_picked', { seatId, sectionId });\n }\n\n cinematicPlayed(durationMs: number): void {\n this.emit('3d_cinematic_played', { durationMs, reducedMotion: false });\n }\n\n cinematicSkipped(): void {\n this.emit('3d_cinematic_skipped', { reducedMotion: true });\n }\n\n cinematicCancelled(): void {\n this.emit('3d_cinematic_cancelled');\n }\n\n panoramaOpened(): void {\n this.panoramaOpenedAt = now();\n this.emit('3d_panorama_opened');\n }\n\n panoramaClosed(): void {\n const viewMs = this.panoramaOpenedAt ? Math.round(now() - this.panoramaOpenedAt) : 0;\n this.panoramaOpenedAt = 0;\n this.emit('3d_panorama_closed', { viewMs });\n }\n\n panoramaFailed(seatId: string, phase: 'source' | 'mount'): void {\n this.emit('3d_panorama_failed', { seatId, phase });\n }\n\n panoramaFallback(seatId: string): void {\n this.emit('3d_panorama_fallback', { seatId });\n }\n\n contextLost(): void {\n this.emit('3d_context_lost');\n }\n\n contextRestored(): void {\n this.emit('3d_context_restored');\n }\n}\n","/**\n * Establish a positioning context only when the host is genuinely static.\n *\n * Buyer overlays are positioned by stylesheet rules. Checking only the inline\n * style would overwrite `position:absolute` and collapse the 3D canvas.\n */\nexport function establishPositioningContext(container: HTMLElement): () => void {\n const originalInline = container.style.position;\n if (getComputedStyle(container).position !== 'static') return () => {};\n\n container.style.position = 'relative';\n return () => {\n // Preserve any host change made while the renderer was mounted.\n if (container.style.position === 'relative') container.style.position = originalInline;\n };\n}\n","import type { ChartDoc, ExpandedSeat } from '../core/types';\nimport { buildSceneModel, type SceneModel } from './scene/sceneModel';\nimport type { Scene3DPrepSource } from './analytics';\n\nexport interface PreparedVenue3D {\n model: SceneModel;\n buildMs: number;\n /** Reported on `3d_opened` as `prepSource`. `inline` is not reachable here —\n * it is what `mountVenue3D` reports when no prepared scene was handed in. */\n source: Exclude<Scene3DPrepSource, 'inline'>;\n}\n\nexport interface PrepareVenue3DInput {\n doc: ChartDoc;\n seats: ExpandedSeat[];\n eventConfigurationId?: string;\n}\n\nfunction prepareOnMain(input: PrepareVenue3DInput): PreparedVenue3D {\n const started = performance.now();\n const model = buildSceneModel(input);\n return { model, buildMs: performance.now() - started, source: 'main' };\n}\n\n/**\n * Build the CPU-heavy merged mesh and seat buffers off the UI thread. Worker\n * failure is non-fatal: old browsers and restrictive hosts fall back to the\n * exact same pure compiler on the main thread.\n */\nexport function prepareVenue3D(input: PrepareVenue3DInput): Promise<PreparedVenue3D> {\n if (typeof Worker === 'undefined') return Promise.resolve(prepareOnMain(input));\n\n return new Promise((resolve) => {\n const worker = new Worker(new URL('./scene/scene.worker.ts', import.meta.url), { type: 'module' });\n let settled = false;\n const finish = (result: PreparedVenue3D): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n worker.terminate();\n resolve(result);\n };\n const fallback = (): void => finish(prepareOnMain(input));\n const timeout = window.setTimeout(fallback, 15_000);\n worker.onerror = fallback;\n worker.onmessage = (event: MessageEvent<{ model: SceneModel; buildMs: number }>) => {\n finish({ model: event.data.model, buildMs: event.data.buildMs, source: 'worker' });\n };\n worker.postMessage(input);\n });\n}\n"],"mappings":"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","names":["import_ogl","import_ogl","norm","now","earcut","scale","polygonClipping","scale","import_polygon_clipping","DEG","import_polygon_clipping","import_earcut","out","probesOf","polygonClipping","poly","earcut","belowY","pointInRing","polygonClipping","s","import_ogl","import_ogl","sub","import_ogl","import_ogl","now","id","import_ogl","DEG","moved"]}
|