@seatlayer/core 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -22
- package/dist/chunk-FGS67GT3.js +1252 -0
- package/dist/chunk-FGS67GT3.js.map +1 -0
- package/dist/index.cjs +572 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +269 -909
- package/dist/index.js.map +1 -1
- package/dist/{types-B-tpUFqz.d.cts → types-CG2pEDoI.d.cts} +56 -0
- package/dist/{types-B-tpUFqz.d.ts → types-CG2pEDoI.d.ts} +56 -0
- package/dist/view3d/index.cjs +2315 -99
- package/dist/view3d/index.cjs.map +1 -1
- package/dist/view3d/index.d.cts +236 -27
- package/dist/view3d/index.d.ts +236 -27
- package/dist/view3d/index.js +1912 -98
- package/dist/view3d/index.js.map +1 -1
- package/package.json +5 -3
- package/dist/chunk-5MLADB2N.js +0 -53
- package/dist/chunk-5MLADB2N.js.map +0 -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/core/units.ts","../../src/view3d/scene/geometry.ts","../../src/view3d/scene/seatInstances.ts","../../src/view3d/scene/sceneModel.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/crossfade/panorama.ts","../../src/view3d/analytics.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 } from './lod';\nimport { buildSceneModel, type SceneModel } from './scene/sceneModel';\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 { mountPanorama, type PanoramaHandle, type SeatView } from './crossfade/panorama';\nimport { Analytics3D, type Analytics3DCallback } from './analytics';\nimport type { SeatState3D } from './palette';\n\nexport type { SeatState3D } from './palette';\nexport type { SeatView } from './crossfade/panorama';\nexport type { Analytics3DCallback } from './analytics';\nexport { buildSceneModel } from './scene/sceneModel';\n\n/** Seat eye height above its deck: SEATED_EYE_HEIGHT_M (1.2) − seat lift (0.18). */\nconst SEAT_EYE_ABOVE_DECK = 1.02;\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}\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 /**\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 /**\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\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 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}\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 model: SceneModel = 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\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 const prefetch = new Map<string, Promise<SeatView>>();\n let flightGen = 0;\n let reducedForced: boolean | null = null;\n\n const rebuildGpu = (): void => {\n gpu = buildGpuScene(glctx.gl, model);\n pick = new PickPipeline(glctx.renderer, gpu.seatGeometry, gpu.solidGeometry, model.seats.count);\n };\n\n const glctx = new GLContext(container, {\n onContextLost: () => {\n contextLost = true;\n loop.stop();\n gpu = null;\n pick = null;\n },\n onContextRestored: () => {\n rebuildGpu();\n contextLost = false;\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 orbit.frame(model.bounds, true);\n\n const cinematic = new Cinematic(orbit.camera);\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 const u = gpu.seatProgram.uniforms;\n u.uSeatScale.value = lod.scale;\n u.uSeatFade.value = lod.fade;\n u.uPixelToWorld.value = (2 * Math.tan((orbit.camera.fov * DEG) / 2)) / Math.max(1, glctx.pixelHeight);\n\n glctx.renderer.render({ scene: gpu.background, clear: true });\n glctx.renderer.render({ scene: gpu.main, camera: orbit.camera, clear: false });\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 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 // 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 seatEyeWorld = (idx: number): Vec3Arr => [\n model.seats.iPosition[idx * 3],\n model.seats.iPosition[idx * 3 + 1] + SEAT_EYE_ABOVE_DECK,\n model.seats.iPosition[idx * 3 + 2],\n ];\n\n const placeCameraFinal = (finalPos: Vec3Arr, focal: Vec3Arr): 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_END;\n orbit.camera.updateProjectionMatrix();\n };\n\n const openPanorama = async (seatId: string, fadeMs: number, gen: number): Promise<void> => {\n const viewPromise = ensureSeatView(seatId);\n if (!viewPromise) { orbit.syncFromCamera(); return; } // 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 // 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) { frozen = false; orbit.resumeAfterFlight(model.focalWorld); loop.requestRender(); }\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) return;\n panorama = mountPanorama(container, view, {\n fadeMs,\n seatLabel: seatId,\n onClose: () => {\n panorama = null;\n frozen = false;\n analytics.panoramaClosed();\n orbit.resumeAfterFlight(model.focalWorld);\n loop.requestRender();\n },\n });\n analytics.panoramaOpened();\n };\n\n const cancelFlight = (): void => {\n flightGen++; // supersede any pending .then(openPanorama)\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 // 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) { panorama.dispose(); panorama = null; }\n frozen = false;\n\n const gen = ++flightGen;\n const seatEye = seatEyeWorld(idx);\n const focal = model.focalWorld;\n const start: Vec3Arr = [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z];\n const { waypoints, finalPos } = buildWaypoints(start, seatEye, focal, model.bounds.center, model.bounds.radius);\n\n if (reducedMotion()) {\n // a11y: no flight — snap to the seat pose, short dissolve to the panorama.\n placeCameraFinal(finalPos, focal);\n loop.requestRender();\n analytics.cinematicSkipped();\n return openPanorama(seatId, 300, gen).then(() => { if (!disposed) orbit.syncFromCamera(); });\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).then(() => {\n if (disposed || gen !== flightGen) return; // disposed or superseded (retarget/cancel)\n analytics.cinematicPlayed(FLIGHT_DURATION_MS);\n return openPanorama(seatId, 400, gen);\n });\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 (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) { analytics.cinematicCancelled(); cancelFlight(); }\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 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 pick.syncFromSeatProgram(gpu.seatProgram);\n const rect = glctx.canvas.getBoundingClientRect();\n const dpr = glctx.renderer.dpr;\n const { x, y } = pickPixelCoords(e.clientX, e.clientY, rect, dpr, glctx.gl.drawingBufferWidth, glctx.gl.drawingBufferHeight);\n const radius = Math.max(2, Math.round(8 * dpr));\n const idx = pick.pick(orbit.camera, x, y, radius);\n if (idx < 0 || idx >= seatIdByIndex.length) {\n if (selection.size) setSelection([]);\n return;\n }\n const seatId = seatIdByIndex[idx];\n if (selection.has(seatId) && selection.size === 1) setSelection([]);\n else 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 resize() {\n const { width, height } = glctx.resize();\n orbit.setAspect(width / Math.max(1, height));\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 setReducedMotionForTest(value) {\n reducedForced = value;\n },\n dispose() {\n disposed = true;\n cancelFlight();\n loop.stop();\n ro?.disconnect();\n if (panorama) { panorama.dispose(); panorama = null; }\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(model.seatCount, hasHeights);\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 private lostHandler: (e: Event) => void;\n private restoredHandler: () => void;\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\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.gl.clearColor(BACKGROUND.top[0], BACKGROUND.top[1], BACKGROUND.top[2], 1);\n\n container.appendChild(this.canvas);\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/** 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 stageTop: [0.42, 0.36, 0.26] as RGB, // warm, slightly emissive read\n stageWall: [0.26, 0.22, 0.16] 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} 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 camera, damped, always centred on the venue focal point. Drag =\n * azimuth/polar; wheel/pinch = dolly. Touch: 1-finger orbit, 2-finger pinch\n * dolly (+ pan). No desktop pan for v1. Polar clamped [15°,80°]; distance clamped\n * to bounds-derived limits; initial framing = a 3/4 view fitted to bounds.\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 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\n private dragging = false;\n private lastX = 0;\n private lastY = 0;\n private activePointers = new Map<number, { x: number; y: number }>();\n private pinchDist = 0;\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\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 // 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 this.dragging = true;\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n } else if (this.activePointers.size === 2) {\n this.dragging = false;\n this.pinchDist = this.currentPinchDistance();\n }\n };\n this.onPointerMove = (e) => {\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 return;\n }\n if (!this.dragging) return;\n const dx = e.clientX - this.lastX;\n const dy = e.clientY - this.lastY;\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;\n };\n this.onWheel = (e) => {\n e.preventDefault();\n // Normalise wheel delta across px / line / page modes to ~±1 per notch,\n // then zoom multiplicatively so every notch makes a real difference.\n const unit = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? 100 : 1;\n const norm = (e.deltaY * unit) / 100;\n this.dollyBy(Math.exp(norm * 0.4));\n this.fireGesture();\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 }\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 /**\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): void {\n this.target.set(bounds.center[0], bounds.center[1], bounds.center[2]);\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 const aspect = 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 = -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 /** 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 moving = Math.abs(da) > 1e-4 || Math.abs(dp) > 1e-4 || Math.abs(dd) > 1e-4;\n this.azimuth += da * DAMP;\n this.polar += dp * DAMP;\n this.distance += dd * 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 }\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.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.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 * Distance-based seat level-of-detail (v1). Beyond a bounds-derived threshold the\n * dots shrink and fade toward the tier colour (both uniform-driven, no geometry\n * change); below it they stay full. The POINTS fallback rung can come later.\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\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 };\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 };\n}\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 type { Point } from '../../core/types';\nimport { 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 /** Vertex count (position.length / 3). */\n count: number;\n}\n\n/** Accumulates flat-shaded, per-vertex-coloured triangles. */\nexport class MeshBuilder {\n private pos: number[] = [];\n private nor: number[] = [];\n private col: number[] = [];\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 this.pos.push(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]);\n this.nor.push(n[0], n[1], n[2], n[0], n[1], n[2], n[0], n[1], n[2]);\n this.col.push(c0[0], c0[1], c0[2], c1[0], c1[1], c1[2], c2[0], c2[1], c2[2]);\n }\n\n get vertexCount(): number {\n return this.pos.length / 3;\n }\n\n build(): MeshData {\n return {\n position: new Float32Array(this.pos),\n normal: new Float32Array(this.nor),\n color: new Float32Array(this.col),\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/**\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 */\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): 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 (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 const outline = toCCW(outlineIn);\n const holes = holesIn?.map((h) => toCCW(h)).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 // 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 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 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, cTop);\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 const oc = centroid(outline);\n const rings: Array<{ ring: Point[]; flip: boolean }> = [{ ring: outline, flip: false }];\n if (holes) for (const h of holes) if (h.length >= 3) rings.push({ ring: 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 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 off += p.count;\n }\n return { position, normal, color, count: total };\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 * 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 type { ExpandedSeat } from '../../core/types';\nimport { SEATED_EYE_HEIGHT_M } from '../../core/units';\nimport { M } from './geometry';\nimport { seatStateIndex, type SeatState3D } from '../palette';\n\n/** Lift so a dot sits clearly ON the tier deck (which is drawn ~0.28 m below the\n * resolved surface), never occluded by its own cap. */\nconst SEAT_SURFACE_LIFT_M = 0.18;\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 /** seatId → instance index (drives targeted availability updates). */\n idToIndex: Map<string, number>;\n}\n\n/**\n * Resolve a seat's surface height in world metres. Prefer the section-resolved\n * eye height (already floor-base + rake-rise aware) minus the seated-eye offset\n * so the dot lands on the seating surface; fall back to ground for flat charts.\n */\nfunction seatSurfaceY(seat: ExpandedSeat): number {\n const eye = seat.eyeHeightM;\n if (Number.isFinite(eye)) {\n return Math.max(0, (eye as number) - SEATED_EYE_HEIGHT_M) + SEAT_SURFACE_LIFT_M;\n }\n return SEAT_SURFACE_LIFT_M;\n}\n\nexport function buildSeatInstances(\n seats: ExpandedSeat[],\n initial?: (seat: ExpandedSeat) => SeatState3D,\n): SeatInstanceData {\n const count = seats.length;\n const iPosition = new Float32Array(count * 3);\n const iState = new Float32Array(count);\n const idToIndex = new Map<string, number>();\n for (let i = 0; i < count; i++) {\n const seat = seats[i];\n iPosition[i * 3] = seat.x * M;\n iPosition[i * 3 + 1] = seatSurfaceY(seat);\n iPosition[i * 3 + 2] = seat.y * M;\n iState[i] = seatStateIndex(initial ? initial(seat) : 'available');\n idToIndex.set(seat.id, i);\n }\n return { count, iPosition, iState, idToIndex };\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 * 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 if (idx === prev + 1) { 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 * 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 } from '../../core/types';\nimport { sectionGeometry } from '../../core/units';\nimport { seatStateColorLUT, STRUCTURE, hexToRgb, mix, desaturate, scaleRgb, type RGB } from '../palette';\nimport {\n MeshBuilder, extrudePrism, mergeMeshData, ellipsePolygon, rectPolygon, M, type MeshData,\n} from './geometry';\nimport { buildSeatInstances, type SeatInstanceData } from './seatInstances';\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\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 /** 5 × vec3 flat LUT for the seat fragment shader. */\n stateColorLUT: number[];\n seatCount: number;\n /** Venue focal point in world metres (cinematic look-at target). */\n focalWorld: [number, number, number];\n}\n\nfunction floorUnits(doc: ChartDoc): FloorUnit[] {\n if (doc.floors?.length) {\n return doc.floors.map((f) => ({\n objects: f.objects,\n focal: f.focalPoint ?? doc.focalPoint,\n baseHeightM: f.baseHeightM ?? 0,\n }));\n }\n return [{ objects: doc.objects, focal: doc.focalPoint, baseHeightM: 0 }];\n}\n\nconst AO = { top: 1.0, wallBottom: 0.5, bottomCap: 0.4 };\n\n/** Seats sit on the deck, so the deck surface is drawn this far BELOW the seat\n * dots (which lift SEAT_SURFACE_LIFT_M above the same resolved surface). Keeping\n * the deck under the dots stops the tier cap from occluding its own seats. */\nconst DECK_DROP_M = 0.28;\n/** Hard ceiling on rake rise so a mis-authored / arc-wrapped section can never\n * produce a runaway spike (defect guard). */\nconst MAX_TIER_RISE_M = 25;\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/** Extrude one section into the shared builder (rake-sloped or flat slab). */\nfunction buildTier(builder: MeshBuilder, section: SectionObject, unit: FloorUnit, fill: RGB | null): void {\n if (!section.outline || section.outline.length < 3) return;\n const geo = sectionGeometry(section, { floorBaseHeightM: unit.baseHeightM });\n const bottomY = unit.baseHeightM;\n const rakeRad = (geo.rake * Math.PI) / 180;\n const flat = geo.rake <= 0.01 && geo.height <= bottomY + 0.001;\n const colTop = tintTop(fill, STRUCTURE.tierTop);\n\n if (flat) {\n // Thin slab whose top stays below the seat dots (which sit at ~lift height).\n const topY = bottomY + 0.05;\n extrudePrism(builder, section.outline, section.holes, () => topY, bottomY, colTop, STRUCTURE.tierWall, AO);\n return;\n }\n\n // Front edge = the section's OWN minimum focal distance (mirrors the\n // assignEyeHeights sightline model: rise grows with drawn radial depth). For\n // arc/ring sections whose outline wraps the focal point this per-vertex delta\n // is bounded by MAX_TIER_RISE_M so no vertex spikes into a fin.\n let frontDist = Infinity;\n for (const p of section.outline) {\n const d = Math.hypot(p.x - unit.focal.x, p.y - unit.focal.y);\n if (d < frontDist) frontDist = d;\n }\n const tan = Math.tan(rakeRad);\n const topY = (p: Point): number => {\n const d = Math.hypot(p.x - unit.focal.x, p.y - unit.focal.y);\n const depthM = Math.max(0, d - frontDist) * M;\n const rise = Math.min(depthM * tan, MAX_TIER_RISE_M);\n return Math.max(bottomY + 0.05, geo.height + rise - DECK_DROP_M);\n };\n extrudePrism(builder, section.outline, section.holes, topY, bottomY, colTop, STRUCTURE.tierWall, AO);\n}\n\n/** Resolve a shape object to a closed chart-unit polygon (or null to skip). */\nfunction shapePolygon(shape: Extract<ChartObject, { type: 'shape' }>): Point[] | null {\n if (shape.kind === 'polygon' && shape.points && shape.points.length >= 3) return shape.points;\n if (shape.kind === 'rect' && shape.width && shape.height) {\n return 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 return ellipsePolygon(cx, cy, shape.width / 2, shape.height / 2);\n }\n return null; // line / polyline are stroke-only\n}\n\nfunction buildShape(builder: MeshBuilder, shape: Extract<ChartObject, { type: 'shape' }>, base: number): void {\n const poly = shapePolygon(shape);\n if (!poly) return;\n const isStage = shape.role === 'stage';\n const height = isStage ? base + 1.0 : base + 0.25;\n const colTop = isStage ? STRUCTURE.stageTop : STRUCTURE.decorTop;\n const colWall = isStage ? STRUCTURE.stageWall : STRUCTURE.decorWall;\n extrudePrism(builder, poly, undefined, () => height, base, colTop, colWall, AO);\n}\n\nfunction buildGa(builder: MeshBuilder, ga: Extract<ChartObject, { type: 'gaArea' }>, base: number, fill: RGB | null): void {\n if (!ga.points || ga.points.length < 3) return;\n const colTop = tintTop(fill, STRUCTURE.gaTop);\n extrudePrism(builder, ga.points, ga.holes, () => base + 0.15, base, colTop, STRUCTURE.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 }\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 /** 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 const units = floorUnits(doc);\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, STRUCTURE.ground, STRUCTURE.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\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'section') buildTier(builder, o, unit, sectionFill(o, sectionFills));\n else if (o.type === 'shape') buildShape(builder, o, unit.baseHeightM);\n else if (o.type === 'gaArea') buildGa(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)));\n }\n }\n\n const solids = mergeMeshData([builder.build()]);\n const seatData: SeatInstanceData = buildSeatInstances(seats, input.initialState);\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\n const focal = doc.focalPoint ?? { x: (fp.minX + fp.maxX) / 2, y: (fp.minY + fp.maxY) / 2 };\n\n return {\n solids,\n seats: seatData,\n bounds: { center: [cx, radius * 0.08, cz], radius, groundY: 0 },\n stateColorLUT: seatStateColorLUT(),\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 };\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.\n */\n\nimport { Geometry, Mesh, Program, Transform, type OGLRenderingContext } from 'ogl';\nimport { BACKGROUND, seatStateColorByIndex } from '../palette';\nimport { createBackgroundProgram, createSeatProgram, createSolidProgram } from './materials';\nimport type { SceneModel } from './sceneModel';\nimport type { DirtyRun } from './seatInstances';\n\n/** Fill an iColor buffer range from the current iState values (state → colour). */\nfunction writeSeatColors(iColor: Float32Array, iState: Float32Array, start: number, count: number): void {\n for (let i = start; i < start + count; i++) {\n const c = seatStateColorByIndex(iState[i]);\n iColor[i * 3] = c[0];\n iColor[i * 3 + 1] = c[1];\n iColor[i * 3 + 2] = c[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 /** Shared instanced seat geometry (reused by the pick pass — no buffer copy). */\n seatGeometry: 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 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 const bgProg = createBackgroundProgram(gl, BACKGROUND.top as unknown as number[], BACKGROUND.bottom as unknown 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 });\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 writeSeatColors(iColor, model.seats.iState, 0, model.seats.count);\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 });\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 return {\n main,\n background,\n seatProgram: seatProg,\n seatGeometry: seatGeo,\n solidGeometry: solidGeo,\n drawCalls: 3,\n uploadSeatStateRuns(runs: DirtyRun[]): void {\n if (!runs.length) return;\n // Refresh only the changed instance colours from the (already-mutated)\n // iState, then upload just those contiguous ranges — never the whole buffer.\n for (const run of runs) writeSeatColors(iColor, model.seats.iState, run.start, run.length);\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 },\n };\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 type { OGLRenderingContext } from 'ogl';\n\nconst SOLID_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nin vec3 normal;\nin vec3 color;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform mat3 normalMatrix;\nout vec3 vColor;\nout vec3 vNormalView;\nout vec3 vPosView;\nvoid main() {\n vec4 mv = modelViewMatrix * vec4(position, 1.0);\n vPosView = mv.xyz;\n vNormalView = normalize(normalMatrix * 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 vNormalView;\nin vec3 vPosView;\nout vec4 fragColor;\nvoid main() {\n vec3 N = normalize(vNormalView);\n vec3 V = normalize(-vPosView);\n float hemi = 0.5 + 0.5 * N.y; // sky/ground gradient\n vec3 L = normalize(vec3(0.4, 0.85, 0.55)); // warm key, view space\n float key = max(dot(N, L), 0.0);\n vec3 base = vColor * (0.60 + 0.32 * hemi) + vColor * key * 0.32;\n float fres = pow(1.0 - max(dot(N, V), 0.0), 3.0);\n base += vec3(0.26, 0.31, 0.38) * fres * 0.35; // cool rim, restrained\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)\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uSeatRadius;\nuniform float uSeatScale;\nuniform float uMinPixels;\nuniform float uPixelToWorld; // (2*tan(fovY/2)) / viewportHeightPx\nout vec2 vUv;\nout vec3 vColor;\nvoid main() {\n vec4 mv = modelViewMatrix * vec4(iOffset, 1.0);\n float depth = max(-mv.z, 0.001);\n float minR = uMinPixels * depth * uPixelToWorld; // screen-space floor\n float r = max(uSeatRadius * uSeatScale, minR);\n mv.xy += position * r; // camera-facing billboard\n vUv = position;\n vColor = iColor;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst SEAT_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nin vec3 vColor;\nuniform float uSeatFade; // fade toward tier colour with distance (LOD)\nuniform vec3 uFadeColor;\nout vec4 fragColor;\nvoid main() {\n float d = length(vUv);\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 fragColor = vec4(c, alpha);\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;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uSeatRadius;\nuniform float uSeatScale;\nuniform float uMinPixels;\nuniform float uPixelToWorld;\nout vec2 vUv;\nflat out vec3 vPick;\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 float minR = uMinPixels * depth * uPixelToWorld;\n float r = max(uSeatRadius * uSeatScale, minR);\n mv.xy += position * r;\n vUv = position;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst SEAT_PICK_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nflat in vec3 vPick;\nout vec4 fragColor;\nvoid main() {\n if (length(vUv) > 1.0) discard; // round hit-mask matches the dot\n fragColor = vec4(vPick, 1.0);\n}`;\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: 0.22 },\n uSeatScale: { value: 1 },\n uMinPixels: { value: 2.5 },\n uPixelToWorld: { value: 0.002 },\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 });\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: 0.22 },\n uSeatScale: { value: 1 },\n uMinPixels: { value: 2.5 },\n uPixelToWorld: { value: 0.002 },\n uSeatFade: { value: 0 },\n uFadeColor: { value: new Float32Array([0.32, 0.37, 0.43]) },\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 { createPickDepthProgram, createSeatPickProgram } from '../scene/materials';\nimport { BACKGROUND } from '../palette';\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 depthProg: Program;\n private seatScene = new Transform();\n private solidScene = new Transform();\n private target: RenderTarget | null = null;\n private maxIndex: number;\n\n constructor(renderer: Renderer, seatGeo: 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.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 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 const [br, bg, bb] = BACKGROUND.top;\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 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.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, 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 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(waypoints: Vec3Arr[], startQuat: Quat, endQuat: Quat, duration = FLIGHT_DURATION_MS): 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.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);\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","/**\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\nexport interface SeatView {\n url: string;\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}\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;\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 onClose?: () => void;\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 root = document.createElement('div');\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-label', opts.seatLabel ? `View from ${opts.seatLabel}` : 'View from seat');\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(\"${view.url}\")`, 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: '34px', height: '34px', 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 const hint = document.createElement('div');\n hint.textContent = 'Drag to look around · 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\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 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 bgH = windowedBgHeight(vh);\n bgW = bgH * (natW / natH);\n pano.style.backgroundSize = `${bgW}px ${bgH}px`;\n if (!posInitialised) { posX = bearingToOffsetPx(bearing, vw, bgW); posInitialised = true; }\n pitchPx = clampPitchPx(pitchPx, 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 const img = new Image();\n img.onload = layout;\n img.src = view.url;\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 const onDown = (e: PointerEvent): void => {\n dragging = true; lastX = e.clientX; lastY = e.clientY; pano.style.cursor = 'grabbing';\n try { pano.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onMove = (e: PointerEvent): void => {\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 dragging = false; pano.style.cursor = 'grab';\n try { pano.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n pano.addEventListener('pointerdown', onDown);\n pano.addEventListener('pointermove', onMove);\n pano.addEventListener('pointerup', onUp);\n pano.addEventListener('pointercancel', onUp);\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 window.removeEventListener('keydown', onKey);\n };\n const teardown = (): void => {\n if (fadeTimer) { window.clearTimeout(fadeTimer); fadeTimer = 0; }\n removeListeners();\n if (root.parentNode) root.parentNode.removeChild(root);\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 === 'Escape') { e.stopPropagation(); close(); }\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 * 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\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(seats: number, hasHeights: boolean): void {\n this.emit('3d_opened', { seats, hasHeights });\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"],"mappings":"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","names":["import_ogl","import_ogl","norm","now","earcut","topY","import_ogl","import_ogl","sub","import_ogl","import_ogl","now","DEG"]}
|
|
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/core/types.ts","../../src/core/units.ts","../../src/view3d/scene/geometry.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/surface.ts","../../src/view3d/scene/deckBands.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/crossfade/panorama.ts","../../src/view3d/analytics.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 } from './lod';\nimport { SEAT_DOT_RADIUS_M } from './scene/seatInstances';\nimport { buildSceneModel, type SceneModel, type SceneZone, type SceneFloor } from './scene/sceneModel';\nimport { LabelOverlay } from './labelOverlay';\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 { mountPanorama, type PanoramaHandle, type SeatView } from './crossfade/panorama';\nimport { Analytics3D, type Analytics3DCallback } from './analytics';\nimport type { SeatState3D } from './palette';\n\nexport type { SeatState3D } from './palette';\nexport type { SeatView } from './crossfade/panorama';\nexport type { Analytics3DCallback } from './analytics';\nexport { buildSceneModel } from './scene/sceneModel';\n\n/** Seat eye height above its deck: SEATED_EYE_HEIGHT_M (1.2) − seat lift (0.18). */\nconst SEAT_EYE_ABOVE_DECK = 1.02;\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}\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 /**\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 /**\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\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 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 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 model: SceneModel = 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\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 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 rebuildGpu = (): void => {\n gpu = buildGpuScene(glctx.gl, model);\n pick = new PickPipeline(glctx.renderer, gpu.seatGeometry, gpu.solidGeometry, model.seats.count);\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 };\n\n const glctx = new GLContext(container, {\n onContextLost: () => {\n contextLost = true;\n loop.stop();\n gpu = null;\n pick = null;\n },\n onContextRestored: () => {\n rebuildGpu();\n contextLost = false;\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 orbit.frame(model.bounds, true, stageAzimuth);\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 if (!container.style.position || container.style.position === 'static') {\n container.style.position = 'relative';\n }\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\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 const u = gpu.seatProgram.uniforms;\n u.uSeatScale.value = lod.scale;\n u.uSeatFade.value = lod.fade;\n u.uPixelToWorld.value = (2 * Math.tan((orbit.camera.fov * DEG) / 2)) / Math.max(1, glctx.pixelHeight);\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 );\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 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 // 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 seatEyeWorld = (idx: number): Vec3Arr => [\n model.seats.iPosition[idx * 3],\n model.seats.iPosition[idx * 3 + 1] + SEAT_EYE_ABOVE_DECK,\n model.seats.iPosition[idx * 3 + 2],\n ];\n\n const placeCameraFinal = (finalPos: Vec3Arr, focal: Vec3Arr): 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_END;\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 let arriveChip: HTMLButtonElement | null = null;\n const removeArriveChip = (): void => {\n arriveChip?.remove();\n arriveChip = null;\n };\n const showArriveChip = (seatId: string, gen: number): void => {\n removeArriveChip();\n if (disposed || !opts.getSeatView) return; // no 360 source → nothing to offer\n const chip = document.createElement('button');\n chip.type = 'button';\n chip.textContent = '◉ View in 360°';\n chip.setAttribute('aria-label', `Open the 360° view from seat ${seatId}`);\n Object.assign(chip.style, {\n position: 'absolute', left: '50%', bottom: '18px', transform: 'translateX(-50%)',\n minHeight: '44px', padding: '10px 18px', borderRadius: '999px',\n 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', zIndex: '4',\n } as Partial<CSSStyleDeclaration>);\n chip.addEventListener('click', () => {\n if (disposed || gen !== flightGen) { removeArriveChip(); return; }\n removeArriveChip();\n void openPanorama(seatId, 400, gen);\n });\n container.appendChild(chip);\n arriveChip = chip;\n };\n\n // --- overview chip: always-available \"take me home\" control. Free orbit can\n // strand you behind the shell staring at walls; one tap glides back to the\n // stage-side 3/4 framing. (Owner: \"we don't have much control in 3D\".)\n const overviewChip = document.createElement('button');\n overviewChip.type = 'button';\n overviewChip.textContent = '⌂ Overview';\n overviewChip.setAttribute('aria-label', 'Return to the venue overview');\n Object.assign(overviewChip.style, {\n position: 'absolute', right: '14px', bottom: '18px',\n minHeight: '40px', padding: '8px 14px', 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 12.5px/1 inherit', cursor: 'pointer', zIndex: '4',\n } as Partial<CSSStyleDeclaration>);\n overviewChip.addEventListener('click', () => {\n if (disposed || frozen) return; // panorama owns the screen while frozen\n cancelFlight();\n removeArriveChip();\n orbit.frameSoft(model.bounds, stageAzimuth);\n loop.requestRender();\n });\n container.appendChild(overviewChip);\n\n const openPanorama = async (seatId: string, fadeMs: number, gen: number): Promise<void> => {\n const viewPromise = ensureSeatView(seatId);\n if (!viewPromise) { orbit.syncFromCamera(); return; } // 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 // 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) { frozen = false; orbit.resumeAfterFlight(model.focalWorld); loop.requestRender(); }\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) return;\n removeArriveChip();\n panorama = mountPanorama(container, view, {\n fadeMs,\n seatLabel: seatId,\n onClose: () => {\n panorama = null;\n frozen = false;\n analytics.panoramaClosed();\n orbit.resumeAfterFlight(model.focalWorld);\n loop.requestRender();\n // Back in the live scene at the seat — offer the 360 again.\n showArriveChip(seatId, flightGen);\n },\n });\n analytics.panoramaOpened();\n };\n\n const cancelFlight = (): void => {\n flightGen++; // supersede any pending .then(openPanorama)\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 // 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) { panorama.dispose(); panorama = null; }\n frozen = false;\n\n const gen = ++flightGen;\n removeArriveChip();\n const seatEye = seatEyeWorld(idx);\n const focal = model.focalWorld;\n const start: Vec3Arr = [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z];\n const { waypoints, finalPos } = buildWaypoints(start, seatEye, focal, model.bounds.center, model.bounds.radius);\n\n if (reducedMotion()) {\n // a11y: no flight — snap straight to the seat pose in the live scene.\n placeCameraFinal(finalPos, focal);\n loop.requestRender();\n analytics.cinematicSkipped();\n if (!disposed) orbit.syncFromCamera();\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).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, still free to orbit. The painted 360 is the chip, not the\n // landing: the real scene is the payoff moment.\n orbit.resumeAfterFlight(model.focalWorld);\n loop.requestRender();\n showArriveChip(seatId, gen);\n });\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 (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) { analytics.cinematicCancelled(); cancelFlight(); }\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 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 pick.syncFromSeatProgram(gpu.seatProgram);\n const rect = glctx.canvas.getBoundingClientRect();\n const dpr = glctx.renderer.dpr;\n const { x, y } = pickPixelCoords(e.clientX, e.clientY, rect, dpr, glctx.gl.drawingBufferWidth, glctx.gl.drawingBufferHeight);\n const radius = Math.max(2, Math.round(8 * dpr));\n const idx = pick.pick(orbit.camera, x, y, radius);\n if (idx < 0 || idx >= seatIdByIndex.length) {\n if (selection.size) setSelection([]);\n return;\n }\n const seatId = seatIdByIndex[idx];\n if (selection.has(seatId) && selection.size === 1) setSelection([]);\n else 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 resize() {\n const { width, height } = glctx.resize();\n orbit.setAspect(width / Math.max(1, height));\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 if (gpu) {\n gpu.seatProgram.uniforms.uFocusFloor.value = value;\n gpu.solidProgram.uniforms.uFocusFloor.value = value;\n }\n focusedFloor = value;\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 cinematic.cancel();\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 setReducedMotionForTest(value) {\n reducedForced = value;\n },\n dispose() {\n disposed = true;\n removeArriveChip();\n overviewChip.remove();\n cancelFlight();\n loop.stop();\n labelOverlay.dispose();\n ro?.disconnect();\n if (panorama) { panorama.dispose(); panorama = null; }\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(model.seatCount, hasHeights);\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\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/** 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 stageTop: [0.42, 0.36, 0.26] as RGB, // warm, slightly emissive read\n stageWall: [0.26, 0.22, 0.16] 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 camera, damped, always centred on the venue focal point. Drag =\n * azimuth/polar; wheel/pinch = dolly. Touch: 1-finger orbit, 2-finger pinch\n * dolly (+ pan). No desktop pan for v1. Polar clamped [15°,80°]; distance clamped\n * to bounds-derived limits; initial framing = a 3/4 view fitted to bounds.\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 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\n private dragging = false;\n private lastX = 0;\n private lastY = 0;\n private activePointers = new Map<number, { x: number; y: number }>();\n private pinchDist = 0;\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\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 // 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 this.dragging = true;\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n } else if (this.activePointers.size === 2) {\n this.dragging = false;\n this.pinchDist = this.currentPinchDistance();\n }\n };\n this.onPointerMove = (e) => {\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 return;\n }\n if (!this.dragging) return;\n const dx = e.clientX - this.lastX;\n const dy = e.clientY - this.lastY;\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;\n };\n this.onWheel = (e) => {\n e.preventDefault();\n // Normalise wheel delta across px / line / page modes to ~±1 per notch,\n // then zoom multiplicatively so every notch makes a real difference.\n const unit = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? 100 : 1;\n const norm = (e.deltaY * unit) / 100;\n this.dollyBy(Math.exp(norm * 0.4));\n this.fireGesture();\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 }\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 /**\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): void {\n this.target.set(bounds.center[0], bounds.center[1], bounds.center[2]);\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 const aspect = 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): void {\n this.target.set(bounds.center[0], bounds.center[1], bounds.center[2]);\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 = 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 /** 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 moving = Math.abs(da) > 1e-4 || Math.abs(dp) > 1e-4 || Math.abs(dd) > 1e-4;\n this.azimuth += da * DAMP;\n this.polar += dp * DAMP;\n this.distance += dd * 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 }\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.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.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 * Distance-based seat level-of-detail (v1). Beyond a bounds-derived threshold the\n * dots shrink and fade toward the tier colour (both uniform-driven, no geometry\n * change); below it they stay full. The POINTS fallback rung can come later.\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\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 };\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 };\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 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 /** 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/**\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/** 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 {\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 /** 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 /** Default commercial attributes inherited by seats without an override. */\n commercial?: SeatCommercialAttributes;\n}\n\nexport interface GAAreaObject {\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 {\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 /** 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 {\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 {\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 {\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 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] 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 interface TextObject {\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}\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 {\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}\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 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 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}\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 /** 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 /** 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 /** 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. */\nexport class MeshBuilder {\n private pos: number[] = [];\n private nor: number[] = [];\n private col: number[] = [];\n private flr: number[] = [];\n /** Floor index stamped onto every triangle emitted from now on. */\n private currentFloor = 0;\n\n /** Stamp subsequent triangles as belonging to `index`. */\n setFloor(index: number): void {\n this.currentFloor = 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.push(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]);\n this.nor.push(n[0], n[1], n[2], n[0], n[1], n[2], n[0], n[1], n[2]);\n this.col.push(c0[0], c0[1], c0[2], c1[0], c1[1], c1[2], c2[0], c2[1], c2[2]);\n this.flr.push(this.currentFloor, this.currentFloor, this.currentFloor);\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 if (isDegenerate(p0, p1, p2)) return;\n this.pos.push(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]);\n this.nor.push(n0[0], n0[1], n0[2], n1[0], n1[1], n1[2], n2[0], n2[1], n2[2]);\n this.col.push(c0[0], c0[1], c0[2], c1[0], c1[1], c1[2], c2[0], c2[1], c2[2]);\n this.flr.push(this.currentFloor, this.currentFloor, this.currentFloor);\n }\n\n get vertexCount(): number {\n return this.pos.length / 3;\n }\n\n build(): MeshData {\n return {\n position: new Float32Array(this.pos),\n normal: new Float32Array(this.nor),\n color: new Float32Array(this.col),\n floor: new Float32Array(this.flr),\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 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 off += p.count;\n }\n return { position, normal, color, floor, 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 * 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 { seatStateIndex, 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 * 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 * 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 /** 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): SeatInstanceData {\n const count = seats.length;\n const iPosition = new Float32Array(count * 3);\n const iState = new Float32Array(count);\n const iMaxRadius = 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 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 // 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, iMaxRadius, iRing, idToIndex,\n iFloor: seatFloor ?? 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' | 'annotation' | 'booth';\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/** 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 if (d <= SECTION_MAX_DISTANCE) out.add('section');\n if (d <= NEAR_MAX_DISTANCE) { out.add('annotation'); out.add('booth'); }\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/** 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 } 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 { buildVenueSurfaces, CAP_MAX_ERROR_M, type SectionSurface } from './surface';\nimport { emitDeckBands, deckFootprints } 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/** 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 /** 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 * 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\nfunction floorUnits(doc: ChartDoc): FloorUnit[] {\n if (doc.floors?.length) {\n return doc.floors.map((f) => ({\n objects: f.objects,\n focal: f.focalPoint ?? doc.focalPoint,\n baseHeightM: f.baseHeightM ?? 0,\n }));\n }\n return [{ objects: doc.objects, 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 __tp = performance.now();\n const padded = outsetRing(section.outline, SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE);\n __t('outsetRing', __tp);\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 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 const __tf = performance.now();\n const blocks = clipFootprints(deckFootprints(surface.rowLevels, unit.focal), footprint);\n __t('deckFootprints', __tf);\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 extrudePrism(builder, b.outline, b.holes, () => b.topY, bottomY,\n colTop, S.tierWall, AO, undefined, capUp);\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 __tb = performance.now();\n emitDeckBands(builder, surface.rowLevels, unit.focal, surface.landingY, {\n tread: [colTop[0] * AO.top, colTop[1] * AO.top, colTop[2] * AO.top],\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],\n }, footprint.length === 1 ? outline : footprint.flat());\n __t('emitDeckBands', __tb);\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 for (const ring of claimed.subtract(outline)) {\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 */\nclass ClaimedArea {\n private rings: Array<[number, number][][]> = [];\n private boxes: RingBox[] = [];\n\n /** `ring` minus everything claimed so far; then claim what is returned. */\n subtract(ring: Point[]): 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 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 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\n/** Height of a banquet table top, world metres (standard dining height). */\nconst TABLE_HEIGHT_M = 0.75;\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\n/** Resolve a shape object to a closed chart-unit polygon (or null to skip). */\nfunction shapePolygon(shape: Extract<ChartObject, { type: 'shape' }>): Point[] | null {\n if (shape.kind === 'polygon' && shape.points && shape.points.length >= 3) return shape.points;\n if (shape.kind === 'rect' && shape.width && shape.height) {\n return 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 return ellipsePolygon(cx, cy, shape.width / 2, shape.height / 2);\n }\n return null; // line / polyline are stroke-only\n}\n\nfunction buildShape(builder: MeshBuilder, shape: Extract<ChartObject, { type: 'shape' }>, base: number, S: typeof STRUCTURE): void {\n const poly = shapePolygon(shape);\n if (!poly) return;\n const isStage = shape.role === 'stage';\n const height = isStage ? base + 1.0 : base + 0.25;\n const colTop = isStage ? S.stageTop : S.decorTop;\n const colWall = isStage ? S.stageWall : S.decorWall;\n extrudePrism(builder, poly, undefined, () => height, base, colTop, colWall, 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 }\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 /** Optional initial per-seat state (default all available). */\n initialState?: (seat: ExpandedSeat) => import('../palette').SeatState3D;\n}\n\nexport const __PROF: Record<string, number> = {};\nconst __t = (k: string, t0: number): void => {\n __PROF[k] = (__PROF[k] ?? 0) + (performance.now() - t0);\n};\n\nexport function buildSceneModel(input: SceneModelInput): SceneModel {\n for (const k of Object.keys(__PROF)) delete __PROF[k];\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);\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\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 __t0 = performance.now();\n const surfaces = buildVenueSurfaces(units, seats);\n __t('surfaces', __t0);\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 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') {\n const t = performance.now();\n buildTier(builder, o, unit, sectionFill(o, sectionFills), surfaces.bySection.get(o.id), claimed, siblings, S);\n __t('buildTier', t);\n }\n else if (o.type === 'shape') buildShape(builder, o, unit.baseHeightM, S);\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 }\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 __tm = performance.now();\n const solids = mergeMeshData([builder.build()]);\n __t('meshBuild+merge', __tm);\n const __ts = performance.now();\n const seatData: SeatInstanceData = buildSeatInstances(seats, input.initialState, surfaces, seatFloor);\n __t('seatInstances', __ts);\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 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 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 }\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 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: [(a.x / a.n) * M, a.deck / a.n + SECTION_LABEL_LIFT_M, (a.y / a.n) * M],\n });\n }\n }\n }\n for (const unit of units) {\n for (const o of unit.objects) {\n 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 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\n return {\n solids,\n seats: seatData,\n bounds: { center: [cx, radius * 0.08, cz], radius, groundY: 0 },\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 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 SeatOverride,\n SectionObject,\n TableObject,\n} from './types';\nimport { distributeAlongCubic } from './complexGeometry';\nimport { translateSectionOutlinePath } from './sectionPath';\nimport { toLetters, toRoman } from './labeling';\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 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 * Number outward from the middle: rank seats by distance from centre (inner-left\n * wins ties), so the centre seat gets rank 0 (the lowest number). Shared by the\n * `center` direction across every scheme.\n */\nfunction centerRank(n: number): number[] {\n const rank = new Array<number>(n);\n Array.from({ length: n }, (_, i) => i)\n .sort((a, b) => Math.abs(2 * a - (n - 1)) - Math.abs(2 * b - (n - 1)) || a - b)\n .forEach((idx, k) => (rank[idx] = k));\n return rank;\n}\n\n/**\n * The seat NUMBER part of a row seat's label (the row prefix is prepended by the\n * caller). Applies the row's numbering scheme, direction, step, start and label\n * prefix. Labels only — never geometry. See `RowObject.seatNumbering.scheme`.\n */\nexport function seatLabelPart(row: RowObject, i: number): string {\n const rawStart = row.seatLabelStart ?? 1;\n const dir = row.seatNumbering?.direction ?? 'ltr';\n const step = row.seatNumbering?.step ?? 1;\n const scheme = row.seatNumbering?.scheme ?? 'decimal';\n const prefix = row.seatNumbering?.prefix ?? '';\n const endAt = row.seatNumbering?.endAt;\n const n = row.seatCount;\n\n // Both up/down variants replace direction and number by physical left→right\n // order. `updown` is odd-up-even-back (1,3,5,…,6,4,2); the distinct reverse\n // variant is odd-back-even-up (…5,3,1,2,4,6). `start` shifts either sequence.\n // They own their sequence, so both ignore `endAt`.\n if (scheme === 'updown' || scheme === 'updown-descending') {\n const half = Math.ceil(n / 2);\n const core = scheme === 'updown'\n ? (i < half ? rawStart + 2 * i : rawStart - 1 + 2 * (n - i))\n : (i < half ? rawStart + 2 * (half - 1 - i) : rawStart + 1 + 2 * (i - half));\n return `${prefix}${core}`;\n }\n\n // End-at preset (\"useEndAt\"): derive `start` so the LAST-numbered seat\n // (position rank n-1) lands on `endAt`, honouring the scheme's effective step\n // (odd/even = 2). `endAt` wins over the stored `seatLabelStart`.\n const effStep = scheme === 'odd' || scheme === 'even' ? 2 : step;\n const start = endAt != null && Number.isFinite(endAt) ? endAt - (n - 1) * effStep : rawStart;\n\n // Position rank p ∈ [0, n-1]: the 0-based ordinal along the numbering\n // direction. Every remaining scheme is a formatting of `start + p*step`.\n const p = dir === 'center' ? centerRank(n)[i] : dir === 'rtl' ? n - 1 - i : i;\n\n let core: string;\n switch (scheme) {\n case 'odd': {\n const firstOdd = start % 2 === 1 ? start : start + 1;\n core = String(firstOdd + p * 2);\n break;\n }\n case 'even': {\n const firstEven = start % 2 === 0 ? start : start + 1;\n core = String(firstEven + p * 2);\n break;\n }\n case 'roman':\n core = toRoman(start + p * step);\n break;\n case 'letters-upper':\n core = toLetters(start + p * step, false);\n break;\n case 'letters-lower':\n core = toLetters(start + p * step, true);\n break;\n case 'decimal':\n default:\n core = String(start + p * step);\n break;\n }\n return `${prefix}${core}`;\n}\n\nexport function expandRowSlots(row: RowObject): 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 const part = seatLabelPart(row, i);\n const inventoryLabel = o?.label ?? `${row.label}-${part}`;\n const displayPrefix = row.displayLabel ?? row.label;\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: o?.displayLabel ?? `${displayPrefix}-${part}`,\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 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 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 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.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/** Stable interior label anchor that cannot land inside a polygon cutout. */\nexport function polygonLabelPoint(outer: Point[], holes: Point[][] | undefined): Point {\n if (!outer.length) return { x: 0, y: 0 };\n const xs = outer.map((point) => point.x);\n const ys = outer.map((point) => point.y);\n const bounds = { minX: Math.min(...xs), maxX: Math.max(...xs), minY: Math.min(...ys), maxY: Math.max(...ys) };\n const centroid = polygonCentroid(outer);\n if (pointInPolygonWithHoles(centroid, outer, holes)) return centroid;\n let best = outer[0];\n let bestScore = -Infinity;\n const rings = [outer, ...(holes ?? [])];\n for (let row = 1; row < 24; row += 1) {\n for (let column = 1; column < 24; column += 1) {\n const point = {\n x: bounds.minX + ((bounds.maxX - bounds.minX) * column) / 24,\n y: bounds.minY + ((bounds.maxY - bounds.minY) * row) / 24,\n };\n if (!pointInPolygonWithHoles(point, outer, holes)) continue;\n const score = Math.min(...rings.flatMap((ring) => ring.map((start, index) => {\n const end = ring[(index + 1) % ring.length];\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n const denominator = dx * dx + dy * dy;\n const projection = denominator\n ? ((point.x - start.x) * dx + (point.y - start.y) * dy) / denominator\n : 0;\n const t = Math.max(0, Math.min(1, projection));\n return Math.hypot(point.x - (start.x + t * dx), point.y - (start.y + t * dy));\n })));\n if (score > bestScore) { best = point; bestScore = score; }\n }\n }\n return best;\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}\n\nfunction expandFloorObjects(\n objects: ChartObject[],\n zones: ChartDoc['zones'],\n fallbackFocal: Point | 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?: string,\n): ExpandedSeat[] {\n const out: ExpandedSeat[] = [];\n const segmented = new Map<string, {\n groupId: string;\n adjacencyOffset: number;\n displayOffset: number;\n displayLabel: string;\n totalSeats: number;\n canonical: RowObject;\n viewFromSeatUrl?: string;\n }>();\n\n // Resolve only complete, internally coherent groups. Malformed metadata is\n // surfaced by validation and deliberately falls back to physical-row\n // semantics here, so a corrupt document can never make buyer adjacency more\n // permissive than the legacy model.\n const grouped = new Map<string, RowObject[]>();\n for (const object of objects) {\n if (object.type !== 'row' || !object.segmentedRow) continue;\n const list = grouped.get(object.segmentedRow.groupId) ?? [];\n list.push(object);\n grouped.set(object.segmentedRow.groupId, list);\n }\n for (const [groupId, members] of grouped) {\n const ordered = members.slice().sort((left, right) => (\n left.segmentedRow!.componentIndex - right.segmentedRow!.componentIndex\n ));\n const expectedCount = ordered[0]?.segmentedRow?.componentCount ?? 0;\n const first = ordered[0]?.segmentedRow;\n if (!first) continue;\n const valid = expectedCount >= 2\n && ordered.length === expectedCount\n && first?.boundaryBefore === 'start'\n && ordered.every((row, index) => (\n row.segmentedRow?.kind === 'segmented-row-v1'\n && row.segmentedRow.groupId === groupId\n && row.segmentedRow.componentCount === expectedCount\n && row.segmentedRow.componentIndex === index\n && (index === 0\n ? row.segmentedRow.boundaryBefore === 'start'\n : row.segmentedRow.boundaryBefore !== 'start')\n && row.segmentedRow.displayLabel === first.displayLabel\n ));\n if (!valid) continue;\n const totalSeats = ordered.reduce((sum, row) => sum + row.seatCount, 0);\n let adjacencyOffset = 0;\n let displayOffset = 0;\n for (const row of ordered) {\n if (row.segmentedRow!.boundaryBefore === 'break') adjacencyOffset += 1;\n segmented.set(row.id, {\n groupId,\n adjacencyOffset,\n displayOffset,\n displayLabel: first.displayLabel,\n totalSeats,\n canonical: ordered[0],\n viewFromSeatUrl: first.viewFromSeatUrl,\n });\n adjacencyOffset += row.seatCount;\n displayOffset += row.seatCount;\n }\n }\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 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 const displayOrdinal = logical.displayOffset + physicalIndex;\n seat.logicalRowId = logical.groupId;\n seat.logicalSeatIndex = logical.adjacencyOffset + physicalIndex;\n // A seat-level display override remains the highest-precedence copy.\n if (!overrides.get(physicalIndex)?.displayLabel) {\n const numberingRow: RowObject = {\n ...logical.canonical,\n seatCount: logical.totalSeats,\n label: logical.displayLabel,\n displayLabel: logical.displayLabel,\n };\n seat.displayLabel = `${logical.displayLabel}-${seatLabelPart(numberingRow, displayOrdinal)}`;\n }\n seat.viewUrl ??= logical.viewFromSeatUrl;\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 (inheritedView) seat.viewUrl ??= inheritedView;\n // Floor > venue default: the last authored tier before a generated panorama.\n if (viewFallback) seat.viewUrl ??= viewFallback;\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 ?? doc.viewFromSeatUrl;\n const seats = expandFloorObjects(floor.objects, doc.zones, floorFocal, floorView);\n assignEyeHeights(floor.objects, floor.focalPoint ?? doc.focalPoint, floor.baseHeightM ?? 0, seats);\n out.push(...seats);\n }\n return out;\n }\n const out = expandFloorObjects(doc.objects, doc.zones, doc.focalPoint, doc.viewFromSeatUrl);\n assignEyeHeights(doc.objects, doc.focalPoint, options.floorBaseHeightM ?? 0, out);\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 s of expandChart(doc)) acc(s.x, s.y);\n\n for (const obj of allObjects(doc)) {\n 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 w = obj.fontSize * obj.text.length * 0.6; // approximate glyph advance\n acc(obj.position.x, obj.position.y);\n acc(obj.position.x + w, obj.position.y + obj.fontSize);\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 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';\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 { readonly pts: readonly Point[]; readonly y: number; readonly depth: number; readonly blockId: number }[];\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/** 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 for (const unit of units) {\n for (const o of unit.objects) {\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 // The section's own rake axis, fitted from its rows. `layout.ts` fits the\n // identical axis from the identical grouping; DEPTH, the front datum and the\n // rise all then come from one field, which is what keeps a seat dot and the\n // deck it stands on from being computed under different models.\n const rake = buildSectionRake([...a.rows.values()].map((points) => ({ points })), a.unit.focal);\n\n let frontU = Infinity;\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 const flatTop = bottomY + FLAT_SLAB_TOP_M;\n const baseFloor = bottomY + FLAT_SLAB_TOP_M;\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 const structure = a.hasSeats\n ? resolveSection(id, seats, a.seatIndices, a.unit.focal)\n : { sectionId: id, rows: [] as ResolvedRow[], blockCount: 0 };\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 }));\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 const rowBounds = rowLevels.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\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 const d = 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 => levelFor(rake.depthAt(x, y));\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 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 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\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 */\nconst 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/** 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 */\nfunction neighbourhoods(rows: readonly BandRow[]): Neighbourhood[] {\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 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 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 */\nexport function deckFootprints(rows: readonly BandRow[], focal: Point): DeckFootprint[] {\n if (rows.length < 2) return [];\n const nbrs = neighbourhoods(rows);\n const rings: Array<[number, number][][]> = [];\n const ribbons: Array<Ribbon | null> = [];\n for (let i = 0; i < rows.length; i++) {\n const r = ribbonOf(rows, i, nbrs, focal);\n ribbons.push(r);\n if (!r) continue;\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]); // polygon-clipping wants closed rings\n rings.push([ring]);\n }\n if (!rings.length) return [];\n\n let merged: ReturnType<typeof polygonClipping.union>;\n try {\n merged = polygonClipping.union(rings[0], ...rings.slice(1));\n } catch {\n return []; // never let a degenerate row set break the whole scene\n }\n\n const out: DeckFootprint[] = [];\n for (const poly of merged) {\n if (!poly.length || poly[0].length < 4) continue;\n const toPts = (ring: readonly [number, number][]): Point[] => {\n const pts = ring.map(([x, y]) => ({ x, y }));\n // union() repeats the first point to close the ring; drop the duplicate.\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 return pts;\n };\n const outline = toPts(poly[0]);\n if (outline.length < 3) continue;\n // This block's base is its own lowest row, not the section's — otherwise a\n // block set high in the bowl would be drawn standing on the front block's floor.\n let topY = Infinity;\n for (let i = 0; i < rows.length; i++) {\n const r = ribbons[i];\n if (!r) continue;\n const mid = r.pts[Math.floor(r.pts.length / 2)];\n if (pointInRing(outline, mid.x, mid.y) && rows[i].y < topY) topY = rows[i].y;\n }\n if (!Number.isFinite(topY)) continue;\n out.push({\n outline: simplifyRing(outline, FOOTPRINT_TOLERANCE_U),\n holes: poly.slice(1).map(toPts).map((h) => simplifyRing(h, FOOTPRINT_TOLERANCE_U))\n .filter((h) => h.length >= 3),\n topY,\n });\n }\n return out;\n}\n\n/**\n * Douglas-Peucker simplification tolerance for a block outline, chart units\n * (~7 mm in world metres).\n *\n * Unioning ~100 overlapping ribbons leaves long runs of near-collinear vertices\n * along a block's boundary, and earcut fans those into needles — measured aspect\n * ratios of 18,893 and 4,576,013, the same defect that flat-shades as dark\n * hairlines. The block outline carries no detail at this scale (the SEATS are\n * carried by the ribbons above it, not by this base), so simplifying is free.\n */\nconst FOOTPRINT_TOLERANCE_U = 0.3;\n\n/**\n * Extra width given to a ribbon before it is unioned into a block footprint,\n * chart units (~3 cm world).\n *\n * Unioning ribbons at their exact width makes neighbouring ribbons meet\n * TANGENTIALLY, and a boolean union of tangential shapes emits zero-width slits\n * along the seam — metres long, well under a millimetre wide. Overlapping them\n * decisively removes the seam instead of leaving one to clean up afterwards. The\n * footprint is only the base beneath the ribbons, so a 3 cm margin is invisible.\n */\nconst FOOTPRINT_MARGIN_U = 1.5;\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 // Split at the two extreme points so the closing edge is simplified too.\n const out = simplifyRun([...ring, ring[0]], tol);\n out.pop();\n return out.length >= 3 ? out : ring;\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 quad 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 */\nfunction emitClippedQuad(\n builder: MeshBuilder,\n clipRing: [number, number][][],\n clipTest: ClipTest,\n quad: Point[],\n y: number,\n color: RGB,\n): void {\n // FAST PATH: a quad 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(quad)) {\n const UPF = [0, 1, 0] as const;\n const a = quad[0], b = quad[1], c = quad[2], d = quad[3];\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 builder.tri([a.x * M, y, a.y * M], [c.x * M, y, c.y * M], [d.x * M, y, d.y * M], UPF, color);\n return;\n }\n const ring: [number, number][] = quad.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): void {\n if (rows.length < 2) return;\n const nbrs = neighbourhoods(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\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\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 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 emitClippedQuad(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 // 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 builder.tri(pF, qF, qFd, rn, colors.riser);\n builder.tri(pF, qFd, pFd, rn, colors.riser);\n }\n }\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 { cullOverlapping, projectToScreen, visibleLabelKinds, type SceneLabel } 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 booth: { size: 11, weight: '500', opacity: 0.85 },\n annotation: { size: 11, weight: '400', opacity: 0.75 },\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\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 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 /**\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 ): void {\n if (!this.labels.length) return;\n const kinds = visibleLabelKinds(cameraDistance, venueRadius);\n\n const candidates: Array<{ label: SceneLabel; screen: ReturnType<typeof projectToScreen> }> = [];\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 candidates.push({ label, screen });\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.\n const kept = cullOverlapping(candidates, SEPARATION_X_PX, SEPARATION_Y_PX);\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) return node;\n node = document.createElement('div');\n node.textContent = label.text;\n node.setAttribute('data-label-kind', label.kind);\n const style = KIND_STYLE[label.kind];\n const s = node.style;\n s.position = 'absolute';\n s.left = '0';\n s.top = '0';\n s.whiteSpace = 'nowrap';\n s.fontSize = `${style.size}px`;\n s.fontWeight = style.weight;\n s.opacity = String(style.opacity);\n s.color = label.color ?? this.opts.ink ?? '#e8edf5';\n // A soft dark halo keeps a label legible over both a pale deck and the dark\n // background, without a plate that would clutter a dense venue.\n s.textShadow = '0 1px 3px rgba(0,0,0,0.85), 0 0 8px rgba(0,0,0,0.55)';\n s.letterSpacing = label.kind === 'zone' ? '0.08em' : '0.02em';\n if (label.kind === 'zone') s.textTransform = 'uppercase';\n s.display = 'none';\n this.root.appendChild(node);\n this.nodes.set(label.id, node);\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.\n */\n\nimport { Geometry, Mesh, Program, Transform, type OGLRenderingContext } from 'ogl';\nimport { SEAT_STATES, type RGB } from '../palette';\nimport { createBackgroundProgram, createSeatProgram, createSolidProgram } from './materials';\nimport type { SceneModel } from './sceneModel';\nimport 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(iColor: Float32Array, iState: Float32Array, start: number, count: number, states: readonly RGB[]): void {\n for (let i = start; i < start + count; i++) {\n const c = states[iState[i]] ?? states[0];\n iColor[i * 3] = c[0];\n iColor[i * 3 + 1] = c[1];\n iColor[i * 3 + 2] = c[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 /** Shared instanced seat geometry (reused by the pick pass — no buffer copy). */\n seatGeometry: 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 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 });\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);\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 // 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 return {\n main,\n background,\n seatProgram: seatProg,\n solidProgram: solidProg,\n seatGeometry: seatGeo,\n solidGeometry: solidGeo,\n drawCalls: 3,\n uploadSeatStateRuns(runs: DirtyRun[]): void {\n if (!runs.length) return;\n // Refresh only the changed instance colours from the (already-mutated)\n // iState, then upload just those contiguous ranges — never the whole buffer.\n for (const run of runs) writeSeatColors(iColor, model.seats.iState, run.start, run.length, stateColors);\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 },\n };\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 type { OGLRenderingContext } from 'ogl';\n\nconst SOLID_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nin vec3 normal;\nin vec3 color;\nin float floorIndex;\nuniform mat4 modelMatrix;\nuniform float uFocusFloor; // -1 = show every floor\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform mat3 normalMatrix;\nout vec3 vColor;\nout vec3 vNormalWorld;\nout vec3 vNormalView;\nout vec3 vPosView;\nout float vDim;\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 vec4 mv = modelViewMatrix * vec4(position, 1.0);\n vPosView = mv.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 float vDim;\nuniform vec3 uKeyDir; // WORLD space, unit, points at the light\nout vec4 fragColor;\nvoid main() {\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 // 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 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\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;\nvoid main() {\n vec4 mv = modelViewMatrix * vec4(iOffset, 1.0);\n float depth = max(-mv.z, 0.001);\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 // 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 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;\nuniform float uSeatFade; // fade toward tier colour with distance (LOD)\nuniform vec3 uFadeColor;\nout vec4 fragColor;\nvoid main() {\n float d = length(vUv);\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 // 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 fragColor = vec4(c, alpha);\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;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uSeatRadius;\nuniform float uSeatScale;\nuniform float uMinPixels;\nuniform float uPixelToWorld;\nout vec2 vUv;\nflat out vec3 vPick;\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 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 gl_Position = projectionMatrix * mv;\n}`;\n\nconst SEAT_PICK_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nflat in vec3 vPick;\nout vec4 fragColor;\nvoid main() {\n if (length(vUv) > 1.0) discard; // round hit-mask matches the dot\n fragColor = vec4(vPick, 1.0);\n}`;\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: 2.5 },\n uPixelToWorld: { value: 0.002 },\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 },\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: 2.5 },\n uPixelToWorld: { value: 0.002 },\n uSeatFade: { value: 0 },\n uFocusFloor: { value: -1 },\n uFadeColor: { value: new Float32Array([0.32, 0.37, 0.43]) },\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 { 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 depthProg: Program;\n private seatScene = 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, 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.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 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 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.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, 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 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(waypoints: Vec3Arr[], startQuat: Quat, endQuat: Quat, duration = FLIGHT_DURATION_MS): 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.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);\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","/**\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\nexport interface SeatView {\n url: string;\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}\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;\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 onClose?: () => void;\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 root = document.createElement('div');\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-label', opts.seatLabel ? `View from ${opts.seatLabel}` : 'View from seat');\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(\"${view.url}\")`, 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: '34px', height: '34px', 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 const hint = document.createElement('div');\n hint.textContent = 'Drag to look around · 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\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 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 bgH = windowedBgHeight(vh);\n bgW = bgH * (natW / natH);\n pano.style.backgroundSize = `${bgW}px ${bgH}px`;\n if (!posInitialised) { posX = bearingToOffsetPx(bearing, vw, bgW); posInitialised = true; }\n pitchPx = clampPitchPx(pitchPx, 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 const img = new Image();\n img.onload = layout;\n img.src = view.url;\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 const onDown = (e: PointerEvent): void => {\n dragging = true; lastX = e.clientX; lastY = e.clientY; pano.style.cursor = 'grabbing';\n try { pano.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onMove = (e: PointerEvent): void => {\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 dragging = false; pano.style.cursor = 'grab';\n try { pano.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n pano.addEventListener('pointerdown', onDown);\n pano.addEventListener('pointermove', onMove);\n pano.addEventListener('pointerup', onUp);\n pano.addEventListener('pointercancel', onUp);\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 window.removeEventListener('keydown', onKey);\n };\n const teardown = (): void => {\n if (fadeTimer) { window.clearTimeout(fadeTimer); fadeTimer = 0; }\n removeListeners();\n if (root.parentNode) root.parentNode.removeChild(root);\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 === 'Escape') { e.stopPropagation(); close(); }\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 * 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\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(seats: number, hasHeights: boolean): void {\n this.emit('3d_opened', { seats, hasHeights });\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"],"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","probesOf","polygonClipping","earcut","polygonClipping","import_ogl","import_ogl","sub","import_ogl","import_ogl","now","DEG"]}
|