@seatlayer/core 0.46.0 → 0.47.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.
Files changed (40) hide show
  1. package/dist/chunk-AZODENEL.js +97 -0
  2. package/dist/chunk-AZODENEL.js.map +1 -0
  3. package/dist/chunk-FVCOOLJO.js +335 -0
  4. package/dist/chunk-FVCOOLJO.js.map +1 -0
  5. package/dist/{chunk-5MRJCIZP.js → chunk-TCTAS4Z2.js} +88 -14
  6. package/dist/chunk-TCTAS4Z2.js.map +1 -0
  7. package/dist/{de-UHAOVDS3.js → de-ST4H423D.js} +6 -3
  8. package/dist/{de-UHAOVDS3.js.map → de-ST4H423D.js.map} +1 -1
  9. package/dist/{es-6TJS2L7S.js → es-WEC5NHRT.js} +6 -3
  10. package/dist/{es-6TJS2L7S.js.map → es-WEC5NHRT.js.map} +1 -1
  11. package/dist/{fr-MMSY7FPE.js → fr-SGBFNAQV.js} +6 -3
  12. package/dist/{fr-MMSY7FPE.js.map → fr-SGBFNAQV.js.map} +1 -1
  13. package/dist/index.cjs +251 -39
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +119 -4
  16. package/dist/index.d.ts +119 -4
  17. package/dist/index.js +152 -24
  18. package/dist/index.js.map +1 -1
  19. package/dist/{types-DBnRO2hX.d.cts → types-CE63BK0j.d.cts} +88 -10
  20. package/dist/{types-DBnRO2hX.d.ts → types-CE63BK0j.d.ts} +88 -10
  21. package/dist/view/panoramaDelivery.cjs +126 -0
  22. package/dist/view/panoramaDelivery.cjs.map +1 -0
  23. package/dist/view/panoramaDelivery.d.cts +34 -0
  24. package/dist/view/panoramaDelivery.d.ts +34 -0
  25. package/dist/view/panoramaDelivery.js +17 -0
  26. package/dist/view/panoramaDelivery.js.map +1 -0
  27. package/dist/view3d/crossfade/panorama.cjs +453 -0
  28. package/dist/view3d/crossfade/panorama.cjs.map +1 -0
  29. package/dist/view3d/crossfade/panorama.d.cts +110 -0
  30. package/dist/view3d/crossfade/panorama.d.ts +110 -0
  31. package/dist/view3d/crossfade/panorama.js +30 -0
  32. package/dist/view3d/crossfade/panorama.js.map +1 -0
  33. package/dist/view3d/index.cjs +1161 -128
  34. package/dist/view3d/index.cjs.map +1 -1
  35. package/dist/view3d/index.d.cts +60 -35
  36. package/dist/view3d/index.d.ts +60 -35
  37. package/dist/view3d/index.js +945 -300
  38. package/dist/view3d/index.js.map +1 -1
  39. package/package.json +28 -1
  40. package/dist/chunk-5MRJCIZP.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/view3d/scene/nearField.ts","../../src/view3d/scene/geometry.ts","../../src/view3d/scene/seatChair.ts","../../src/view3d/scene/seatInstances.ts","../../src/view3d/theme.ts","../../src/view3d/labels.ts","../../src/view3d/scene/sceneModel.ts","../../src/core/rake.ts","../../src/view3d/scene/deckBands.ts","../../src/view3d/scene/surface.ts","../../src/view3d/labelOverlay.ts","../../src/view3d/scene/build.ts","../../src/view3d/scene/materials.ts","../../src/view3d/pick/pickPipeline.ts","../../src/view3d/pick/encode.ts","../../src/view3d/pick/selection.ts","../../src/view3d/camera/cinematic.ts","../../src/view3d/camera/cinematicMath.ts","../../src/view3d/crossfade/panorama.ts","../../src/view3d/scene/panoSphere.ts","../../src/view3d/crossfade/panoramaSphere.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, CHAIR_GATHER_M, CHAIR_MAX_INSTANCES, CHAIR_REBUILD_M } from './lod';\nimport { NearFieldIndex } from './scene/nearField';\nimport { SEAT_DOT_RADIUS_M } from './scene/seatInstances';\nimport { buildSceneModel, type SceneModel, type SceneZone, type SceneSection, type SceneFloor } 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 { mountPanoramaSphere } from './crossfade/panoramaSphere';\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 seated sections (id, name, seat count) in authored order. */\n sections(): SceneSection[];\n /**\n * Frame a section — the middle rung of the venue ladder, and the one every\n * chart has. Zones are optional and a chart may author none or one; sections\n * are what a buyer picks between when \"which part of the venue\" has already\n * been answered. Returns false for an unknown or empty section.\n */\n focusSection(sectionId: string): boolean;\n /** 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 gpu.chairProgram.uniforms.uFocusFloor.value = focusedFloor;\n // A rebuilt scene has an empty chair set; force the next frame to re-gather\n // instead of trusting the last camera position.\n lastGatherX = Infinity;\n };\n\n // --- near field ------------------------------------------------------------\n // The spatial index is constructed here but its grid is built lazily, on the\n // first gather — a session that never leaves the overview never pays for it.\n const nearIndex = new NearFieldIndex(model.seats.iPosition, model.seats.count);\n const nearBuf = new Int32Array(CHAIR_MAX_INSTANCES);\n let lastGatherX = Infinity;\n let lastGatherZ = Infinity;\n\n /**\n * Re-gather the near set when the camera has moved far enough to invalidate\n * it — not every frame.\n *\n * The cheap early-out is the venue bound: if the camera is further from the\n * whole seat cloud than the gather radius, nothing can qualify and the grid is\n * never even touched. That is the state the overview, the intro ease and every\n * frame of a wide orbit are in, so the common case costs one distance test.\n */\n const updateNearField = (): void => {\n if (!gpu) return;\n const cam = orbit.camera.position;\n const moved = Math.hypot(cam.x - lastGatherX, cam.z - lastGatherZ);\n if (moved < CHAIR_REBUILD_M) return;\n lastGatherX = cam.x;\n lastGatherZ = cam.z;\n const outside = Math.hypot(cam.x - model.bounds.center[0], cam.z - model.bounds.center[2])\n - model.bounds.radius;\n if (outside > CHAIR_GATHER_M) {\n if (gpu.nearSeatCount()) gpu.setNearSeats(nearBuf, 0);\n return;\n }\n const n = nearIndex.gather(cam.x, cam.z, CHAIR_GATHER_M, nearBuf);\n if (n === 0 && gpu.nearSeatCount() === 0) return;\n gpu.setNearSeats(nearBuf, n);\n };\n\n const glctx = new GLContext(container, {\n onContextLost: () => {\n 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 // Relax the dot's pixel floor as the venue recedes — see SeatLod.minPixels\n // for why holding 2.5 px all the way out is what welds distant rows into one\n // mass. The pick pass syncs this same value, so the hit mask never drifts\n // off the drawn dot.\n u.uMinPixels.value = lod.minPixels;\n u.uPixelToWorld.value = (2 * Math.tan((orbit.camera.fov * DEG) / 2)) / Math.max(1, glctx.pixelHeight);\n updateNearField();\n\n glctx.renderer.render({ scene: gpu.background, clear: true });\n glctx.renderer.render({ scene: gpu.main, camera: orbit.camera, clear: false });\n // After the render, so the camera's matrices are the ones just drawn with —\n // projecting from stale matrices makes labels lag the venue by a frame.\n labelOverlay.update(\n orbit.camera.projectionViewMatrix as unknown as ArrayLike<number>,\n glctx.canvas.clientWidth || 1,\n glctx.canvas.clientHeight || 1,\n orbit.currentDistance,\n model.bounds.radius,\n // The dense label rungs rank by real distance from the eye, not by the\n // orbit radius — at the arrival pose those are wildly different numbers.\n [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z],\n );\n return moving;\n });\n\n const ro = typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => handle.resize())\n : null;\n ro?.observe(container);\n\n const setSelection = (ids: string[]): void => {\n const baseStateIndex = (id: string): number | undefined => {\n const idx = model.seats.idToIndex.get(id);\n return idx === undefined ? undefined : model.seats.iState[idx];\n };\n const { updates, next } = diffSelection(selection, ids, baseStateIndex);\n selection = next;\n 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 // The panorama owns the screen whenever it is up. Guarding on `frozen`\n // alone was equivalent only while every panorama froze the loop; the\n // in-scene sphere deliberately does not, so ask about the panorama itself.\n if (disposed || frozen || panorama) return;\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 const onClose = (): void => {\n panorama = null;\n labelOverlay.setVisible(true);\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 // Prefer the in-scene sphere: it keeps the loop running, so the arrival is a\n // cross-fade between two views of the same place from the same camera\n // rather than a bitmap dropped over a frozen frame. It needs the image to\n // decode and a GL context that still exists, so it can decline — and the\n // DOM viewer stays as the fallback because a 360 that fails to mount must\n // never leave the buyer worse off than no 360 at all.\n // A GENERATED panorama is a 2048×1024 picture of the very scene already on\n // screen. Through a 106° window on a 3072-device-pixel viewport that is a\n // 5× magnification of ~600 source pixels, and it reads soft next to the\n // arrival frame the buyer was just looking at. So it is not drawn: the\n // camera looks around the real geometry instead, which is sharp at any\n // field of view and gets better every time the scene does. Real photographs\n // still go on the sphere, where their 5760–8192 px capture has detail worth\n // showing.\n const sceneMode = view.generated === true;\n const spherical = gpu && !contextLost\n ? await mountPanoramaSphere(container, sceneMode ? null : view, {\n gl: glctx.gl,\n scene: gpu.main,\n camera: orbit.camera,\n requestRender: () => loop.requestRender(),\n focalWorld: model.focalWorld,\n }, { fadeMs, seatLabel: seatId, onClose })\n : null;\n // Superseded while the image was decoding — the newer flight owns the state.\n if (disposed || gen !== flightGen) { spherical?.dispose(); return; }\n if (spherical) {\n // The venue keeps rendering behind the sphere, so the freeze that the DOM\n // path depends on must be lifted or the cross-fade has nothing to fade\n // from. The sphere itself pins the camera.\n //\n // Sync orbit to where the flight actually parked the camera FIRST. Its\n // damped pose is still whatever it was before the flight, so the next\n // `orbit.update()` would read that as movement, call `applyPosition()`\n // and yank the camera off the seat mid-fade. Syncing equalises pose and\n // target so `update()` reports \"not moving\" and leaves the camera alone —\n // which is what makes it safe to run the loop at all here.\n orbit.syncFromCamera();\n // Labels are DOM and the sphere is GL, so every row and seat label would\n // otherwise float on top of the panorama.\n labelOverlay.setVisible(false);\n frozen = false;\n loop.requestRender();\n panorama = spherical;\n } else {\n panorama = mountPanorama(container, view, { fadeMs, seatLabel: seatId, onClose });\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 gpu.chairProgram.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 sections(): SceneSection[] {\n return model.sections;\n },\n focusSection(sectionId: string): boolean {\n const sec = model.sections.find((s) => s.id === sectionId);\n if (!sec || sec.seatCount === 0) return false;\n cinematic.cancel();\n // Approached from the side it faces, like a zone — a section framed from\n // behind shows the buyer the backs of the seats they are considering.\n const dx = sec.focalWorld[0] - sec.center[0];\n const dz = sec.focalWorld[2] - sec.center[2];\n const azimuth = Math.hypot(dx, dz) > sec.radius * 0.12 ? Math.atan2(dx, dz) : undefined;\n // Tighter padding than a zone's: a section IS the thing being looked at,\n // so it should fill more of the frame, but still with enough around it to\n // place it in the venue.\n orbit.frame({ center: sec.center, radius: sec.radius * 1.45 }, 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/**\n * A category colour as UPHOLSTERY — what that seat looks like from inside the\n * room rather than from above it.\n *\n * The same colour has two jobs at two ranges. On the map it is a price tier and\n * must be legible at a glance across a whole venue, so it is saturated. At eye\n * level it is a fabric-covered chair a metre away, and the saturated version\n * reads as moulded plastic: an amphitheatre whose tiers are pink, gold and blue\n * arrives at the seat looking like a toy.\n *\n * So the near-field chair wears a muted version — desaturated toward its own\n * luminance and darkened. Hue survives, which is the point: a buyer sitting in\n * the Terrace can still see where the Orchestra ends, they just are not sitting\n * in a bag of sweets. The DOT keeps the full colour, so nothing changes about\n * reading the map.\n */\nexport function upholsteryTone(rgb: RGB): RGB {\n const luma = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];\n const SAT = 0.42; // how much original hue survives\n const VALUE = 0.62; // and how much of its brightness\n return [\n (luma + (rgb[0] - luma) * SAT) * VALUE,\n (luma + (rgb[1] - luma) * SAT) * VALUE,\n (luma + (rgb[2] - luma) * SAT) * VALUE,\n ];\n}\n\n/** Flat LUT (5 × vec3) for the seat fragment shader uniform. */\nexport function seatStateColorLUT(): number[] {\n const out: number[] = [];\n for (const s of SEAT_STATES) out.push(...SEAT_STATE_COLORS[s]);\n return out;\n}\n\n/** Colour for a state index (from `iState`) — used to fill the per-instance\n * `iColor` attribute CPU-side, avoiding a dynamically-indexed array uniform. */\nexport function seatStateColorByIndex(index: number): RGB {\n const state = SEAT_STATES[index] ?? 'available';\n return SEAT_STATE_COLORS[state];\n}\n\n/** Structure palette — cool desaturated greys + one warm stage accent. */\nexport const STRUCTURE = {\n ground: [0.07, 0.085, 0.11] as RGB,\n tierTop: [0.24, 0.28, 0.34] as RGB,\n tierWall: [0.17, 0.20, 0.25] as RGB,\n /**\n * The lit performance surface.\n *\n * This was [0.42, 0.36, 0.26] and commented \"slightly emissive read\", which it\n * was not — under the scene's rig it resolved to a mid-brown slab, and from a\n * seat the stage was the DIMMEST large surface in a dark hall. That is exactly\n * backwards: the stage is the one place in a venue with light pointed at it,\n * and it is what a buyer's eye should land on when they arrive at their seat.\n *\n * Bright and warm enough to hold that role against the surrounding structure,\n * which sits around 0.24. An authored `fill` still tints it (see `buildShape`),\n * so an organizer's own stage colour survives — it is just no longer lit like\n * a basement.\n */\n stageTop: [0.86, 0.64, 0.36] as RGB,\n stageWall: [0.34, 0.26, 0.18] as RGB,\n decorTop: [0.22, 0.25, 0.29] as RGB,\n decorWall: [0.15, 0.17, 0.20] as RGB,\n gaTop: [0.24, 0.28, 0.33] as RGB,\n gaWall: [0.16, 0.19, 0.23] as RGB,\n /** Exhibition / trade-show booth stand. */\n boothTop: [0.30, 0.33, 0.38] as RGB,\n boothWall: [0.20, 0.23, 0.27] as RGB,\n /** Banquet table top — warmer, so a laid table reads apart from structure. */\n tableTop: [0.38, 0.34, 0.29] as RGB,\n tableWall: [0.24, 0.21, 0.18] as RGB,\n} as const;\n\n/** Background vertical gradient (matches the app's dark UI). */\nexport const BACKGROUND = {\n top: [0.05, 0.06, 0.08] as RGB,\n bottom: [0.10, 0.12, 0.15] as RGB,\n};\n\n/** Parse `#rrggbb` (or `#rgb`) to linear-ish 0..1 RGB; null on anything else. */\nexport function hexToRgb(hex: string | undefined): RGB | null {\n if (!hex) return null;\n let h = hex.trim();\n if (h[0] === '#') h = h.slice(1);\n if (h.length === 3) h = h.split('').map((c) => c + c).join('');\n if (h.length !== 6 || /[^0-9a-fA-F]/.test(h)) return null;\n const n = parseInt(h, 16);\n return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];\n}\n\n/** Mix two colours (a*(1-t) + b*t). */\nexport function mix(a: RGB, b: RGB, t: number): RGB {\n return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];\n}\n\n/** Desaturate toward its own luma by `amount` (0 = unchanged, 1 = grey). */\nexport function desaturate(c: RGB, amount: number): RGB {\n const l = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];\n return mix(c, [l, l, l], amount);\n}\n\n/** Scale a colour by a scalar (baked vertex AO), clamped to [0,1]. */\nexport function scaleRgb(c: RGB, k: number): RGB {\n return [Math.min(1, c[0] * k), Math.min(1, c[1] * k), Math.min(1, c[2] * k)];\n}\n","/**\n * Orbit + dolly + pan camera, damped. Drag = azimuth/polar; wheel/pinch = dolly;\n * right-drag, middle-drag or shift-drag = pan, as does a two-finger drag on\n * touch. Polar clamped [15°,80°]; distance clamped to bounds-derived limits; pan\n * clamped to a bounds-derived box; initial framing = a 3/4 view fitted to bounds.\n *\n * Pan was deliberately omitted in v1 (\"always centred on the venue focal point\"),\n * on the reasoning that a venue is a single object you orbit. That is true only\n * while the whole venue is in frame. Dolly in far enough to read seat labels — the\n * thing the low `minDist` exists to allow — and the pivot stays pinned to the\n * venue centre, so the upper tiers sit off-screen with no way to bring them back\n * except zooming out again. Reported from the marketing hero: \"if I want to move\n * this 3D down to see the upper part it is not possible.\"\n */\n\nimport { Camera, Vec3 } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\n\nconst DEG = Math.PI / 180;\nconst POLAR_MIN = 15 * DEG;\nconst POLAR_MAX = 80 * DEG;\nconst DAMP = 0.12;\nconst FOV = 35;\n/** Fit multiplier past a tight bounds-sphere fit. The 3/4 tilt makes the near\n * ground edge overhang below the fitted sphere, so a wide-shallow layout needs\n * more than a nominal 10% or its front row clips — this clears it while keeping\n * the venue centred with a comfortable margin. */\nconst FRAME_MARGIN = 1.25;\n\nexport interface OrbitBounds {\n center: [number, number, number];\n radius: number;\n}\n\nexport 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 panning = false;\n private lastX = 0;\n private lastY = 0;\n private activePointers = new Map<number, { x: number; y: number }>();\n private pinchDist = 0;\n private pinchCx = 0;\n private pinchCy = 0;\n /** Damped pan pivot. `target` chases this the way azimuth chases `azT`. */\n private targetT = new Vec3();\n /** Venue centre + radius, so pan can be clamped to somewhere still useful. */\n private panAnchor = new Vec3();\n private panLimit = 0;\n\n private onPointerDown: (e: PointerEvent) => void;\n private onPointerMove: (e: PointerEvent) => void;\n private onPointerUp: (e: PointerEvent) => void;\n private onWheel: (e: WheelEvent) => void;\n private onContextMenu: (e: Event) => void;\n\n constructor(gl: OGLRenderingContext, canvas: HTMLElement, requestRender: () => void, onGesture?: () => void) {\n this.camera = new Camera(gl, { fov: FOV, near: 0.1, far: 5000, aspect: 1 });\n this.canvas = canvas;\n this.requestRender = requestRender;\n this.onGesture = onGesture;\n\n this.onPointerDown = (e) => {\n // Guard: a synthetic/stale pointer id has no active pointer to capture.\n try { this.canvas.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (this.activePointers.size === 1) {\n // Secondary/middle button or shift = pan, matching every 3D viewer people\n // already know. Left-drag stays orbit, which is the common case.\n this.panning = e.button === 2 || e.button === 1 || e.shiftKey;\n this.dragging = !this.panning;\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n } else if (this.activePointers.size === 2) {\n this.dragging = false;\n this.panning = false;\n this.pinchDist = this.currentPinchDistance();\n const c = this.pinchCentroid();\n this.pinchCx = c.x;\n this.pinchCy = c.y;\n }\n };\n this.onPointerMove = (e) => {\n if (!this.activePointers.has(e.pointerId)) return;\n this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (this.activePointers.size >= 2) {\n const d = this.currentPinchDistance();\n // Fingers apart (d grows) → zoom in (distance shrinks).\n if (this.pinchDist > 0) { this.dollyBy(Math.exp((this.pinchDist - d) * 0.005)); this.fireGesture(); }\n this.pinchDist = d;\n // Two fingers moving TOGETHER pan; the pinch term above already took the\n // spreading component, so the centroid carries the translation.\n const c = this.pinchCentroid();\n this.panBy(c.x - this.pinchCx, c.y - this.pinchCy);\n this.pinchCx = c.x;\n this.pinchCy = c.y;\n return;\n }\n const dx = e.clientX - this.lastX;\n const dy = e.clientY - this.lastY;\n if (this.panning) {\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n if (dx !== 0 || dy !== 0) { this.panBy(dx, dy); this.fireGesture(); }\n return;\n }\n if (!this.dragging) return;\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n if (dx !== 0 || dy !== 0) this.fireGesture();\n this.azT -= dx * 0.006;\n this.polT = Math.max(POLAR_MIN, Math.min(POLAR_MAX, this.polT - dy * 0.006));\n this.requestRender();\n };\n this.onPointerUp = (e) => {\n this.activePointers.delete(e.pointerId);\n try { this.canvas.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n if (this.activePointers.size < 2) this.pinchDist = 0;\n if (this.activePointers.size === 0) { this.dragging = false; this.panning = false; }\n };\n // Right-drag pans, so the context menu must not interrupt the gesture.\n this.onContextMenu = (e) => { e.preventDefault(); };\n this.onWheel = (e) => {\n e.preventDefault();\n // 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 canvas.addEventListener('contextmenu', this.onContextMenu);\n }\n\n private pinchCentroid(): { x: number; y: number } {\n const pts = [...this.activePointers.values()];\n if (!pts.length) return { x: 0, y: 0 };\n let x = 0;\n let y = 0;\n for (const p of pts) { x += p.x; y += p.y; }\n return { x: x / pts.length, y: y / pts.length };\n }\n\n /**\n * Slide the orbit pivot across the camera's own screen plane.\n *\n * Scaled by distance and FOV so a pixel of drag moves the same amount of VENUE\n * under the cursor whatever the zoom: at the overview a drag sweeps the whole\n * bowl, and pushed in among the seats it nudges. A fixed world-units-per-pixel\n * would be unusable at one end or the other.\n */\n private panBy(dxPx: number, dyPx: number): void {\n const h = (this.canvas as HTMLElement).clientHeight || 1;\n // World units spanned by one pixel at the pivot's depth.\n const perPx = (2 * this.distance * Math.tan((this.fovY * DEG) / 2)) / h;\n // Camera basis on the ground-plane projection: right is perpendicular to the\n // view azimuth; \"up\" on screen maps to the horizontal forward direction,\n // damped by the tilt so a top-down view pans in the plane you can see.\n const sinA = Math.sin(this.azimuth);\n const cosA = Math.cos(this.azimuth);\n const rightX = cosA;\n const rightZ = -sinA;\n const cp = Math.cos(this.polar);\n const sp = Math.sin(this.polar);\n const fwdX = -sinA * cp;\n const fwdZ = -cosA * cp;\n this.targetT.x += (-dxPx * rightX + dyPx * fwdX) * perPx;\n this.targetT.z += (-dxPx * rightZ + dyPx * fwdZ) * perPx;\n this.targetT.y += dyPx * sp * perPx;\n this.clampPan();\n this.requestRender();\n }\n\n /** Keep the pivot within a bounds-derived box so a stray drag cannot lose the\n * venue entirely — the Overview chip should never be the only way back. */\n private clampPan(): void {\n if (this.panLimit <= 0) return;\n const lim = this.panLimit;\n const cx = this.panAnchor.x;\n const cy = this.panAnchor.y;\n const cz = this.panAnchor.z;\n this.targetT.x = Math.max(cx - lim, Math.min(cx + lim, this.targetT.x));\n this.targetT.z = Math.max(cz - lim, Math.min(cz + lim, this.targetT.z));\n this.targetT.y = Math.max(cy - lim * 0.5, Math.min(cy + lim, this.targetT.y));\n }\n\n /** One-shot: notify the first real user gesture (drives 3d_orbit_engaged). */\n private fireGesture(): void {\n if (this.gestureFired) return;\n this.gestureFired = true;\n this.onGesture?.();\n }\n\n private currentPinchDistance(): number {\n const pts = [...this.activePointers.values()];\n if (pts.length < 2) return 0;\n return Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);\n }\n\n /** Multiply target distance by `factor` (proportional zoom — same feel near\n * and far), clamped so you can swoop right down among the seats. */\n private dollyBy(factor: number): void {\n this.distT = Math.max(this.minDist, Math.min(this.maxDist, this.distT * factor));\n this.requestRender();\n }\n\n /**\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 this.targetT.copy(this.target);\n this.panAnchor.copy(this.target);\n this.panLimit = Math.max(1, bounds.radius) * 1.5;\n const r = Math.max(1, bounds.radius);\n // Aspect-aware fit: the bounds sphere must clear BOTH the vertical and the\n // (aspect-narrowed) horizontal FOV, so a wide designer canvas frames the\n // chart centred with margin instead of parking it low-left. `setAspect` must\n // run before `frame` for the horizontal term to be correct.\n const halfV = (this.fovY * DEG) / 2;\n 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.targetT.copy(this.target);\n this.panAnchor.copy(this.target);\n this.panLimit = Math.max(1, bounds.radius) * 1.5;\n this.syncFromCamera(); // re-derive pose around the new pivot — no snap\n this.camera.perspective({ fov: this.fovY, aspect: this.camera.aspect });\n const r = Math.max(1, bounds.radius);\n const halfV = (this.fovY * DEG) / 2;\n const aspect = 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 tx = this.targetT.x - this.target.x;\n const ty = this.targetT.y - this.target.y;\n const tz = this.targetT.z - this.target.z;\n // Pan is damped on the same curve as the rest of the pose, and its epsilon is\n // scaled by distance: 1e-4 world units is imperceptible at the overview but\n // would keep a pushed-in camera redrawing forever.\n const panEps = Math.max(1e-4, this.distance * 1e-4);\n const moving = Math.abs(da) > 1e-4 || Math.abs(dp) > 1e-4 || Math.abs(dd) > 1e-4\n || Math.abs(tx) > panEps || Math.abs(ty) > panEps || Math.abs(tz) > panEps;\n this.azimuth += da * DAMP;\n this.polar += dp * DAMP;\n this.distance += dd * DAMP;\n this.target.x += tx * DAMP;\n this.target.y += ty * DAMP;\n this.target.z += tz * DAMP;\n if (moving) this.applyPosition();\n return moving;\n }\n\n /** Distance from camera to target (for LOD). */\n get currentDistance(): number {\n return this.distance;\n }\n\n /**\n * Re-derive the orbit's spherical state from the camera's CURRENT pose (after a\n * cinematic flight leaves it somewhere arbitrary), so a subsequent drag damps\n * from where it actually is with no snap. Does not move the camera.\n */\n syncFromCamera(): void {\n const dx = this.camera.position.x - this.target.x;\n const dy = this.camera.position.y - this.target.y;\n const dz = this.camera.position.z - this.target.z;\n const dist = Math.hypot(dx, dy, dz) || 1;\n const polar = Math.max(POLAR_MIN, Math.min(POLAR_MAX, Math.acos(Math.max(-1, Math.min(1, dy / dist)))));\n // Distance is NOT clamped here: a flight can park closer than minDist, and\n // clamping would jump the camera radially on the very first drag. The clamp\n // applies lazily from the next user-driven dolly (see dollyBy).\n this.distance = this.distT = dist;\n this.polar = this.polT = polar;\n this.azimuth = this.azT = Math.atan2(dx, dz);\n }\n\n /** Point the orbit pivot at a new world target without moving the camera. */\n setTarget(target: [number, number, number]): void {\n this.target.set(target[0], target[1], target[2]);\n this.targetT.copy(this.target);\n }\n\n /** Restore the base FOV (a flight ends pushed-in) and re-sync orbit state. A\n * flight ends looking at `target` (the venue focal), so re-pivot there first —\n * otherwise the first drag would `lookAt(bounds.center)` and pop the aim. */\n resumeAfterFlight(target?: [number, number, number]): void {\n this.camera.perspective({ fov: this.fovY, aspect: this.camera.aspect });\n if (target) this.target.set(target[0], target[1], target[2]);\n this.targetT.copy(this.target);\n this.syncFromCamera();\n }\n\n private applyPosition(): void {\n const sp = Math.sin(this.polar);\n const x = this.target.x + this.distance * sp * Math.sin(this.azimuth);\n const y = this.target.y + this.distance * Math.cos(this.polar);\n const z = this.target.z + this.distance * sp * Math.cos(this.azimuth);\n this.camera.position.set(x, y, z);\n this.camera.lookAt(this.target);\n }\n\n dispose(): void {\n this.canvas.removeEventListener('pointerdown', this.onPointerDown);\n this.canvas.removeEventListener('pointermove', this.onPointerMove);\n this.canvas.removeEventListener('pointerup', this.onPointerUp);\n this.canvas.removeEventListener('pointercancel', this.onPointerUp);\n this.canvas.removeEventListener('wheel', this.onWheel);\n this.canvas.removeEventListener('contextmenu', this.onContextMenu);\n this.activePointers.clear();\n }\n}\n","/**\n * Dirty-flag render loop. A frame is drawn only while the camera is moving /\n * damping or an availability update arrived; when idle, no rAF is scheduled at\n * all (zero CPU/GPU when parked). Tracks an FPS EMA over frames actually\n * rendered — it reads as \"idle\" when nothing is scheduled.\n */\n\nexport interface RenderLoopStats {\n fps: number;\n rendered: number;\n idle: boolean;\n}\n\nexport class RenderLoop {\n private frame: (dt: number) => boolean;\n private rafId = 0;\n private running = false;\n private lastTime = 0;\n private fpsEma = 0;\n private rendered = 0;\n\n /** `frame(dt)` renders one frame and returns true if another is needed. */\n constructor(frame: (dt: number) => boolean) {\n this.frame = frame;\n }\n\n requestRender(): void {\n if (this.running) return;\n this.running = true;\n this.lastTime = 0;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n private tick = (now: number): void => {\n const dt = this.lastTime ? (now - this.lastTime) / 1000 : 1 / 60;\n this.lastTime = now;\n if (dt > 0) {\n const instFps = 1 / dt;\n this.fpsEma = this.fpsEma ? this.fpsEma * 0.9 + instFps * 0.1 : instFps;\n }\n this.rendered++;\n const again = this.frame(dt);\n if (again) {\n this.rafId = requestAnimationFrame(this.tick);\n } else {\n this.running = false;\n this.rafId = 0;\n }\n };\n\n stats(): RenderLoopStats {\n return { fps: this.running ? Math.round(this.fpsEma) : 0, rendered: this.rendered, idle: !this.running };\n }\n\n stop(): void {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.running = false;\n }\n}\n","/**\n * The seat level-of-detail ladder.\n *\n * Three rungs, all driven by how far a seat is from the CAMERA rather than by a\n * global switch, so one venue can be showing all three at once — chairs in the\n * row you are sitting in, dots across the bowl, a dissolving tint on the far\n * side:\n *\n * near (≤ CHAIR_FULL_M) extruded chair silhouettes (scene/seatChair.ts)\n * band (…CHAIR_NONE_M) chair scales in / dot fades out — no pop\n * far the instanced billboard dot, as before\n *\n * Everything below is pure so the thresholds can be reasoned about (and tested)\n * without a GL context. The shaders re-derive the same weight per instance from\n * view depth; these constants are the single source both sides read.\n */\n\nexport interface SeatLod {\n /** Multiplier on the seat world radius (1 = full). */\n scale: number;\n /** Fade toward the tier/fade colour (0 = pure state colour). */\n fade: number;\n /**\n * Minimum screen size a dot is allowed to demand, in pixels.\n *\n * The shader holds this floor by GROWING a dot's world radius with depth, and\n * that growth is what smears neighbouring rows into one mass at range: measured\n * mean seat spacing is 0.53–0.58 m against a 0.44 m dot diameter, so a dot only\n * has to grow ~20 % before it touches its neighbour. `iMaxRadius` caps each dot\n * at its own pitch, but a capped dot then sits at its ceiling across the whole\n * far half of the venue, which is exactly the solid-mass look.\n *\n * So the DEMAND is tapered as well as the growth capped. Past the far knee the\n * requested floor relaxes toward SEAT_MIN_PIXELS_FAR, the dots stop pressing\n * against their ceilings, and the gap between rows survives to the back wall.\n * Legibility past that point is the tier tint's job, not the dot's — which is\n * what `fade` above is for.\n */\n minPixels: number;\n}\n\n/** Pixel floor a dot demands in the near/mid field — unchanged from v1. */\nexport const SEAT_MIN_PIXELS_NEAR = 2.5;\n/**\n * Pixel floor a dot demands at full distance.\n *\n * Not zero: a dot that demands nothing shrinks below a pixel, aliases, and\n * shimmers as the camera moves. 1.15 keeps a sub-pixel dot anchored while\n * leaving it far enough under its pitch ceiling that its neighbour stays\n * separate. The fragment stage already dissolves anything that cannot afford\n * even this (`vBudget`).\n */\nexport const SEAT_MIN_PIXELS_FAR = 1.15;\n\n/**\n * View depth, in metres, at or below which a seat draws as a full chair.\n *\n * 12 m is roughly \"the far end of your own block\". At the arrival pose the\n * camera sits at the seat, so the whole neighbourhood a buyer is actually\n * looking at is inside this.\n */\nexport const CHAIR_FULL_M = 12;\n\n/**\n * View depth at and beyond which a seat is a dot again.\n *\n * The 10 m band between this and CHAIR_FULL_M is the transition. It is wide on\n * purpose: the fly-to-seat cinematic crosses it in well under a second, and a\n * narrow band would read as a wave of furniture sweeping over the venue.\n */\nexport const CHAIR_NONE_M = 22;\n\n/**\n * Radius, in metres, of the CPU gather that decides which seats are handed to\n * the chair mesh at all.\n *\n * Strictly greater than CHAIR_NONE_M + CHAIR_REBUILD_M. That inequality is the\n * whole no-pop argument: a seat always enters the instanced set while it is\n * still beyond the fade band (weight 0, drawn as nothing), so by the time it is\n * close enough to be visible it has already been in the set for a while. A seat\n * can never blink into existence mid-band, whatever the camera does between two\n * gathers. `chairGatherCoversFadeBand()` asserts it.\n */\nexport const CHAIR_GATHER_M = 30;\n\n/**\n * How far the camera may travel before the gathered set is rebuilt.\n *\n * The gather is the only per-frame CPU cost the near field adds, so it is not\n * run per frame. 4 m against an 8 m slack (GATHER − NONE) leaves a 2× margin.\n */\nexport const CHAIR_REBUILD_M = 4;\n\n/**\n * Hard ceiling on instances handed to the chair mesh.\n *\n * The cap is what makes the near field's cost bounded rather than proportional\n * to venue size, and seats are gathered nearest-first, so it only ever drops the\n * OUTER edge of the disc.\n *\n * Its value is not a round number picked for comfort — it is sized against\n * measured venue density, because a cap that bites inside the fade band would\n * silently reintroduce popping on exactly the biggest venues. Worst-case seats\n * within a radius, measured by standing on 60 sampled seats of each shipped\n * chart:\n *\n * 21 m 26 m 30 m\n * megaStadium 4,419 6,073 7,469\n * uberArena 2,670 3,690 4,585\n * amphitheatre 2,326 2,873 3,211\n *\n * The binding requirement is that the kept disc still reach CHAIR_NONE_M +\n * CHAIR_REBUILD_M (26 m), which megaStadium meets at 6,073. An earlier 4,096\n * capped megaStadium's disc at roughly 22 m — inside that margin, and the defect\n * would only ever have shown up on the largest chart. 8,192 leaves ~30 % headroom\n * for a denser chart than any shipped today. `chairCapCoversFadeBand()` is the\n * executable form of this.\n */\nexport const CHAIR_MAX_INSTANCES = 8192;\n\n/**\n * The near/far ladder invariant, as a value rather than a comment.\n *\n * Exported so the test suite asserts the actual constants instead of restating\n * them: if someone widens the fade band or slows the rebuild without widening\n * the gather, seats start popping in mid-band and this goes false.\n */\nexport function chairGatherCoversFadeBand(): boolean {\n return CHAIR_GATHER_M > CHAIR_NONE_M + CHAIR_REBUILD_M;\n}\n\n/** The radius the gathered set must still reach after the cap has bitten. */\nexport const CHAIR_REQUIRED_COVER_M = CHAIR_NONE_M + CHAIR_REBUILD_M;\n\n/**\n * Does a venue holding `seatsWithinRequiredCover` seats inside\n * CHAIR_REQUIRED_COVER_M still get every one of them into the capped set?\n *\n * The companion to `chairGatherCoversFadeBand`. That one checks the RADIUS is\n * generous enough; this checks the CAP is, which is the failure mode that only\n * appears on the densest chart and is therefore the one most likely to ship.\n */\nexport function chairCapCoversFadeBand(seatsWithinRequiredCover: number): boolean {\n return seatsWithinRequiredCover <= CHAIR_MAX_INSTANCES;\n}\n\n/** GLSL `smoothstep`, so the JS side and the shaders agree exactly. */\nfunction smoothstep(edge0: number, edge1: number, x: number): number {\n const t = Math.min(1, Math.max(0, (x - edge0) / Math.max(1e-6, edge1 - edge0)));\n return t * t * (3 - 2 * t);\n}\n\n/**\n * How much chair a seat at `depth` metres of view depth shows: 1 = full\n * silhouette, 0 = nothing drawn (the dot carries it).\n *\n * The chair SCALES IN rather than fading in. A cross-fade would need the chairs\n * to be transparent through the whole band, and a transparent chair either\n * writes depth (and punches holes in the chairs behind it) or does not (and\n * shows its own back panel through its own seat pad). Growing an OPAQUE chair\n * out of the seat point has neither problem, and it lands where the dot already\n * is: at weight 0 the chair is a zero-size nub exactly at the seat centre, under\n * a dot at full opacity.\n *\n * Mirrored in CHAIR_VERT / SEAT_VERT — keep the three in step.\n */\nexport function chairWeight(depth: number): number {\n return 1 - smoothstep(CHAIR_FULL_M, CHAIR_NONE_M, depth);\n}\n\n/** The dot's share of a seat: the exact complement of `chairWeight`. */\nexport function dotWeight(depth: number): number {\n return 1 - chairWeight(depth);\n}\n\n/**\n * The chair's linear size at `depth`, as a fraction of full size.\n *\n * NOT the same curve as `chairWeight`, and the difference is what the middle of\n * the band actually looks like. Scaling linearly with the weight put a\n * quarter-size chair inside a full-opacity dot for most of the crossover: the\n * dot is ~0.44 m across and holds its size until it is handed over, so a chair\n * at 25 % read as a speck floating inside a blob rather than as a seat.\n *\n * The square root front-loads the growth — half a weight is 71 % of full size —\n * so by the time the dot has faded to half, the chair underneath it is nearly\n * the same size and the two genuinely overlay each other. It still passes\n * through exactly 0 at CHAIR_NONE_M, so nothing pops into existence.\n */\nexport function chairScale(depth: number): number {\n return Math.sqrt(chairWeight(depth));\n}\n\nexport function computeSeatLod(distance: number, radius: number): SeatLod {\n const near = radius * 1.4;\n const far = radius * 3.2;\n if (distance <= near) return { scale: 1, fade: 0, minPixels: SEAT_MIN_PIXELS_NEAR };\n const t = Math.min(1, (distance - near) / Math.max(1e-3, far - near));\n return {\n scale: 1 - t * 0.4,\n fade: t * 0.55,\n // Tapered on the same ramp as the fade: as the block starts reading by its\n // tier tint, the dots stop fighting each other for pixels.\n minPixels: SEAT_MIN_PIXELS_NEAR + t * (SEAT_MIN_PIXELS_FAR - SEAT_MIN_PIXELS_NEAR),\n };\n}\n","/**\n * \"Which seats are close enough to be worth drawing as chairs?\" — a uniform XZ\n * grid over the seat cloud, queried nearest-cell-first.\n *\n * This is the piece that makes the near field affordable. The chair mesh could\n * simply carry every seat and let the vertex stage collapse the far ones to\n * zero size, but that is 52,000 × 72 vertices of shading per frame to draw a few\n * hundred chairs — the whole 60 fps claim, spent on geometry that is discarded.\n * Instead the CPU hands the mesh a COMPACT set bounded by CHAIR_MAX_INSTANCES,\n * so near-field cost is a function of the camera's neighbourhood and not of\n * venue size.\n *\n * The grid is built lazily on the first gather: a camera that never comes down\n * to the deck never pays for it, and the intro pose is always far out.\n */\n\n/** Target seats per cell. Small enough to prune hard, large enough that the\n * bucket arrays stay short and cache-friendly. */\nconst SEATS_PER_CELL = 4;\n\nexport class NearFieldIndex {\n private readonly iPosition: Float32Array;\n private readonly count: number;\n private minX = 0;\n private minZ = 0;\n private cell = 1;\n private cols = 1;\n private rows = 1;\n /** CSR-style buckets: `cellStart[c]…cellStart[c+1]` indexes into `cellItems`. */\n private cellStart: Int32Array | null = null;\n private cellItems: Int32Array | null = null;\n\n constructor(iPosition: Float32Array, count: number) {\n this.iPosition = iPosition;\n this.count = count;\n }\n\n /** Built on demand; safe to call repeatedly. */\n private ensureGrid(): void {\n if (this.cellStart || this.count === 0) return;\n const p = this.iPosition;\n let minX = Infinity, minZ = Infinity, maxX = -Infinity, maxZ = -Infinity;\n for (let i = 0; i < this.count; i++) {\n const x = p[i * 3], z = p[i * 3 + 2];\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (z < minZ) minZ = z;\n if (z > maxZ) maxZ = z;\n }\n const w = Math.max(maxX - minX, 1e-3);\n const h = Math.max(maxZ - minZ, 1e-3);\n this.cell = Math.max(Math.sqrt((w * h * SEATS_PER_CELL) / this.count), 0.25);\n this.cols = Math.max(1, Math.ceil(w / this.cell) + 1);\n this.rows = Math.max(1, Math.ceil(h / this.cell) + 1);\n this.minX = minX;\n this.minZ = minZ;\n // Counting sort into CSR — no per-cell arrays, one pass to count and one to\n // place. At 52k seats this is two linear scans and two typed allocations.\n const nCells = this.cols * this.rows;\n const start = new Int32Array(nCells + 1);\n const cellOf = (i: number): number => {\n const cx = Math.min(this.cols - 1, Math.max(0, Math.floor((p[i * 3] - minX) / this.cell)));\n const cz = Math.min(this.rows - 1, Math.max(0, Math.floor((p[i * 3 + 2] - minZ) / this.cell)));\n return cz * this.cols + cx;\n };\n for (let i = 0; i < this.count; i++) start[cellOf(i) + 1]++;\n for (let c = 0; c < nCells; c++) start[c + 1] += start[c];\n const items = new Int32Array(this.count);\n const cursor = start.slice(0, nCells);\n for (let i = 0; i < this.count; i++) items[cursor[cellOf(i)]++] = i;\n this.cellStart = start;\n this.cellItems = items;\n }\n\n /**\n * Fill `out` with the indices of seats within `radius` metres of (camX, camZ),\n * nearest cell-ring first, and return how many were written.\n *\n * Ring order is what makes the CHAIR_MAX_INSTANCES cap harmless: when the cap\n * bites it drops the OUTERMOST seats, which are the ones already past the fade\n * band and drawing nothing. A cap that truncated in index order would instead\n * punch holes in the row you are sitting in.\n *\n * Note this is a horizontal (XZ) query and ignores height. A stacked venue's\n * upper tier is therefore gathered along with the stalls beneath it — which is\n * correct, because the fade weight is re-derived from true view depth in the\n * shader anyway. The grid's only job is to bound the candidate set.\n */\n gather(camX: number, camZ: number, radius: number, out: Int32Array): number {\n this.ensureGrid();\n const start = this.cellStart;\n const items = this.cellItems;\n if (!start || !items) return 0;\n const cap = out.length;\n const r2 = radius * radius;\n const cx = Math.floor((camX - this.minX) / this.cell);\n const cz = Math.floor((camZ - this.minZ) / this.cell);\n const maxRing = Math.ceil(radius / this.cell) + 1;\n const p = this.iPosition;\n let n = 0;\n for (let ring = 0; ring <= maxRing && n < cap; ring++) {\n const z0 = cz - ring, z1 = cz + ring;\n const x0 = cx - ring, x1 = cx + ring;\n for (let gz = z0; gz <= z1 && n < cap; gz++) {\n if (gz < 0 || gz >= this.rows) continue;\n // Only the newly-exposed cells of this ring: the full row on the top and\n // bottom edges, just the two end cells in between.\n const edge = (gz === z0 || gz === z1);\n for (let gx = x0; gx <= x1 && n < cap; gx++) {\n if (!edge && gx !== x0 && gx !== x1) { gx = x1 - 1; continue; }\n if (gx < 0 || gx >= this.cols) continue;\n const c = gz * this.cols + gx;\n for (let k = start[c], e = start[c + 1]; k < e && n < cap; k++) {\n const i = items[k];\n const dx = p[i * 3] - camX;\n const dz = p[i * 3 + 2] - camZ;\n if (dx * dx + dz * dz <= r2) out[n++] = i;\n }\n }\n }\n }\n return n;\n }\n}\n","/**\n * 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. */\n/**\n * Growable Float32 buffer.\n *\n * `MeshBuilder` accumulated into plain `number[]` and converted once at the end.\n * On a 99k-seat venue that meant four arrays of ~1.2M boxed numbers each and a\n * final copy of all of them — profiled at 11 % of scene build in `build()`\n * alone, plus a share of the 8 % spent in GC. Writing straight into typed\n * storage that doubles when full removes both the boxing and the final copy.\n */\nclass F32Buffer {\n private buf: Float32Array;\n private len = 0;\n\n constructor(initial = 1 << 14) {\n this.buf = new Float32Array(initial);\n }\n\n push3(a: number, b: number, c: number): void {\n if (this.len + 3 > this.buf.length) this.grow(this.len + 3);\n this.buf[this.len++] = a;\n this.buf[this.len++] = b;\n this.buf[this.len++] = c;\n }\n\n push1(a: number): void {\n if (this.len + 1 > this.buf.length) this.grow(this.len + 1);\n this.buf[this.len++] = a;\n }\n\n private grow(need: number): void {\n let cap = this.buf.length * 2;\n while (cap < need) cap *= 2;\n const next = new Float32Array(cap);\n next.set(this.buf.subarray(0, this.len));\n this.buf = next;\n }\n\n get length(): number { return this.len; }\n\n /** A copy trimmed to the used length. */\n toArray(): Float32Array {\n return this.buf.slice(0, this.len);\n }\n}\n\nexport class MeshBuilder {\n private pos = new F32Buffer();\n private nor = new F32Buffer();\n private col = new F32Buffer();\n private flr = new F32Buffer();\n /** 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.push3(p0[0], p0[1], p0[2]); this.pos.push3(p1[0], p1[1], p1[2]); this.pos.push3(p2[0], p2[1], p2[2]);\n this.nor.push3(n[0], n[1], n[2]); this.nor.push3(n[0], n[1], n[2]); this.nor.push3(n[0], n[1], n[2]);\n this.col.push3(c0[0], c0[1], c0[2]); this.col.push3(c1[0], c1[1], c1[2]); this.col.push3(c2[0], c2[1], c2[2]);\n this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor);\n }\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.push3(p0[0], p0[1], p0[2]); this.pos.push3(p1[0], p1[1], p1[2]); this.pos.push3(p2[0], p2[1], p2[2]);\n this.nor.push3(n0[0], n0[1], n0[2]); this.nor.push3(n1[0], n1[1], n1[2]); this.nor.push3(n2[0], n2[1], n2[2]);\n this.col.push3(c0[0], c0[1], c0[2]); this.col.push3(c1[0], c1[1], c1[2]); this.col.push3(c2[0], c2[1], c2[2]);\n this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor);\n }\n\n get vertexCount(): number {\n return this.pos.length / 3;\n }\n\n build(): MeshData {\n return {\n position: this.pos.toArray(),\n normal: this.nor.toArray(),\n color: this.col.toArray(),\n floor: this.flr.toArray(),\n 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 * The near-field seat: a three-box extruded silhouette (pedestal, pad, back)\n * and the per-seat facing that aims it at the stage.\n *\n * Why this exists at all: every seat in the venue was a camera-facing disc, and\n * a disc has no front. That is fine across a bowl and wrong at the one moment\n * the 3D view is selling — the buyer lands ON their seat and the row they are\n * sitting in is a line of coloured blobs rather than chairs. The renderer's own\n * programme notes call it the biggest visual gap at close range.\n *\n * What it deliberately is NOT: furniture. There are no armrests, no cushion\n * bevel, no legs. At 3–10 m what the eye needs is a pad at seat height and a\n * back rising behind it, facing the right way — the silhouette, not the detail.\n * Everything here is one instanced mesh over a 72-vertex base, because the third\n * draw call is the budget.\n */\n\n/** Local-space part id, so the fragment stage can treat the three boxes apart. */\nexport const CHAIR_PART = { pedestal: 0, pad: 1, back: 2, body: 3, head: 4 } as const;\n\n/**\n * Parts at or above this index belong to the OCCUPANT, not the chair.\n *\n * They are collapsed to nothing on an empty seat (see the chair vertex shader),\n * which is how one instanced mesh draws both a bare seat and a taken one without\n * a second draw call or a second geometry.\n */\nexport const CHAIR_PART_OCCUPANT_MIN = CHAIR_PART.body;\n\nexport interface ChairMesh {\n /**\n * Local vertex positions. x and z are in units of the seat's own chair\n * half-width (`iChairWidth`); y is in METRES and is not scaled.\n *\n * The split matters: a chart authored at a tight seat pitch needs narrower\n * chairs or they interpenetrate, but it does not need SHORTER ones — a tight\n * row is still full of adult-sized people. A uniform scale would have produced\n * a doll's-house block on exactly the densest charts.\n */\n position: Float32Array;\n normal: Float32Array;\n /** Per-vertex CHAIR_PART. */\n part: Float32Array;\n index: Uint16Array;\n vertexCount: number;\n indexCount: number;\n}\n\n/**\n * A chair's half-width as a fraction of its seat's own spacing.\n *\n * NOT the dot's `SEAT_PITCH_FRACTION` (0.42), and the difference matters. That\n * constant exists to keep round dots visually separate, so it is deliberately\n * conservative and it is capped at SEAT_DOT_RADIUS_M on top. Sizing a chair from\n * it produced tall narrow slabs — a 0.34 m chair with a 0.95 m back, an aspect\n * ratio of nearly 3:1, which reads as a gravestone rather than as seating.\n *\n * Real auditorium seats very nearly touch: at a 0.53 m pitch (the measured mean)\n * a seat is about 0.48 m across. 0.44 reproduces that — the pair occupies 0.88\n * of the pitch and the remaining 12 % is the gap you actually see in a venue.\n */\nexport const CHAIR_PITCH_FRACTION = 0.44;\n\n/**\n * Bounds on the resolved half-width, world metres.\n *\n * The floor is the honest compromise in this file. The amphitheatre measures a\n * 0.21 m minimum seat spacing, and at that spacing NO chair both fits between\n * its neighbours and reads as a chair — 21 cm is not a real seat pitch, it is a\n * chart authored tighter than the units claim. Given the choice between chairs\n * that overlap slightly and chairs that are legible, the overlap wins: it is\n * invisible from any angle a buyer actually looks from, whereas a 9 cm-wide\n * 0.95 m-tall slab is wrong from every angle. The ceiling stops a sparse box\n * seat from inflating into an armchair.\n */\nexport const CHAIR_HALF_WIDTH_MIN_M = 0.15;\nexport const CHAIR_HALF_WIDTH_MAX_M = 0.30;\n/** Used when a seat has no resolvable neighbour at all. */\nexport const CHAIR_HALF_WIDTH_DEFAULT_M = 0.24;\n\n/**\n * The world half-width of one seat's chair, from its own spacing in metres.\n *\n * Everything horizontal about the chair scales from this single number, so a\n * chair's depth tracks its width and the proportions hold at any pitch. Height\n * does not — see `ChairMesh.position`.\n */\nexport function chairHalfWidth(pitchM: number | undefined): number {\n if (pitchM === undefined || !Number.isFinite(pitchM) || pitchM <= 0) {\n return CHAIR_HALF_WIDTH_DEFAULT_M;\n }\n return Math.min(CHAIR_HALF_WIDTH_MAX_M, Math.max(CHAIR_HALF_WIDTH_MIN_M, pitchM * CHAIR_PITCH_FRACTION));\n}\n\n/**\n * Backward lean of the seat back, as a slope (metres of z per metre of rise).\n *\n * The first build had a vertical back and the result read as a crate: a flat top\n * face, a flat wall behind it, and nothing to say which way was up or which way\n * was forward. A rake is the cheapest possible chair cue — it costs a shear —\n * and it puts a lit face at an angle to every other surface in the venue, which\n * is what breaks the block up.\n *\n * Applied in the SHADER, in world metres, and deliberately not baked into the\n * base mesh. The mesh's x/z are in half-width units, so a lean baked there would\n * scale with the seat's width: the same chair would lean 20° on a wide-pitch\n * stadium row and 6° on a tight theatre row. 0.21 is about 12° from vertical,\n * which is roughly a real fixed auditorium seat.\n */\nexport const BACK_RAKE_SLOPE = 0.21;\n\n/**\n * Air between the top of the pad and the bottom of the back, world metres.\n *\n * The other half of \"crate vs chair\". Two boxes sharing a face silhouette as one\n * solid; separate them and the eye reads a seat with a back behind it, even at\n * 10 m where the gap itself is barely a pixel — because it breaks the outline.\n */\nconst PAD_BACK_GAP_M = 0.05;\n\nconst PAD_TOP_M = 0.45;\n/** World height at which the back starts, and therefore where the rake pivots. */\nexport const BACK_BASE_M = PAD_TOP_M + PAD_BACK_GAP_M;\n\n/**\n * Chair dimensions. +Z is the direction the seat FACES, so the back panel lives\n * at negative z. x/z are half-width units; y is metres above the seat's own deck\n * point, chosen against the height contract the rest of the renderer uses — a\n * seated eye sits SEAT_EYE_ABOVE_DECK (1.02 m) up, so a 0.45 m pad and a 0.92 m\n * back top put the occupant's shoulders just over the seat back.\n */\nconst BOXES: Array<{\n part: number;\n min: [number, number, number];\n max: [number, number, number];\n}> = [\n // Pedestal — a plain column under the pad. Without it the pad floats 0.36 m\n // over the deck and the row reads as hovering trays.\n { part: CHAIR_PART.pedestal, min: [-0.30, 0.00, -0.30], max: [0.30, 0.36, 0.30] },\n // Seat pad — a full seat width across and about as deep, which is what a real\n // one is. Its depth is bounded by the same pitch as its width, because the\n // pitch measure is the tighter of the in-row and row-to-row spacings, so a\n // tightly-raked tier cannot drive a pad into the back of the row in front.\n { part: CHAIR_PART.pad, min: [-1.00, 0.36, -0.95], max: [1.00, PAD_TOP_M, 1.00] },\n // Back panel — thin, raked, and the tallest thing in the row, so it is what\n // carries the state colour when you look along a row from behind.\n {\n part: CHAIR_PART.back,\n min: [-1.00, BACK_BASE_M, -1.00],\n max: [1.00, 0.92, -0.72],\n },\n // --- the occupant ---------------------------------------------------------\n //\n // A hall with every seat empty reads as an architectural model, not a venue.\n // The 2048-px panorama this replaced drew a crowd; losing it was the price of\n // sharpness, and this is how it is bought back — in geometry, where there is\n // no resolution ceiling.\n //\n // Deliberately two blocks and no limbs. At the range these are visible a\n // person is a torso and a head, and every extra part multiplies by the number\n // of occupied seats in view. The silhouette is what carries it, exactly as it\n // does in the generated panorama's head-and-shoulder figures.\n //\n // Sized as a seated adult against the 0.92 m chair back: hips at the pad top,\n // shoulders just above the back panel, head clear of it. Torso is narrower\n // than the chair so neighbours never interpenetrate at any pitch, and it sits\n // forward of the back panel rather than inside it.\n {\n part: CHAIR_PART.body,\n min: [-0.66, PAD_TOP_M, -0.58],\n max: [0.66, 1.00, 0.26],\n },\n // The head is deliberately SMALL. Sized by eye against the chair it came out\n // near-cubic and read as Lego; a real head is about 0.16 m across and 0.22 m\n // tall, which against a 0.24 m chair half-width is roughly a third of the\n // chair's width and clearly taller than it is wide. Getting this ratio wrong\n // is what makes a crowd look like toys rather than people.\n {\n part: CHAIR_PART.head,\n min: [-0.34, 1.03, -0.40],\n max: [0.34, 1.27, 0.02],\n },\n];\n\n/** The six axis-aligned faces of a box, as (normal, corner order). */\nconst FACES: Array<{ n: [number, number, number]; c: Array<[number, number, number]> }> = [\n // +X\n { n: [1, 0, 0], c: [[1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 0, 1]] },\n // -X\n { n: [-1, 0, 0], c: [[0, 0, 1], [0, 1, 1], [0, 1, 0], [0, 0, 0]] },\n // +Y\n { n: [0, 1, 0], c: [[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]] },\n // -Y\n { n: [0, -1, 0], c: [[0, 0, 1], [0, 0, 0], [1, 0, 0], [1, 0, 1]] },\n // +Z\n { n: [0, 0, 1], c: [[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]] },\n // -Z\n { n: [0, 0, -1], c: [[1, 0, 0], [0, 0, 0], [0, 1, 0], [1, 1, 0]] },\n];\n\n/**\n * Build the shared chair base mesh. Called once per GPU scene; the result is\n * 72 vertices and 108 indices, i.e. ~1.7 KB of GPU memory total however many\n * seats the venue has.\n */\nexport function buildChairMesh(): ChairMesh {\n const vertexCount = BOXES.length * FACES.length * 4;\n const indexCount = BOXES.length * FACES.length * 6;\n const position = new Float32Array(vertexCount * 3);\n const normal = new Float32Array(vertexCount * 3);\n const part = new Float32Array(vertexCount);\n const index = new Uint16Array(indexCount);\n let v = 0;\n let t = 0;\n for (const box of BOXES) {\n for (const face of FACES) {\n const base = v;\n const corner3 = new Float32Array(12);\n for (let ci = 0; ci < 4; ci++) {\n const corner = face.c[ci];\n for (let a = 0; a < 3; a++) {\n corner3[ci * 3 + a] = corner[a] ? box.max[a] : box.min[a];\n }\n }\n // Face normal derived from the quad rather than asserted. The boxes are\n // axis-aligned today so this reproduces face.n exactly; deriving it means\n // a future non-axis-aligned part cannot ship silently mis-lit.\n const ax = corner3[3] - corner3[0], ay = corner3[4] - corner3[1], az = corner3[5] - corner3[2];\n const bx = corner3[6] - corner3[0], by = corner3[7] - corner3[1], bz = corner3[8] - corner3[2];\n let nx = ay * bz - az * by;\n let ny = az * bx - ax * bz;\n let nz = ax * by - ay * bx;\n const nl = Math.hypot(nx, ny, nz);\n if (nl > 1e-9) { nx /= nl; ny /= nl; nz /= nl; }\n else { nx = face.n[0]; ny = face.n[1]; nz = face.n[2]; }\n // Keep the authored outward sense — the cross product's sign depends on\n // corner order, and a flipped normal would light the face from inside.\n if (nx * face.n[0] + ny * face.n[1] + nz * face.n[2] < 0) { nx = -nx; ny = -ny; nz = -nz; }\n for (let ci = 0; ci < 4; ci++) {\n position[v * 3] = corner3[ci * 3];\n position[v * 3 + 1] = corner3[ci * 3 + 1];\n position[v * 3 + 2] = corner3[ci * 3 + 2];\n normal[v * 3] = nx;\n normal[v * 3 + 1] = ny;\n normal[v * 3 + 2] = nz;\n part[v] = box.part;\n v++;\n }\n index[t++] = base; index[t++] = base + 1; index[t++] = base + 2;\n index[t++] = base; index[t++] = base + 2; index[t++] = base + 3;\n }\n }\n return { position, normal, part, index, vertexCount, indexCount };\n}\n\n/**\n * Per-seat facing yaw, radians, such that the chair's local +Z points at what\n * the seat looks at.\n *\n * Derived from the ROW, not from the seat alone. Aiming every seat individually\n * at the focal point is the obvious rule and it is wrong for the commonest case:\n * a straight row in a theatre fans out into a shallow arc of chairs pointing\n * slightly inward, which no real auditorium does. Taking the row's local tangent\n * and turning perpendicular to it gives BOTH cases for free — a straight row\n * comes out uniformly forward, and a curved arena row keeps the genuine radial\n * fan it is built with.\n *\n * The focal only picks the SIGN (which of the two perpendiculars faces the\n * show), so a mis-authored focal can never rotate a row sideways, only flip it.\n * A row with a single seat has no tangent and falls back to facing the focal\n * directly.\n *\n * Row membership is read as RUNS of equal `rowId` rather than through a map:\n * expanded seats come out row-contiguous, the scan is O(n) with no allocation,\n * and a chart that somehow interleaved rows degrades to shorter runs rather than\n * to wrong answers.\n */\nexport function computeSeatYaw(\n iPosition: Float32Array,\n count: number,\n rowIdAt: (index: number) => string | undefined,\n focal: (index: number) => readonly [number, number],\n): Float32Array {\n const yaw = new Float32Array(count);\n const px = (i: number): number => iPosition[i * 3];\n const pz = (i: number): number => iPosition[i * 3 + 2];\n\n let runStart = 0;\n const flushRun = (start: number, end: number): void => {\n // [start, end) share a rowId.\n const n = end - start;\n for (let i = start; i < end; i++) {\n const [fx, fz] = focal(i);\n let dx = fx - px(i);\n let dz = fz - pz(i);\n if (n >= 2) {\n // Central difference where possible; one-sided at the row ends.\n const a = Math.max(start, i - 1);\n const b = Math.min(end - 1, i + 1);\n const tx = px(b) - px(a);\n const tz = pz(b) - pz(a);\n const tl = Math.hypot(tx, tz);\n if (tl > 1e-6) {\n // Perpendicular to the row, signed toward the focal.\n let nx = -tz / tl;\n let nz = tx / tl;\n if (nx * dx + nz * dz < 0) { nx = -nx; nz = -nz; }\n dx = nx;\n dz = nz;\n }\n }\n // atan2(x, z): the yaw that rotates local +Z onto (dx, dz).\n yaw[i] = (dx === 0 && dz === 0) ? 0 : Math.atan2(dx, dz);\n }\n };\n\n for (let i = 1; i <= count; i++) {\n if (i === count || rowIdAt(i) !== rowIdAt(runStart)) {\n flushRun(runStart, i);\n runStart = i;\n }\n }\n return yaw;\n}\n","/**\n * Pure builder for the instanced seat cloud. Produces the per-instance arrays a\n * single OGL InstancedMesh consumes (one draw call for every seat), plus the\n * seatId → instanceIndex map that `setAvailability` uses to patch only the seats\n * that actually changed via a sub-range `bufferSubData` upload.\n */\n\nimport { accessibilityRingColor, type ExpandedSeat } from '../../core/types';\nimport { hexToRgb } from '../palette';\nimport { SEATED_EYE_HEIGHT_M } from '../../core/units';\nimport { M } from './geometry';\nimport { chairHalfWidth } from './seatChair';\nimport { seatStateIndex, SEAT_STATES, type SeatState3D } from '../palette';\nimport type { VenueSurfaces } from './surface';\n\n/**\n * World radius of a seat dot in metres — the single source for the shader\n * uniform AND for how far a section deck is padded so its seats sit on it.\n * (The shader may grow this with distance to hold `uMinPixels`; this is the\n * near-field base.)\n */\nexport const SEAT_DOT_RADIUS_M = 0.22;\n\n/**\n * Whether a seat has somebody in it, and who.\n *\n * Occupancy is not invented — it is the inventory. A seat that is SOLD has been\n * bought by a person, and a HELD one is being bought right now, so those are the\n * seats that get an occupant. Available and dimmed seats stay empty, and a\n * SELECTED seat stays empty too: that is the buyer's own seat, and drawing a\n * stranger in it while they choose it would be worse than an empty hall.\n *\n * This means the crowd tells the truth about the event. A nearly-sold show looks\n * full from the seat you are considering, and a quiet one looks quiet — which is\n * information a buyer actually wants, arrived at without inventing anything.\n *\n * Returns a negative value for an empty seat; otherwise a stable per-seat hash\n * in [0,1) that varies the occupant's build and tone. Stable so a person does\n * not change shape as the near-field set is re-gathered around a moving camera.\n */\nexport function seatOccupantSeed(stateIndex: number, seatIndex: number): number {\n const state = SEAT_STATES[stateIndex];\n if (state !== 'sold' && state !== 'held') return -1;\n // Cheap integer hash — deterministic, no Math.random, and well distributed\n // across neighbouring indices so adjacent seats do not come out matching.\n let h = (seatIndex + 1) * 2654435761;\n h ^= h >>> 15;\n h = Math.imul(h, 2246822519);\n h ^= h >>> 13;\n return ((h >>> 0) % 100000) / 100000;\n}\n\n/**\n * Fraction of a seat's nearest-neighbour spacing that its dot may occupy.\n *\n * Below 0.5 two adjacent dots cannot touch even when both sit at their ceiling,\n * so a gap always survives between rows. 0.42 leaves that gap visible rather\n * than hairline.\n */\nexport const SEAT_PITCH_FRACTION = 0.42;\n\nexport interface SeatInstanceData {\n count: number;\n /** vec3 per instance: world (x, y, z) in metres. */\n iPosition: Float32Array;\n /** float per instance: index into the seat-state colour LUT. */\n iState: Float32Array;\n /**\n * vec3 per instance: the seat's CATEGORY colour, used while it is available.\n *\n * 2D fills a free seat with its category colour and only overrides it once the\n * seat is held/sold/selected. 3D used one flat green for every available seat,\n * which threw away the price-tier read on switching views and made a full\n * house look like a status map instead of a venue.\n */\n iCategory: Float32Array;\n /**\n * float per instance: the largest world radius this dot may take, metres.\n *\n * The shader enforces `uMinPixels` by GROWING a dot's world radius with depth,\n * which is what merges rows into a solid mass at range — measured mean seat\n * spacing is 0.53–0.58 m against a 0.44 m dot diameter, so there is very little\n * slack to spend before neighbours touch. A global cap cannot fix it: spacing\n * is a property of the chart, and varies between sections of the same venue\n * (measured min 0.21 m on the amphitheatre against a 0.58 m mean).\n *\n * So the ceiling travels per seat, derived from that seat's own nearest\n * neighbour. A dot grows to hold its minimum pixel size and then STOPS,\n * whatever the distance. Past that point holding legibility is the LOD ladder's\n * job (fade toward the tier tint), not the dot's.\n */\n iMaxRadius: Float32Array;\n /**\n * vec3 per instance: accommodation ring colour, or (0,0,0) for none.\n *\n * 2D draws a coloured ring around every seat with an accessibility type, and\n * 3D drew nothing at all — so a wheelchair space, a companion seat or a\n * lift-armrest seat was indistinguishable from any other the moment a buyer\n * switched to the 3D view. A ring mirrors the 2D treatment exactly, needs no\n * texture, and costs one instanced attribute rather than a draw call.\n */\n iRing: Float32Array;\n /** float per instance: owning floor index, for per-floor isolation. */\n iFloor: Float32Array;\n /**\n * float per instance: facing yaw in radians, for the near-field chair mesh.\n *\n * A billboard dot has no front, so nothing here ever needed a direction. A\n * chair does, and getting it wrong is worse than drawing nothing — a row of\n * seats with their backs to the stage reads as a mistake immediately.\n *\n * Left at zero by this builder and filled by `sceneModel`, which is the layer\n * that knows what each seat FACES (its floor's focal point). Direct callers\n * and tests that build instances without a scene get zeros, which is a\n * well-defined \"facing +Z\" rather than an undefined attribute.\n */\n iYaw: Float32Array;\n /**\n * float per instance: half-width of this seat's near-field CHAIR, world metres.\n *\n * Deliberately not `iMaxRadius`. That value is the dot's ceiling — conservative\n * by design so round dots stay visually apart, and clamped to\n * SEAT_DOT_RADIUS_M on top of that. Sizing a chair from it gave 0.34 m chairs\n * under 0.95 m backs: an aspect ratio near 3:1, which reads as a headstone.\n * Real seats very nearly touch. See `chairHalfWidth`.\n */\n iChairWidth: Float32Array;\n /** seatId → instance index (drives targeted availability updates). */\n idToIndex: Map<string, number>;\n}\n\n/**\n * Nearest-neighbour distance in chart units for every seat, via a uniform grid.\n *\n * Linear in seat count for realistic charts (the grid cell is sized to the mean\n * spacing, so each lookup touches a bounded neighbourhood) — the naive pairwise\n * version is 14k² on the Uber Arena and would show up in build time.\n */\nfunction nearestNeighbourSpacing(seats: ExpandedSeat[]): Float64Array {\n const n = seats.length;\n const out = new Float64Array(n);\n if (n < 2) { out.fill(Infinity); return out; }\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const s of seats) {\n if (s.x < minX) minX = s.x;\n if (s.y < minY) minY = s.y;\n if (s.x > maxX) maxX = s.x;\n if (s.y > maxY) maxY = s.y;\n }\n const w = Math.max(maxX - minX, 1e-6), h = Math.max(maxY - minY, 1e-6);\n // Aim at ~1 seat per cell: cell = sqrt(area / count).\n const cell = Math.max(Math.sqrt((w * h) / n), 1e-6);\n const cols = Math.max(1, Math.ceil(w / cell) + 1);\n const rows = Math.max(1, Math.ceil(h / cell) + 1);\n const buckets = new Map<number, number[]>();\n const cellOf = (x: number, y: number): number => {\n const cx = Math.min(cols - 1, Math.max(0, Math.floor((x - minX) / cell)));\n const cy = Math.min(rows - 1, Math.max(0, Math.floor((y - minY) / cell)));\n return cy * cols + cx;\n };\n for (let i = 0; i < n; i++) {\n const k = cellOf(seats[i].x, seats[i].y);\n const b = buckets.get(k);\n if (b) b.push(i); else buckets.set(k, [i]);\n }\n for (let i = 0; i < n; i++) {\n const s = seats[i];\n const cx = Math.min(cols - 1, Math.max(0, Math.floor((s.x - minX) / cell)));\n const cy = Math.min(rows - 1, Math.max(0, Math.floor((s.y - minY) / cell)));\n let best = Infinity;\n // Widen the ring until a hit is found — a sparse region (a lone box seat)\n // would otherwise report Infinity and lose its ceiling entirely.\n for (let r = 1; r <= 4; r++) {\n for (let gy = cy - r; gy <= cy + r; gy++) {\n if (gy < 0 || gy >= rows) continue;\n for (let gx = cx - r; gx <= cx + r; gx++) {\n if (gx < 0 || gx >= cols) continue;\n // Only the newly-added ring after the first pass.\n if (r > 1 && Math.abs(gy - cy) < r && Math.abs(gx - cx) < r) continue;\n const b = buckets.get(gy * cols + gx);\n if (!b) continue;\n for (const j of b) {\n if (j === i) continue;\n const d = Math.hypot(seats[j].x - s.x, seats[j].y - s.y);\n if (d < best) best = d;\n }\n }\n }\n if (Number.isFinite(best) && best <= r * cell) break;\n }\n out[i] = best;\n }\n return out;\n}\n\n/**\n * Resolve a seat's surface height in world metres — the fallback path used only\n * when no venue surface was supplied (direct callers, tests). Prefer the\n * section-resolved eye height minus the seated-eye offset.\n *\n * There is deliberately NO lift constant here any more. A dot is placed exactly\n * ON the deck, and the seat shader offsets the billboard by its own radius along\n * screen-up so the dot RESTS on that point at every distance. A fixed lift could\n * not do that: the shader enforces `uMinPixels`, so a dot's world radius grows\n * with depth and any constant clearance is eventually smaller than the dot.\n */\nfunction seatSurfaceY(seat: ExpandedSeat): number {\n const eye = seat.eyeHeightM;\n if (Number.isFinite(eye)) return Math.max(0, (eye as number) - SEATED_EYE_HEIGHT_M);\n return 0;\n}\n\nexport function buildSeatInstances(\n seats: ExpandedSeat[],\n initial?: (seat: ExpandedSeat) => SeatState3D,\n surfaces?: VenueSurfaces,\n seatFloor?: Float32Array,\n /** categoryKey -> display colour, for the AVAILABLE tint. Omitted by direct\n * callers and tests, which then get the flat state green as before. */\n categoryColor?: Map<string, string>,\n): SeatInstanceData {\n const count = seats.length;\n const iPosition = new Float32Array(count * 3);\n const iState = new Float32Array(count);\n const iCategory = new Float32Array(count * 3);\n const iMaxRadius = new Float32Array(count);\n const iChairWidth = new Float32Array(count);\n const iRing = new Float32Array(count * 3);\n const idToIndex = new Map<string, number>();\n const spacing = nearestNeighbourSpacing(seats);\n for (let i = 0; i < count; i++) {\n const seat = seats[i];\n // The seat's OWN row pitch when its section resolved into rows; the raw\n // nearest-neighbour distance only as a fallback for unstructured seats.\n // See `VenueSurfaces.seatPitchU` for why the raw measure is wrong.\n const resolved = surfaces?.seatPitchU(i);\n const pitchM = (resolved ?? spacing[i]) * M;\n // The ceiling is allowed BELOW the base radius. A chart authored tighter\n // than the 0.22 m default dot (measured 0.21 m minimum spacing on the\n // amphitheatre) overlaps at every distance today, including close up, and\n // shrinking those dots is the only thing that separates them. A small floor\n // keeps a pathologically dense chart's seats visible rather than vanishing.\n // A seat with no resolvable neighbour keeps the base radius, not an\n // unbounded one.\n iMaxRadius[i] = Number.isFinite(pitchM)\n ? Math.max(0.06, Math.min(SEAT_DOT_RADIUS_M, pitchM * SEAT_PITCH_FRACTION))\n : SEAT_DOT_RADIUS_M;\n // The chair reads the SAME pitch but through its own rule — see iChairWidth.\n iChairWidth[i] = chairHalfWidth(Number.isFinite(pitchM) ? pitchM : undefined);\n iPosition[i * 3] = seat.x * M;\n // The venue surface wins when present: it is the same function the tier cap\n // is built from, which is what guarantees a seat can never sit inside it.\n iPosition[i * 3 + 1] = surfaces ? surfaces.seatDeckY(i) : seatSurfaceY(seat);\n iPosition[i * 3 + 2] = seat.y * M;\n iState[i] = seatStateIndex(initial ? initial(seat) : 'available');\n // The seat's CATEGORY colour, which is what 2D paints a free seat with\n // (`SeatmapRenderer.paintSeat` -> `seatBaseColor(categoryKey)`). 3D painted\n // every available seat one flat green instead, so a venue lost its price\n // tiers the moment a buyer switched view — and at seat level a hall of\n // identical green read as a diagram rather than a room. Falls back to the\n // same #6e7bff 2D uses when a category has no colour.\n const cat = hexToRgb(categoryColor?.get(seat.categoryKey) ?? '#6e7bff') ?? [0.43, 0.48, 1.0];\n iCategory[i * 3] = cat[0];\n iCategory[i * 3 + 1] = cat[1];\n iCategory[i * 3 + 2] = cat[2];\n // Same source of truth as 2D: the ring colour is derived from the seat's\n // accessibility types, so the two views cannot disagree about which seat is\n // an accessible one.\n if (seat.accessibility?.length) {\n const rgb = hexToRgb(accessibilityRingColor(seat.accessibility));\n if (rgb) { iRing[i * 3] = rgb[0]; iRing[i * 3 + 1] = rgb[1]; iRing[i * 3 + 2] = rgb[2]; }\n }\n idToIndex.set(seat.id, i);\n }\n return {\n count, iPosition, iState, iCategory, iMaxRadius, iRing, idToIndex, iChairWidth,\n iFloor: seatFloor ?? new Float32Array(count),\n iYaw: new Float32Array(count),\n };\n}\n\n/** Contiguous run of instance indices to upload in a single bufferSubData call. */\nexport interface DirtyRun {\n start: number;\n /** number of instances (floats, since iState is 1 float/instance). */\n length: number;\n}\n\n/**\n * Instances worth re-uploading just to avoid another GL call.\n *\n * Runs are merged across gaps up to this size. A `bufferSubData` has fixed\n * driver overhead regardless of how few bytes it carries, so uploading a handful\n * of unchanged instances inside one call is far cheaper than issuing a second.\n *\n * The case this exists for is a live socket delta on a big venue, where the\n * changed seats are SCATTERED rather than contiguous. Measured on a 50,400-seat\n * chart before merging: 1,000 scattered seats produced 983 separate uploads and\n * 5,000 produced 4,438 — thousands of GL calls in a single frame to move a few\n * kilobytes. A block going on sale already coalesced well; random churn did not.\n */\nconst RUN_MERGE_GAP = 64;\n\n/**\n * Apply state updates to the CPU `iState` array and return the coalesced\n * contiguous runs that changed — the caller uploads exactly those ranges and\n * never the whole buffer.\n */\nexport function applySeatStates(\n data: SeatInstanceData,\n updates: Array<{ seatId: string; state: SeatState3D }>,\n): DirtyRun[] {\n const changed: number[] = [];\n for (const u of updates) {\n const idx = data.idToIndex.get(u.seatId);\n if (idx === undefined) continue;\n const v = seatStateIndex(u.state);\n if (data.iState[idx] !== v) {\n data.iState[idx] = v;\n changed.push(idx);\n }\n }\n if (!changed.length) return [];\n changed.sort((a, b) => a - b);\n const runs: DirtyRun[] = [];\n let start = changed[0];\n let prev = changed[0];\n for (let i = 1; i < changed.length; i++) {\n const idx = changed[i];\n if (idx === prev) continue;\n // Bridge small gaps: the unchanged instances in between are rewritten from\n // `iState`, which already holds their current value, so this is a no-op for\n // them and saves a GL call. See RUN_MERGE_GAP.\n if (idx <= prev + RUN_MERGE_GAP) { prev = idx; continue; }\n runs.push({ start, length: prev - start + 1 });\n start = idx;\n prev = idx;\n }\n runs.push({ start, length: prev - start + 1 });\n return runs;\n}\n","/**\n * Resolve a chart's authored theme into the 3D scene's colours.\n *\n * Every chart already carries a `ChartTheme` — background, brand accent, seat\n * scale — and the 2D renderer and the picker chrome honour it. The 3D view did\n * not: `palette.ts` was a fixed set of constants, so a white-labelled event\n * rendered in SeatLayer's own dark grey whatever the organizer had branded. That\n * is a visible gap in a paid feature, and it is the kind of thing a customer\n * notices immediately when they switch from the 2D map to the 3D view.\n *\n * The palette in `palette.ts` stays the DEFAULT and the reference. This module\n * only rebases it, so an unthemed chart is byte-for-byte what it was.\n *\n * ## How structure is rebased\n *\n * Structure colours are not replaced by the brand colour — a venue rendered in\n * flat brand paint reads as a diagram, not a building, and the look brief is\n * deliberately \"desaturated greys for structure, saturated colour only on\n * seats\". Instead each structure colour is blended a little way toward the\n * authored background, so the whole venue picks up the brand's cast and sits in\n * its own light, while keeping the tonal relationships (tier above wall, stage\n * warmer than tier) that make the geometry readable.\n */\n\nimport type { ChartTheme } from '../core/types';\nimport { BACKGROUND, SEAT_STATE_COLORS, STRUCTURE, hexToRgb, mix, scaleRgb, type RGB, type SeatState3D } from './palette';\n\n/** How far a structure colour is pulled toward the authored background. */\nconst BACKGROUND_INFLUENCE = 0.15;\n\n/**\n * The background gradient's two stops as multiples of the authored colour.\n *\n * Fitted to the existing hand-tuned gradient on the default `#0e1117`, whose\n * per-channel ratios are 0.91/0.90/0.89 for the top stop and 1.82/1.80/1.66 for\n * the bottom. A single scalar cannot reproduce a hand-picked triple exactly, so\n * these are the best fit: an authored background lands within ~0.01 of the old\n * look, and an UNTHEMED chart bypasses this path entirely and stays identical.\n * What matters is that a themed chart gets the same vertical falloff around its\n * own colour rather than a flat wash.\n */\nconst BG_TOP_SCALE = 0.9;\nconst BG_BOTTOM_SCALE = 1.76;\n\n/** Bounds on the authored seat-size multiplier, matching `ChartTheme.seatScale`. */\nconst SEAT_SCALE_MIN = 0.7;\nconst SEAT_SCALE_MAX = 1.6;\n\nexport interface Theme3D {\n background: { top: RGB; bottom: RGB };\n structure: typeof STRUCTURE;\n seatStates: Record<SeatState3D, RGB>;\n /** Multiplier on the seat dot's world radius. */\n seatScale: number;\n}\n\n/** The unthemed default — the palette exactly as authored in `palette.ts`. */\nexport function defaultTheme3D(): Theme3D {\n return {\n background: { top: [...BACKGROUND.top] as RGB, bottom: [...BACKGROUND.bottom] as RGB },\n structure: STRUCTURE,\n seatStates: { ...SEAT_STATE_COLORS },\n seatScale: 1,\n };\n}\n\nexport function resolveTheme3D(theme: ChartTheme | undefined): Theme3D {\n const base = defaultTheme3D();\n if (!theme) return base;\n\n const bg = hexToRgb(theme.background);\n if (bg) {\n base.background = {\n top: scaleRgb(bg, BG_TOP_SCALE),\n bottom: scaleRgb(bg, BG_BOTTOM_SCALE),\n };\n // Rebase every structure colour onto the authored background. Done as one\n // pass over the palette rather than field by field, so a colour added to\n // STRUCTURE later is themed automatically instead of silently staying fixed.\n const rebased: Record<string, RGB> = {};\n for (const [key, value] of Object.entries(STRUCTURE)) {\n rebased[key] = mix(value as RGB, bg, BACKGROUND_INFLUENCE);\n }\n base.structure = rebased as unknown as typeof STRUCTURE;\n }\n\n // Selection is the one seat colour a brand owns: it is the buyer's own\n // choice reflected back, and the picker chrome already paints it in `accent`.\n // Availability, held and sold stay fixed — they carry MEANING, and letting a\n // brand recolour \"sold\" would let a chart mislead about what is for sale.\n const selection = hexToRgb(theme.selectionColor) ?? hexToRgb(theme.accent);\n if (selection) base.seatStates = { ...base.seatStates, selected: selection };\n\n const scale = theme.seatScale;\n if (typeof scale === 'number' && Number.isFinite(scale)) {\n base.seatScale = Math.min(SEAT_SCALE_MAX, Math.max(SEAT_SCALE_MIN, scale));\n }\n return base;\n}\n\n/** Flat LUT (5 × vec3) for the seat fragment shader, in `SEAT_STATES` order. */\nexport function themeSeatColorLUT(theme: Theme3D, order: readonly SeatState3D[]): number[] {\n const out: number[] = [];\n for (const s of order) out.push(...theme.seatStates[s]);\n return out;\n}\n","/**\n * Venue labels — anchors, level-of-detail, and world→screen projection.\n *\n * ## Why labels are DOM, not geometry\n *\n * The renderer is deliberately texture-free at three draw calls. Drawing text on\n * the GPU means a signed-distance font atlas: a texture, another shader, a build\n * asset, and a resolution ceiling — a lot of machinery for the few dozen labels a\n * venue actually needs. Projecting anchors and positioning DOM elements costs\n * nothing when a chart has no labels, and buys properties the GPU path cannot:\n *\n * - **Real text.** A screen reader can read the venue's structure. That is the\n * accessibility gap in 3D, not just a rendering convenience.\n * - Crisp at any device pixel ratio and any zoom, with no atlas to outgrow.\n * - `ChartTheme.fontFamily`, i18n and RTL come from the browser.\n *\n * This module is the pure half — what to label, where its anchor sits, and when\n * it should show. The overlay that positions elements lives in `index.ts`.\n *\n * ## Why the rungs mirror 2D\n *\n * 2D melts through zones → sections → seats. Labels follow the same idea for the\n * same reason: at a distance a buyer needs to know which part of the venue they\n * are looking at, and up close they need to know which block and which door. A\n * label set that does not thin out with distance turns a 51-section arena into\n * unreadable confetti.\n */\n\nimport type { Point } from '../core/types';\n\nexport type LabelKind = 'zone' | 'section' | 'ga' | 'annotation' | 'booth' | 'row' | 'seat';\n\nexport interface SceneLabel {\n id: string;\n kind: LabelKind;\n text: string;\n /** World-metre anchor the label is pinned to. */\n anchor: [number, number, number];\n /** Authored colour (`#rrggbb`), when the object carries one. */\n color?: string;\n /** Authored rotation in degrees, for annotations that specify one. */\n rotation?: number;\n}\n\n/**\n * Distance thresholds as multiples of the venue radius, matching the seat LOD's\n * scale so labels and seats thin out together rather than fighting.\n *\n * Zone labels are the far rung and switch OFF close in, where they would sit on\n * top of the section labels that have become more useful. Section labels are the\n * middle rung. Annotations and booth labels are wayfinding — only legible, and\n * only wanted, once the buyer is actually in that part of the venue.\n */\nconst ZONE_MIN_DISTANCE = 1.15;\nconst SECTION_MAX_DISTANCE = 2.2;\nconst NEAR_MAX_DISTANCE = 0.85;\n/**\n * Row and seat identity are the last two rungs, mirroring 2D's melt: a buyer\n * this close has stopped choosing a part of the venue and started choosing a\n * place to sit. Seats come in last of all — a seat number is unreadable and\n * useless until you are close enough that one row fills much of the screen.\n */\nconst ROW_MAX_DISTANCE = 0.5;\nconst SEAT_MAX_DISTANCE = 0.26;\n\n/** Which label kinds should show at this camera distance. */\nexport function visibleLabelKinds(distance: number, venueRadius: number): Set<LabelKind> {\n const r = Math.max(1e-6, venueRadius);\n const d = distance / r;\n const out = new Set<LabelKind>();\n if (d >= ZONE_MIN_DISTANCE) out.add('zone');\n // A GA area is named at the same rung as a section because that is what it is\n // — a sellable part of the venue the buyer picks before picking a place in it.\n if (d <= SECTION_MAX_DISTANCE) { out.add('section'); out.add('ga'); }\n if (d <= NEAR_MAX_DISTANCE) { out.add('annotation'); out.add('booth'); }\n if (d <= ROW_MAX_DISTANCE) out.add('row');\n if (d <= SEAT_MAX_DISTANCE) out.add('seat');\n return out;\n}\n\nexport interface Projected {\n /** CSS pixels from the container's left/top. */\n x: number;\n y: number;\n /** Normalised depth; smaller is nearer. */\n depth: number;\n /** False when the anchor is behind the camera or outside the frustum. */\n visible: boolean;\n}\n\n/**\n * Project a world point through a column-major 4x4 view-projection matrix.\n *\n * Behind-camera points are reported invisible rather than mirrored to the far\n * side of the screen, which is what a naive divide by a negative w produces —\n * a label for the section behind you appearing over the stage in front of you.\n */\nexport function projectToScreen(\n viewProjection: ArrayLike<number>,\n p: readonly [number, number, number],\n width: number,\n height: number,\n): Projected {\n const m = viewProjection;\n const x = p[0], y = p[1], z = p[2];\n const cx = m[0] * x + m[4] * y + m[8] * z + m[12];\n const cy = m[1] * x + m[5] * y + m[9] * z + m[13];\n const cz = m[2] * x + m[6] * y + m[10] * z + m[14];\n const cw = m[3] * x + m[7] * y + m[11] * z + m[15];\n if (!(cw > 1e-6)) return { x: 0, y: 0, depth: Infinity, visible: false };\n const ndcX = cx / cw, ndcY = cy / cw, ndcZ = cz / cw;\n const inside = ndcX >= -1.05 && ndcX <= 1.05 && ndcY >= -1.05 && ndcY <= 1.05 && ndcZ <= 1;\n return {\n x: (ndcX * 0.5 + 0.5) * width,\n y: (1 - (ndcY * 0.5 + 0.5)) * height,\n depth: ndcZ,\n visible: inside,\n };\n}\n\n/**\n * Drop labels that would overlap, nearest kept.\n *\n * Without this a dense venue paints its section names on top of each other and\n * every one of them becomes unreadable — worse than showing fewer. Nearest-wins\n * because the label a buyer is closest to is the one they are asking about.\n *\n * The test is RECTANGULAR, not radial. A label is a line of text: wide and\n * short. A single radius big enough to stop two names colliding side by side is\n * far bigger than the vertical gap they actually need, so a radial test threw\n * away labels that were stacked but perfectly readable — on the amphitheatre it\n * dropped \"Terrace\" purely for sitting between the other two concentric zones.\n */\nexport function cullOverlapping<T extends { screen: Projected }>(\n items: T[],\n separationX: number,\n separationY: number = separationX,\n): T[] {\n const kept: T[] = [];\n const ordered = [...items].sort((a, b) => a.screen.depth - b.screen.depth);\n for (const item of ordered) {\n let clash = false;\n for (const k of kept) {\n const dx = Math.abs(item.screen.x - k.screen.x);\n const dy = Math.abs(item.screen.y - k.screen.y);\n // Only a genuine box overlap counts: near on BOTH axes.\n if (dx < separationX && dy < separationY) { clash = true; break; }\n }\n if (!clash) kept.push(item);\n }\n return kept;\n}\n\n/**\n * How many of the dense per-place labels may be painted at once.\n *\n * The overlap cull above is necessary and NOT sufficient, and the gap between\n * those two is a real defect: it only removes labels that collide, so the closer\n * the camera gets the FEWER collisions there are and the MORE labels survive.\n * Flown to a seat in a 3,605-seat amphitheatre, every row and seat name in the\n * frustum passed the test and the frame became a wall of tiled text with the\n * seating unreadable behind it — the labels stopped describing the venue and\n * became the thing obscuring it.\n *\n * A budget is the missing rung. A buyer arriving at their seat wants the handful\n * of names around them, not an index of the building; past a dozen or so the set\n * has no informational value left, only cost.\n */\nexport const DENSE_LABEL_BUDGET: Record<'row' | 'seat', number> = { row: 8, seat: 14 };\n\n/**\n * Order dense labels by how much the buyer is likely to care: near the camera\n * and near the middle of the frame first.\n *\n * Both terms are needed. Distance alone keeps labels for seats behind the\n * camera's shoulder that happen to be metres away; screen-centre alone keeps a\n * label for a seat on the far side of the venue purely for being dead ahead. The\n * product ranks \"what I am looking at, close to me\" top, which is the seat a\n * buyer just flew to.\n */\nexport function focusScore(\n screen: Projected,\n worldDistance: number,\n width: number,\n height: number,\n): number {\n const half = Math.max(1, Math.min(width, height) * 0.5);\n const off = Math.min(2, Math.hypot(screen.x - width / 2, screen.y - height / 2) / half);\n return worldDistance * (1 + 1.5 * off);\n}\n\n/**\n * Pick the dense labels to paint: rank by focus, drop overlaps in that order,\n * then take at most `budget`.\n *\n * Order matters. Culling overlaps FIRST and then truncating would let an\n * arbitrary far-corner label win a slot over the seat the buyer is sitting in,\n * because the overlap pass keeps whatever it happens to reach first.\n */\nexport function pickDenseLabels<T extends { screen: Projected; focus: number }>(\n items: T[],\n separationX: number,\n separationY: number,\n budget: number,\n): T[] {\n const ordered = [...items].sort((a, b) => a.focus - b.focus);\n const kept: T[] = [];\n for (const item of ordered) {\n if (kept.length >= budget) break;\n let clash = false;\n for (const k of kept) {\n if (Math.abs(item.screen.x - k.screen.x) < separationX\n && Math.abs(item.screen.y - k.screen.y) < separationY) { clash = true; break; }\n }\n if (!clash) kept.push(item);\n }\n return kept;\n}\n\n/** Mean of a point set, or null when empty. */\nexport function centroidOf(points: readonly Point[]): Point | null {\n if (!points.length) return null;\n let x = 0, y = 0;\n for (const p of points) { x += p.x; y += p.y; }\n return { x: x / points.length, y: y / points.length };\n}\n","/**\n * The JS-side source of truth for the 3D scene — a pure, GPU-free description\n * built once from the chart's existing height contract. Everything the renderer\n * uploads (merged solid geometry, the instanced seat cloud, camera-framing\n * bounds, the seat-state colour LUT) is derived here, so it survives a WebGL\n * context loss: on `webglcontextrestored` the renderer simply re-uploads from\n * this model without recomputing anything.\n *\n * Feeds 100% from `sectionGeometry` / `Floor.baseHeightM` / `ExpandedSeat`\n * (docs/3d-program-workorder §Architecture) — no new chart data is invented.\n */\n\nimport type { ChartDoc, ChartObject, ExpandedSeat, Point, SectionObject } from '../../core/types';\nimport { CHART_UNITS_PER_METRE } from '../../core/units';\nimport { SEAT_STATES, STRUCTURE, hexToRgb, mix, desaturate, scaleRgb, type RGB } from '../palette';\nimport { resolveTheme3D, themeSeatColorLUT, type Theme3D } from '../theme';\nimport { centroidOf, type SceneLabel } from '../labels';\nimport {\n MeshBuilder, extrudePrism, mergeMeshData, ellipsePolygon, rectPolygon, outsetRing, M, type MeshData,\n} from './geometry';\nimport polygonClipping from 'polygon-clipping';\nimport { buildSeatInstances, SEAT_DOT_RADIUS_M, type SeatInstanceData } from './seatInstances';\nimport { computeSeatYaw } from './seatChair';\nimport { buildVenueSurfaces, CAP_MAX_ERROR_M, type SectionSurface } from './surface';\n\n/** Axis-aligned stage footprint in world metres. */\nexport interface StageBounds {\n cx: number;\n cz: number;\n y: number;\n halfX: number;\n halfZ: number;\n}\nimport { emitDeckBands, deckFootprints, rowNeighbourhoods } from './deckBands';\n\n/** One resolved plane of geometry — a single-floor chart is one of these. */\ninterface FloorUnit {\n objects: ChartObject[];\n focal: Point;\n baseHeightM: number;\n}\n\n/**\n * A navigable zone — the venue's own top-level grouping (Orchestra, Lower Bowl,\n * Hall A), resolved for camera framing and for the section/zone LOD rung.\n *\n * Every shipped chart authors zones and gives every section one, and the 2D\n * renderer uses them as its farthest LOD rung. 3D ignored them entirely, so the\n * one structure a buyer navigates by (\"take me to the Grand Circle\") had no\n * representation at all in the 3D view.\n */\nexport interface SceneZone {\n id: string;\n label: string;\n /** Authored zone colour, or null when the chart leaves it to the category mix. */\n color: RGB | null;\n /** Section object ids belonging to this zone. */\n sectionIds: string[];\n /** Seats resolved into this zone. */\n seatCount: number;\n /** World-metre centre of the zone's seats (camera target). */\n center: [number, number, number];\n /** Half-diagonal of its footprint, world metres (camera fit). */\n radius: number;\n /** What this zone faces — its authored focal, else the venue's. */\n focalWorld: [number, number, number];\n}\n\n/**\n * One seated section, resolved for navigation — the middle rung of the venue\n * ladder, between a zone and a seat. A chart with no zones authored still has\n * sections, so this is the level at which every venue can be navigated.\n */\nexport interface SceneSection {\n id: string;\n /** Buyer-facing name, matching the section label. */\n label: string;\n seatCount: number;\n /** World-metre centre of the section's seats (camera target). */\n center: [number, number, number];\n /** Half-diagonal of its seat footprint, world metres (camera fit). */\n radius: number;\n /** What this section faces — the camera approaches from this side. */\n focalWorld: [number, number, number];\n}\n\n/** 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 * Stage footprints in world metres.\n *\n * Collected for stage lighting, which is not shipped: additive cones were\n * built, placed correctly and still could not be read from a seat (see\n * `git log` for `stageLight.ts`). The bounds stay because they are cheap,\n * correct, and exactly what a second attempt needs — the geometry was never\n * the hard part, the technique was.\n */\n stages: StageBounds[];\n /** Seated sections, in authored order — the navigable middle rung. */\n sections: SceneSection[];\n /**\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 padded = outsetRing(section.outline, SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE);\n // Only siblings whose bounding box actually overlaps can remove any area, and\n // a polygon boolean is expensive. Differencing against EVERY sibling made\n // scene build quadratic in section count: measured 2.7 s at 50k seats / 40\n // sections and 11.7 s at 99k / 60, where doubling the venue cost 4.3x the\n // time. A venue's sections are laid out around it, so almost no pair overlaps\n // and this prunes nearly all of the work.\n const box = bboxOfRing(padded);\n const others = siblings.filter((o) => o !== section\n && o.outline && o.outline.length >= 3\n && boxesOverlap(box, bboxOfRing(o.outline)));\n if (!others.length) return [padded];\n const toRing = (pts: readonly Point[]): [number, number][] => {\n const r = pts.map((p) => [p.x, p.y] as [number, number]);\n r.push(r[0]);\n return r;\n };\n try {\n const diff = polygonClipping.difference([toRing(padded)], ...others.map((o) => [toRing(o.outline)]));\n const out: Point[][] = [];\n for (const poly of diff) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push(pts);\n }\n return out.length ? out : [padded];\n } catch {\n return [padded];\n }\n}\n\n/** Intersect each block footprint with the section's own allowed area. */\nfunction clipFootprints(\n blocks: ReturnType<typeof deckFootprints>,\n allowed: Point[][],\n): ReturnType<typeof deckFootprints> {\n if (!allowed.length) return blocks;\n const toRing = (pts: readonly Point[]): [number, number][] => {\n const r = pts.map((p) => [p.x, p.y] as [number, number]);\n r.push(r[0]);\n return r;\n };\n const allowedPolys = allowed.map((a) => [toRing(a)]);\n const out: ReturnType<typeof deckFootprints> = [];\n for (const b of blocks) {\n // NOTE: a \"skip the clip when the block looks contained\" fast path was tried\n // here and reverted. Testing that the block's VERTICES lie inside the allowed\n // ring is not sound for a CONCAVE allowed region — and a section footprint is\n // routinely concave, since an arena wedge curves inward and siblings are\n // subtracted from it. An edge can bulge outside while every vertex is inside,\n // which buried seats on the arena. A sound test needs edge-intersection\n // checks, which is most of the cost of the boolean it would be avoiding.\n let pieces;\n try {\n pieces = polygonClipping.intersection([toRing(b.outline)], ...[allowedPolys.flat()]);\n } catch {\n out.push(b);\n continue;\n }\n for (const poly of pieces) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push({ outline: pts, holes: [], topY: b.topY });\n }\n }\n return out.length ? out : blocks;\n}\n\nfunction buildTier(\n builder: MeshBuilder,\n section: SectionObject,\n unit: FloorUnit,\n fill: RGB | null,\n surface: SectionSurface | undefined,\n claimed: ClaimedArea,\n siblings: readonly SectionObject[],\n S: typeof STRUCTURE,\n): void {\n if (!section.outline || section.outline.length < 3) return;\n const bottomY = unit.baseHeightM;\n const colTop = tintTop(fill, S.tierTop);\n if (!surface) return;\n const topY = (p: Point): number => surface.deckAt(p.x, p.y);\n // Pad the deck so a seat authored hard against the section boundary rests ON\n // its own section instead of overhanging into the aisle. Seats are chart data\n // and never move; the deck grows to meet them. 1.5x the dot footprint: enough\n // that a dot sitting exactly on the boundary still lands clear of the edge,\n // and small against real aisle widths.\n const footprint = paddedFootprint(section, siblings);\n const outline = footprint[0] ?? section.outline;\n\n // A section whose rows resolved into levels is drawn as GEOMETRY, not as a\n // tessellated field: a flat landing prism carrying one level ribbon per row,\n // with a riser between consecutive ribbons. See `deckBands.ts` for why a\n // refined cap cannot work once every row has to be level.\n if (surface.rowLevels.length >= 2) {\n // One base prism per BLOCK, hugging the ribbons above it, instead of one flat\n // plate over the section's whole authored outline. The outline reaches well\n // past the seating, so a single plate stuck out around the stands as a slab\n // and cut every block's end square through it. Each block also stands on its\n // OWN lowest row rather than the section's.\n // Clipped to the section's own footprint like the treads are: a block base is\n // built from a ribbon union carrying its own outward margin, so it overhangs\n // for the same reason and buries a lower neighbour's seats the same way.\n // One neighbourhood solve, shared by the footprint and the emission below.\n const nbrs = rowNeighbourhoods(surface.rowLevels);\n const blocks = clipFootprints(deckFootprints(surface.rowLevels, unit.focal, nbrs), footprint);\n // Explicit UP normals rather than face normals. A block footprint is a\n // boolean union of ~100 overlapping ribbons, and such a union always leaves\n // some very thin triangles along its seams; deriving a normal from one by\n // cross product is numerically meaningless and shades as a dark hairline.\n // The top is FLAT, so the correct normal is known outright and the thin\n // triangles become harmless.\n const capUp = (): [number, number, number] => [0, 1, 0];\n for (const b of blocks) {\n 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 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(), nbrs);\n return;\n }\n\n // A flat section's cap is exact at any tessellation, so it is left\n // unsubdivided (Infinity) and stays identical to the pre-surface mesh —\n // legacy height-less charts must not shift by a single vertex.\n const maxErr = surface.flat ? Infinity : CAP_MAX_ERROR_M;\n const topN = (p: Point): [number, number, number] => surface.normalAt(p.x, p.y);\n\n // Take only the ground this section has not already lost to an earlier one.\n //\n // Every flat section's top sits at exactly the same height, and the deck\n // padding above grows each one by 0.33 m, so neighbouring sections OVERLAP.\n // Two coplanar surfaces at identical depth is a z-fight, and it renders as a\n // serrated comb of interpenetrating teeth along every shared boundary — the\n // artifact visible on the flat chart. Depth-biasing them apart would hide it\n // rather than fix it, and would break down as soon as a chart has many\n // sections. Removing the overlap means there is nothing to fight over.\n // A flat section's deck is one constant height, so it can be tested for\n // coplanarity; a raked fallback surface has no single height to offer.\n const level = surface.flat ? surface.landingY : undefined;\n for (const ring of claimed.subtract(outline, level)) {\n extrudePrism(builder, ring, section.holes, topY, bottomY, colTop, S.tierWall, AO, maxErr, topN);\n }\n}\n\n/**\n * The ground already taken by sections drawn so far, so a later section can be\n * clipped to what is left. Only meaningful between COPLANAR surfaces, which is\n * why raked sections (each at its own height) never reach this.\n */\n/** Two claims fight for the same ground only if both are level and level TOGETHER.\n * A claim with no single height (an unresolved raked surface) is treated as\n * comparable with everything, which is what it did before heights existed. */\nfunction coplanarLevels(a: number | undefined, b: number | undefined): boolean {\n if (a === undefined || b === undefined) return true;\n return Math.abs(a - b) < 1e-3;\n}\n\nclass ClaimedArea {\n private rings: Array<[number, number][][]> = [];\n private boxes: RingBox[] = [];\n /** Constant deck height of each claim, or undefined when it is not level. */\n private levels: Array<number | undefined> = [];\n\n /**\n * `ring` minus everything claimed so far AT THE SAME HEIGHT; then claim it.\n *\n * `level` is the claim's constant deck height when it has one. Two decks only\n * z-fight when they are coplanar, so only a coplanar claim may take ground\n * away — an elevated box hanging over a ground-level section overlaps it in\n * plan and must still draw its whole floor, or the box loses the part of its\n * deck that shares a footprint with whatever is underneath it. Passing\n * undefined (a surface with no single height) keeps the original\n * clip-against-everything behaviour.\n */\n subtract(ring: Point[], level?: number): Point[][] {\n const closed: [number, number][] = ring.map((p) => [p.x, p.y]);\n if (closed.length < 3) return [];\n closed.push(closed[0]);\n const box = bboxOfRing(ring);\n // Same prune as `paddedFootprint`: a claim that cannot overlap cannot remove\n // anything, and without this the boolean work is quadratic in section count.\n const overlapping = this.rings.filter((_, i) => boxesOverlap(box, this.boxes[i])\n && coplanarLevels(level, this.levels[i]));\n let pieces: Array<[number, number][][]> = [[closed]];\n if (overlapping.length) {\n try {\n const diff = polygonClipping.difference([closed], ...overlapping);\n pieces = diff.map((poly) => poly.map((r) => r.map(([x, y]) => [x, y] as [number, number])));\n } catch {\n pieces = [[closed]]; // a degenerate outline must not drop the section\n }\n }\n this.rings.push([closed]);\n this.boxes.push(box);\n this.levels.push(level);\n const out: Point[][] = [];\n for (const poly of pieces) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push(pts);\n }\n return out;\n }\n}\n\n/**\n * Height of a booth stand, world metres — a partition wall, not a table.\n *\n * Trade-show and exhibition charts are entirely booths (267 across the two\n * shipped templates), and before this they drew nothing at all: the whole venue\n * rendered as empty floor. A booth is a sellable unit the buyer picks, so it has\n * to be a solid the camera can approach and the pointer can hit.\n */\nconst BOOTH_HEIGHT_M = 2.4;\n\n/** How far a label floats above the thing it names, world metres. */\nconst ZONE_LABEL_LIFT_M = 6;\nconst SECTION_LABEL_LIFT_M = 2.2;\nconst ANNOTATION_LIFT_M = 0.1;\nconst BOOTH_LABEL_LIFT_M = 0.3;\n/** A GA area is flat, so its name sits at head height over the standing floor. */\nconst GA_LABEL_LIFT_M = 1.8;\n/** Row and seat labels ride just above their own deck, like painted markings. */\nconst ROW_LABEL_LIFT_M = 0.9;\nconst SEAT_LABEL_LIFT_M = 0.55;\n/**\n * Seat-label ceiling. Above this the whole set is not emitted — see the note at\n * the emit site. 6,000 covers theatres, halls, cinemas and mid-size arenas; the\n * charts above it are exactly the ones where a per-seat label is least readable.\n */\nconst SEAT_LABEL_MAX = 6_000;\n\n/** Height of a banquet table top, world metres (standard dining height). */\nconst TABLE_HEIGHT_M = 0.75;\n\n/**\n * Height of a décor-image floor plate — a marking on the ground, not an object.\n *\n * Matched to the GA slab (0.15 m) rather than made as thin as it \"should\" be: a\n * 4 cm plate under a 76 m rink is a depth ratio of 5e-4 and Uber Arena's ice\n * z-fought the ground slab into horizontal stripes across the whole surface.\n */\nconst DECOR_PLATE_M = 0.15;\n\n/** Resolve a booth to its closed chart-unit polygon, honouring a custom outline. */\nfunction boothPolygon(booth: Extract<ChartObject, { type: 'booth' }>): Point[] | null {\n // A custom outline wins and ignores rotation, exactly as the 2D renderer does\n // (see BoothObject.points) — L-shaped and island units on an expo floor.\n if (booth.points && booth.points.length >= 3) return booth.points;\n const { center, width, height, rotation } = booth;\n if (!width || !height) return null;\n const a = ((rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: center.x + lx * cos - ly * sin,\n y: center.y + lx * sin + ly * cos,\n }));\n}\n\nfunction buildBooth(\n builder: MeshBuilder,\n booth: Extract<ChartObject, { type: 'booth' }>,\n base: number,\n fill: RGB | null,\n S: typeof STRUCTURE,\n): void {\n const poly = boothPolygon(booth);\n if (!poly) return;\n extrudePrism(builder, poly, undefined, () => base + BOOTH_HEIGHT_M, base,\n tintTop(fill, S.boothTop), S.boothWall, AO);\n}\n\n/** Resolve a table to its closed chart-unit polygon. */\nfunction tablePolygon(table: Extract<ChartObject, { type: 'table' }>): Point[] | null {\n if (table.shape === 'round') {\n const r = table.radius;\n if (!r) return null;\n return ellipsePolygon(table.center.x, table.center.y, r, r, 24);\n }\n const { width, height, rotation, center } = table;\n if (!width || !height) return null;\n const a = ((rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: center.x + lx * cos - ly * sin,\n y: center.y + lx * sin + ly * cos,\n }));\n}\n\nfunction buildTable(\n builder: MeshBuilder,\n table: Extract<ChartObject, { type: 'table' }>,\n base: number,\n fill: RGB | null,\n S: typeof STRUCTURE,\n): void {\n const poly = tablePolygon(table);\n if (!poly) return;\n // Banquet and club charts (77 tables across the shipped templates) drew their\n // chairs floating around nothing. The table is what makes the arrangement read\n // as a table rather than a ring of stray seats.\n extrudePrism(builder, poly, undefined, () => base + TABLE_HEIGHT_M, base,\n tintTop(fill, S.tableTop), S.tableWall, AO);\n}\n\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(\n builder: MeshBuilder,\n shape: Extract<ChartObject, { type: 'shape' }>,\n base: number,\n S: typeof STRUCTURE,\n /** Collects the footprint of each STAGE, for the lighting rig above it. */\n stages?: StageBounds[],\n): void {\n const poly = shapePolygon(shape);\n if (!poly) return;\n const isStage = shape.role === 'stage';\n const height = isStage ? base + 1.0 : base + 0.25;\n if (isStage && stages) {\n let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity;\n for (const pt of poly) {\n if (pt.x < minX) minX = pt.x;\n if (pt.x > maxX) maxX = pt.x;\n if (pt.y < minZ) minZ = pt.y;\n if (pt.y > maxZ) maxZ = pt.y;\n }\n // Chart units -> world metres, matching every other position in the scene.\n stages.push({\n cx: ((minX + maxX) / 2) * M,\n cz: ((minZ + maxZ) / 2) * M,\n y: height,\n halfX: Math.max(((maxX - minX) / 2) * M, 0.5),\n halfZ: Math.max(((maxZ - minZ) / 2) * M, 0.5),\n });\n }\n // An authored `fill` is what the 2D renderer paints this shape (see\n // `renderShape`), and 3D used to discard it for a fixed grey — so an organizer\n // who coloured their stage, dance floor or pitch saw it in the map and lost it\n // the moment they switched to 3D. Muted through `tintTop` like every other\n // authored colour, so it keeps its hue without out-shouting the seating.\n const colTop = tintTop(hexToRgb(shape.fill), 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\n/**\n * Resolve a décor image to its closed chart-unit polygon.\n *\n * `DecorImageObject` is authored as a top-left anchor plus size, rotated about\n * its own CENTRE — the same convention the 2D renderer uses when it offsets the\n * Konva node by half its extent.\n */\nfunction decorPolygon(decor: Extract<ChartObject, { type: 'decorImage' }>): Point[] | null {\n const { x, y, width, height } = decor;\n if (!width || !height) return null;\n const cx = x + width / 2, cy = y + height / 2;\n const a = ((decor.rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: cx + lx * cos - ly * sin,\n y: cy + lx * sin + ly * cos,\n }));\n}\n\n/**\n * The colour a décor plate is painted, sniffed from an SVG data URL.\n *\n * The renderer is deliberately texture-free (three draw calls, no atlas), so a\n * décor image cannot be drawn as an image. What it CAN do is paint the plate in\n * the graphic's own dominant colour, which is the difference between an ice rink\n * reading as ice and reading as a grey slab.\n *\n * The heuristic is narrow on purpose: take the paint of the FIRST covering\n * element (`<rect>` or `<path>`), because an SVG floor graphic is built up\n * background-first — the rink surface, then the lines on top of it. A gradient\n * reference resolves to the mean of that gradient's stops. Anything else (a\n * raster data URL, an external href, an SVG that does not match) returns null\n * and the caller falls back to the neutral décor tone, which is what the object\n * rendered as before this existed.\n */\nfunction decorPlatePaint(href: string | undefined): RGB | null {\n if (!href || !href.startsWith('data:image/svg+xml')) return null;\n let svg = href.slice(href.indexOf(',') + 1);\n if (/;base64/i.test(href.slice(0, href.indexOf(',')))) {\n try { svg = atob(svg); } catch { return null; }\n } else {\n try { svg = decodeURIComponent(svg); } catch { /* already literal */ }\n }\n\n const covering = svg.match(/<(?:rect|path)\\b[^>]*\\bfill=\"([^\"]+)\"/i);\n if (!covering) return null;\n const paint = covering[1].trim();\n if (paint === 'none' || paint === 'transparent') return null;\n\n const gradient = paint.match(/^url\\(#([^)]+)\\)$/);\n if (!gradient) return hexToRgb(paint);\n\n // Average the referenced gradient's stops — a two-stop ice gradient is one\n // colour as far as a flat-shaded plate is concerned.\n const def = svg.match(new RegExp(`<(?:linear|radial)Gradient\\\\b[^>]*\\\\bid=\"${gradient[1]}\"[^>]*>([\\\\s\\\\S]*?)</(?:linear|radial)Gradient>`, 'i'));\n if (!def) return null;\n const stops: RGB[] = [];\n for (const m of def[1].matchAll(/stop-color=\"([^\"]+)\"/gi)) {\n const c = hexToRgb(m[1].trim());\n if (c) stops.push(c);\n }\n if (!stops.length) return null;\n return stops.reduce<RGB>((acc, c, i) => mix(acc, c, 1 / (i + 1)), stops[0]);\n}\n\nfunction buildDecorImage(\n builder: MeshBuilder,\n decor: Extract<ChartObject, { type: 'decorImage' }>,\n base: number,\n S: typeof STRUCTURE,\n): void {\n // A `foreground` décor image draws ABOVE the seats in 2D — a canopy or an\n // overlay graphic. In 3D an opaque plate over the seating would hide the very\n // thing the buyer came to pick from, so it is left undrawn rather than drawn\n // wrongly. No shipped template authors one; the designer can.\n if (decor.layer === 'foreground') return;\n const poly = decorPolygon(decor);\n if (!poly) return;\n // The plate is a floor marking, not an object: it sits a few centimetres proud\n // of the ground slab so it cannot z-fight, and well under the 0.25 m décor\n // shapes that may be standing on it.\n const top = base + DECOR_PLATE_M;\n // Muted into the structure palette exactly like a category fill on a tier cap.\n // Taken literally, the rink's #f4f9ff ice is the BRIGHTEST thing in the venue\n // and the floor out-shouts the seating — against the look brief, which puts\n // saturated colour only on seats. Through `tintTop` it keeps its blue cast and\n // sits in the same tonal range as every other surface.\n let paint = tintTop(decorPlatePaint(decor.href), S.decorTop);\n // No alpha in the solid pass, so authored opacity is honoured by mixing the\n // plate toward the ground it would otherwise be showing through to.\n const opacity = Math.max(0, Math.min(1, decor.opacity ?? 1));\n if (opacity < 1) paint = mix(S.ground, paint, opacity);\n extrudePrism(builder, poly, undefined, () => top, base, paint, mix(paint, S.decorWall, 0.5), AO);\n}\n\nfunction buildGa(builder: MeshBuilder, ga: Extract<ChartObject, { type: 'gaArea' }>, base: number, fill: RGB | null, S: typeof STRUCTURE): void {\n if (!ga.points || ga.points.length < 3) return;\n const colTop = tintTop(fill, S.gaTop);\n extrudePrism(builder, ga.points, ga.holes, () => base + 0.15, base, colTop, S.gaWall, AO);\n}\n\n/** Compute the horizontal chart-unit footprint over everything drawable. */\nfunction chartFootprint(units: FloorUnit[], seats: ExpandedSeat[]): { minX: number; minY: number; maxX: number; maxY: number } {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n const acc = (x: number, y: number): void => {\n if (x < minX) minX = x; if (y < minY) minY = y;\n if (x > maxX) maxX = x; if (y > maxY) maxY = y;\n };\n for (const s of seats) acc(s.x, s.y);\n for (const u of units) {\n for (const o of u.objects) {\n if (o.type === 'section') for (const p of o.outline) acc(p.x, p.y);\n else if (o.type === 'shape' && o.points) for (const p of o.points) acc(p.x, p.y);\n else if (o.type === 'gaArea') for (const p of o.points) acc(p.x, p.y);\n else if (o.type === 'booth') { const b = boothPolygon(o); if (b) for (const p of b) acc(p.x, p.y); }\n else if (o.type === 'table') { const t = tablePolygon(o); if (t) for (const p of t) acc(p.x, p.y); }\n else if (o.type === 'decorImage') { const d = decorPolygon(o); if (d) for (const p of d) acc(p.x, p.y); }\n }\n }\n if (!Number.isFinite(minX)) { minX = -100; minY = -100; maxX = 100; maxY = 100; }\n return { minX, minY, maxX, maxY };\n}\n\nexport interface SceneModelInput {\n doc: ChartDoc;\n seats: ExpandedSeat[];\n /** Optional initial per-seat state (default all available). */\n initialState?: (seat: ExpandedSeat) => import('../palette').SeatState3D;\n}\n\nexport function buildSceneModel(input: SceneModelInput): SceneModel {\n const { doc, seats } = input;\n // Resolved once and threaded down, so no builder reaches for the module-level\n // palette and quietly ignores an organizer's branding.\n const theme = resolveTheme3D(doc.theme);\n const S = theme.structure;\n const units = floorUnits(doc);\n const builder = new MeshBuilder();\n\n // Ground slab sized to the footprint (+ margin), sitting at datum 0.\n const fp = chartFootprint(units, seats);\n const padU = Math.max(60, (fp.maxX - fp.minX + fp.maxY - fp.minY) * 0.06);\n const groundPoly = rectPolygon(fp.minX - padU, fp.minY - padU, (fp.maxX - fp.minX) + padU * 2, (fp.maxY - fp.minY) + padU * 2);\n extrudePrism(builder, groundPoly, undefined, () => 0, -0.4, S.ground, S.ground, AO);\n\n // Per-section 2D fill colours (member-category mix), carried onto tier tops.\n const sectionFills = resolveSectionFills(doc, seats);\n const catColor = new Map<string, string>();\n for (const c of doc.categories ?? []) catColor.set(c.key, c.color);\n const stageBounds: StageBounds[] = [];\n\n // ONE seating surface per section, resolved from the same model layout.ts uses\n // for eye heights. Consumed below by the tier caps and by the seat instances.\n const surfaces = buildVenueSurfaces(units, seats);\n /** Owning floor per seat, for per-instance floor isolation. */\n const seatFloor = new Float32Array(seats.length);\n\n // Coplanar sections must not overlap (see ClaimedArea). One claim per floor:\n // sections on different floors sit at different heights and cannot z-fight.\n for (let unitIndex = 0; unitIndex < units.length; unitIndex++) {\n const unit = units[unitIndex];\n builder.setFloor(unitIndex);\n const claimed = new ClaimedArea();\n const siblings = unit.objects.filter((o): o is SectionObject => o.type === 'section' && !!o.outline && o.outline.length >= 3);\n for (const o of unit.objects) {\n if (o.type === 'section') buildTier(builder, o, unit, sectionFill(o, sectionFills), surfaces.bySection.get(o.id), claimed, siblings, S);\n else if (o.type === 'shape') buildShape(builder, o, unit.baseHeightM, S, stageBounds);\n else if (o.type === 'gaArea') buildGa(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'booth') buildBooth(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'table') buildTable(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'decorImage') buildDecorImage(builder, o, unit.baseHeightM, S);\n }\n }\n\n const focal = doc.focalPoint ?? { x: (fp.minX + fp.maxX) / 2, y: (fp.minY + fp.maxY) / 2 };\n\n // NOTE — per-row stepped treads (./rowSteps.ts) are DISABLED pending the\n // unified-surface rework. See docs/handover-2026-07-25-3d-surface-rework.md.\n //\n // They layered a second surface on top of the section prism, and the two could\n // not be kept in agreement across chart shapes: treads became large flat plates\n // that swallowed the stepping, sliver triangles appeared where seat ordering\n // failed, and seat dots were sliced by their own tread. The fix is one surface\n // function per section consumed by cap + steps + dots, not a second layer.\n // The module is kept (with its measurements in the doc comments) as input to\n // that rework; re-enabling it as-is is not the intended path.\n\n const solids = mergeMeshData([builder.build()]);\n const seatData: SeatInstanceData = buildSeatInstances(seats, input.initialState, surfaces, seatFloor, catColor);\n\n // --- Zones -----------------------------------------------------------------\n // Resolved from the SEATS, not the section outlines: a zone's meaning to a\n // buyer is the seats in it, and framing on the outlines would include the\n // aisles and margins a section is drawn with.\n const zoneDefs = doc.zones ?? [];\n const zones: SceneZone[] = [];\n if (zoneDefs.length) {\n const sectionZone = new Map<string, string>();\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'section' && o.zone) sectionZone.set(o.id, o.zone);\n }\n }\n interface Acc { n: number; minX: number; minY: number; maxX: number; maxY: number; sumY: number }\n const acc = new Map<string, Acc>();\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n // Prefer the seat's own resolved zone; fall back to its owning section's.\n const owner = surfaces.seatOwner[i];\n const zid = s.zoneId ?? (owner ? sectionZone.get(owner) : undefined);\n if (!zid) continue;\n let a = acc.get(zid);\n if (!a) { a = { n: 0, minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity, sumY: 0 }; acc.set(zid, a); }\n a.n++;\n if (s.x < a.minX) a.minX = s.x;\n if (s.y < a.minY) a.minY = s.y;\n if (s.x > a.maxX) a.maxX = s.x;\n if (s.y > a.maxY) a.maxY = s.y;\n a.sumY += surfaces.seatDeckY(i);\n }\n for (const z of zoneDefs) {\n const a = acc.get(z.id);\n const sectionIds: string[] = [];\n for (const [secId, zid] of sectionZone) if (zid === z.id) sectionIds.push(secId);\n if (!a || a.n === 0) {\n // A zone with no seats still exists (an empty hall, a zone of GA areas);\n // report it rather than dropping it, so navigation can show it as empty.\n zones.push({\n id: z.id, label: z.label, color: hexToRgb(z.color), sectionIds, seatCount: 0,\n center: [0, 0, 0], radius: 0,\n focalWorld: [(z.focalPoint ?? focal).x * M, 1.5, (z.focalPoint ?? focal).y * M],\n });\n continue;\n }\n zones.push({\n id: z.id,\n label: z.label,\n color: hexToRgb(z.color),\n sectionIds,\n seatCount: a.n,\n center: [((a.minX + a.maxX) / 2) * M, a.sumY / a.n, ((a.minY + a.maxY) / 2) * M],\n radius: 0.5 * Math.hypot((a.maxX - a.minX) * M, (a.maxY - a.minY) * M) || 1,\n // A zone may face its own point (`ZoneDef.focalPoint`); the documented\n // fallback is the floor/chart focal.\n focalWorld: [(z.focalPoint ?? focal).x * M, 1.5, (z.focalPoint ?? focal).y * M],\n });\n }\n }\n\n // --- Labels ----------------------------------------------------------------\n const labels: SceneLabel[] = [];\n const sections: SceneSection[] = [];\n for (const z of zones) {\n if (z.seatCount === 0) continue;\n // Zone labels float above the seating they name, so they read as belonging\n // to the whole block rather than to whichever seat is under them.\n labels.push({\n id: `zone:${z.id}`,\n kind: 'zone',\n text: z.label,\n anchor: [z.center[0], z.center[1] + ZONE_LABEL_LIFT_M, z.center[2]],\n color: doc.zones?.find((d) => d.id === z.id)?.color,\n });\n }\n {\n // A section is named over its own SEATS, at their mean deck height. Its\n // outline centroid would drift into the aisles a concave section wraps, and\n // on a raked tier the label would sit at the wrong height entirely.\n const acc = new Map<string, { n: number; x: number; y: number; deck: number }>();\n // Seat extent per section, for the camera fit `focusSection` needs.\n const ext = new Map<string, { minX: number; maxX: number; minY: number; maxY: number }>();\n for (let i = 0; i < seats.length; i++) {\n const owner = surfaces.seatOwner[i];\n if (!owner) continue;\n let a = acc.get(owner);\n if (!a) { a = { n: 0, x: 0, y: 0, deck: 0 }; acc.set(owner, a); }\n a.n++; a.x += seats[i].x; a.y += seats[i].y; a.deck += surfaces.seatDeckY(i);\n let e = ext.get(owner);\n if (!e) { e = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity }; ext.set(owner, e); }\n if (seats[i].x < e.minX) e.minX = seats[i].x;\n if (seats[i].x > e.maxX) e.maxX = seats[i].x;\n if (seats[i].y < e.minY) e.minY = seats[i].y;\n if (seats[i].y > e.maxY) e.maxY = seats[i].y;\n }\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type !== 'section') continue;\n const a = acc.get(o.id);\n if (!a || a.n === 0) continue;\n const e = ext.get(o.id)!;\n const centre: [number, number, number] = [(a.x / a.n) * M, a.deck / a.n, (a.y / a.n) * M];\n sections.push({\n id: o.id,\n label: o.displayLabel || o.label || o.id,\n seatCount: a.n,\n center: centre,\n // Half-diagonal of the seat extent — the same fit the zones use.\n radius: Math.max(1, Math.hypot(e.maxX - e.minX, e.maxY - e.minY) * 0.5 * M),\n // A section faces its floor's focal, which is what the camera should\n // look along so the seats present their fronts.\n focalWorld: [unit.focal.x * M, unit.baseHeightM + 1.5, unit.focal.y * M],\n });\n labels.push({\n id: `section:${o.id}`,\n kind: 'section',\n // The buyer-facing name wins over the technical one, as it does in 2D.\n text: o.displayLabel || o.label || o.id,\n anchor: [centre[0], centre[1] + SECTION_LABEL_LIFT_M, centre[2]],\n });\n }\n }\n }\n // --- GA areas, rows and seats ----------------------------------------------\n {\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type !== 'gaArea') continue;\n const c = centroidOf(o.points);\n if (!c) continue;\n // A GA area's capacity IS its buyer-facing fact — \"Standing Floor\" alone\n // does not say whether 40 or 4,000 people fit in it, and 2D shows the\n // number. The label carries both, as one line.\n const name = o.displayLabel || o.label || o.id;\n labels.push({\n id: `ga:${o.id}`,\n kind: 'ga',\n text: o.capacity > 0 ? `${name} · ${o.capacity.toLocaleString()}` : name,\n anchor: [c.x * M, unit.baseHeightM + GA_LABEL_LIFT_M, c.y * M],\n });\n }\n }\n\n // Rows are labelled at the END of the row, where a real venue paints them —\n // on the aisle, not over the middle of the seating. Anchored to the outermost\n // seat so the label tracks the row's own rake instead of a flat guess.\n const rowEnds = new Map<string, { seat: ExpandedSeat; index: number; far: number }>();\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n const far = Math.hypot(s.x - focal.x, s.y - focal.y);\n const cur = rowEnds.get(s.rowId);\n // \"End\" = furthest along the row from its own midpoint; approximated by\n // distance from the venue focal, which for a row is one of its two ends.\n if (!cur || far > cur.far) rowEnds.set(s.rowId, { seat: s, index: i, far });\n }\n for (const [rowId, end] of rowEnds) {\n // A seat's public label is `${rowLabel}-${seatNumber}`, so the row's name\n // is everything before the LAST separator — a row legitimately called\n // \"A-1\" in a segmented plan must not be truncated to \"A\".\n const cut = end.seat.label ? end.seat.label.lastIndexOf('-') : -1;\n const text = cut > 0 ? end.seat.label.slice(0, cut) : rowId;\n if (!text) continue;\n labels.push({\n id: `row:${rowId}`,\n kind: 'row',\n text,\n anchor: [end.seat.x * M, surfaces.seatDeckY(end.index) + ROW_LABEL_LIFT_M, end.seat.y * M],\n });\n }\n\n // Seat labels are the densest rung by far, and unlike every other kind their\n // count is the seat count. On a 52,000-seat ground that is 52,000 DOM-backed\n // labels to hold and project, for text that is only legible when a handful\n // are on screen. So they are emitted for charts where the whole set stays\n // cheap, and above that a seat identifies itself the way it already does on\n // every surface: by being tapped, which raises the seat's own card.\n if (seats.length <= SEAT_LABEL_MAX) {\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n const text = s.displayLabel || s.label;\n if (!text) continue;\n labels.push({\n id: `seat:${s.id}`,\n kind: 'seat',\n text,\n anchor: [s.x * M, surfaces.seatDeckY(i) + SEAT_LABEL_LIFT_M, s.y * M],\n });\n }\n }\n }\n\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'text') {\n // Authored wayfinding sits just above the floor it annotates — a door or\n // aisle name belongs to the ground, not to the air above it.\n if (!o.text) continue;\n labels.push({\n id: `text:${o.id}`,\n kind: 'annotation',\n text: o.text,\n anchor: [o.position.x * M, unit.baseHeightM + ANNOTATION_LIFT_M, o.position.y * M],\n color: o.color,\n rotation: o.rotation,\n });\n } else if (o.type === 'booth') {\n // A booth is a sellable unit the buyer picks by NAME, so it is labelled\n // on top of its own stand rather than left as an anonymous box.\n const poly = boothPolygon(o);\n if (!poly) continue;\n const c = centroidOf(poly);\n if (!c) continue;\n labels.push({\n id: `booth:${o.id}`,\n kind: 'booth',\n text: o.displayLabel || o.label || o.id,\n anchor: [c.x * M, unit.baseHeightM + BOOTH_HEIGHT_M + BOOTH_LABEL_LIFT_M, c.y * M],\n });\n }\n }\n }\n\n // --- Floors ----------------------------------------------------------------\n const floors: SceneFloor[] = [];\n {\n const sectionFloor = new Map<string, number>();\n for (let ui = 0; ui < units.length; ui++) {\n for (const o of units[ui].objects) if (o.type === 'section') sectionFloor.set(o.id, ui);\n }\n const acc = units.map(() => ({ n: 0, minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity, sumY: 0 }));\n for (let i = 0; i < seats.length; i++) {\n const owner = surfaces.seatOwner[i];\n const ui = owner !== null ? sectionFloor.get(owner) : undefined;\n if (ui === undefined) continue;\n const a = acc[ui];\n const s = seats[i];\n a.n++;\n if (s.x < a.minX) a.minX = s.x;\n if (s.y < a.minY) a.minY = s.y;\n if (s.x > a.maxX) a.maxX = s.x;\n if (s.y > a.maxY) a.maxY = s.y;\n a.sumY += surfaces.seatDeckY(i);\n seatFloor[i] = ui;\n }\n for (let ui = 0; ui < units.length; ui++) {\n const a = acc[ui];\n const f = doc.floors?.[ui];\n floors.push({\n index: ui,\n id: f?.id ?? `floor-${ui}`,\n label: f?.name ?? (units.length > 1 ? `Floor ${ui + 1}` : 'Venue'),\n seatCount: a.n,\n center: a.n ? [((a.minX + a.maxX) / 2) * M, a.sumY / a.n, ((a.minY + a.maxY) / 2) * M] : [0, 0, 0],\n radius: a.n ? (0.5 * Math.hypot((a.maxX - a.minX) * M, (a.maxY - a.minY) * M) || 1) : 0,\n });\n }\n }\n\n // --- Seat facing -----------------------------------------------------------\n // Resolved here rather than in `buildSeatInstances` because it needs the thing\n // only this layer knows: which floor a seat belongs to, and therefore what it\n // looks at. Runs after the floors block above, which is what fills `seatFloor`.\n seatData.iYaw = computeSeatYaw(\n seatData.iPosition,\n seats.length,\n (i) => seats[i].rowId,\n (i) => {\n const f = units[seatFloor[i]]?.focal ?? focal;\n return [f.x * M, f.y * M];\n },\n );\n\n const cx = ((fp.minX + fp.maxX) / 2) * M;\n const cz = ((fp.minY + fp.maxY) / 2) * M;\n const radius = 0.5 * Math.hypot((fp.maxX - fp.minX) * M, (fp.maxY - fp.minY) * M) || 10;\n\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 stages: stageBounds,\n sections,\n labels,\n floors,\n };\n}\n","/**\n * A section's RAKE FIELD — the scalar \"depth\" that its seating rises along.\n *\n * ## The defect this exists to fix\n *\n * Both the eye-height model (`layout.ts`) and the 3D seating surface\n * (`view3d/scene/surface.ts`) used to raise a seat by its radial distance from\n * ONE venue focal point. That is only correct when a section's rows are\n * concentric arcs about that focal.\n *\n * Measured on the amphitheatre gallery, whose rows are straight blocks: a single\n * row's seats span a radial-distance range of 105 chart units on the\n * centre-facing blocks and 240 on the side blocks. Since height is a function of\n * that distance, one row's seats landed at DIFFERENT heights — a within-row deck\n * spread of 2.30 m on the side blocks against 1.05 m on the centre-facing ones.\n * On screen the rows visibly tilt, and the tilt differs block to block, which is\n * exactly the left/right asymmetry the owner reported.\n *\n * ## Why a per-section AXIS is not enough\n *\n * The first attempt fitted one straight rake axis per section by PCA over its row\n * centroids, and scored it against the radial model. It did not fix the\n * amphitheatre, because `sec-gall` is a SINGLE section containing six wedge\n * blocks at six different orientations. No single axis — straight or radial —\n * fits them all, so the fit correctly fell back to radial and the tilt survived.\n * Sections containing several blocks are normal, not exotic.\n *\n * ## The model that does work\n *\n * Depth is defined by the section's OWN ROWS. Each row gets one depth value (the\n * mean distance of its seats from the focal), and the field at an arbitrary point\n * is an inverse-distance blend of the nearest rows' depths.\n *\n * The properties that matter:\n *\n * - **Exact on a row.** A point on a row has zero distance to it, so it takes\n * that row's depth exactly and every seat in the row lands at ONE height.\n * The tilt cannot come back, whatever the row's orientation.\n * - **Orientation-free.** Straight blocks, arcs, fans, in-the-round and blocks\n * at six different angles in one section all work, because nothing is fitted\n * to a direction.\n * - **Continuous.** It is a field over the plane, not a per-seat lookup. The 3D\n * cap is tessellated at points BETWEEN seats and needs a value and a gradient\n * there; a per-row lookup would leave the deck undefined between rows and put\n * the seats back off the surface they stand on.\n *\n * That last point is why quantising per row inside `assignEyeHeights` was tried\n * and removed: the renderer cannot follow a step function it has no definition\n * for, and the two models diverging is the trap that made every offset constant\n * chart-specific.\n */\n\nimport type { Point } from './types';\n\n/** A resolved row: its seats as an ordered polyline, plus its depth ordinate. */\nexport interface RakeRowFit {\n /** The row's seats, ordered along the row. */\n readonly pts: readonly Point[];\n /** Mean distance of the row's seats from the focal — what rise is a function of. */\n readonly depth: number;\n}\n\nexport interface SectionRake {\n readonly kind: 'radial' | 'rows';\n /** Depth ordinate at a chart-unit point; rise is a function of this. */\n depthAt(x: number, y: number): number;\n /** Unit gradient direction of `depthAt` — turns a rake angle into a normal. */\n gradientAt(x: number, y: number): readonly [number, number];\n /** Number of rows the field was built from (0 for the radial fallback). */\n readonly rowCount: number;\n /**\n * The section's rows, ORDERED BY DEPTH (front first).\n *\n * The deck is built directly from these — one level ribbon per row — rather\n * than by tessellating `depthAt`. See `deckBands.ts` for why: a section holding\n * several blocks at different heights has a genuine cliff between them, and no\n * continuous field can be both level on every row and trackable by refinement.\n */\n readonly rows: readonly RakeRowFit[];\n /** Index into `rows` of the row nearest a point, or -1 when there are none. */\n nearestRow(x: number, y: number): number;\n}\n\n/** One row's member points, in any order. */\nexport interface RakeRow {\n points: Point[];\n}\n\n/** How many nearest rows contribute to a blended sample. */\nconst BLEND_ROWS = 3;\n\n/**\n * A row further than this multiple of the nearest row's distance contributes\n * nothing. Just above 2 so the two rows bracketing a point midway between them\n * both count (their distances differ by at most the row pitch), while a row on\n * the far side of an aisle or beyond the last row does not.\n */\nconst BLEND_DISTANCE_RATIO = 2.2;\n\n/** Step used for the finite-difference gradient, chart units. */\nconst GRAD_STEP = 0.5;\n\nfunction radialRake(focal: Point): SectionRake {\n return {\n kind: 'radial',\n rowCount: 0,\n rows: [],\n nearestRow: () => -1,\n depthAt: (x, y) => Math.hypot(x - focal.x, y - focal.y),\n gradientAt: (x, y) => {\n const dx = x - focal.x, dy = y - focal.y;\n const d = Math.hypot(dx, dy);\n // At the focal the gradient is undefined; the surface is flat there anyway.\n return d < 1e-9 ? [0, 0] : [dx / d, dy / d];\n },\n };\n}\n\ninterface RowFit {\n /**\n * The row as an ordered POLYLINE through its own seats — not a straight chord.\n *\n * A chord was tried first and broke the arena: its upper-bowl rows are strong\n * arcs, so a seat at the end of a row sits far from the chord between the row's\n * extremes. The blend below then treated that seat as \"between rows\" and gave\n * it a neighbouring row's depth, which put the within-row spread UP from 0.07 m\n * to 2.83 m. Distance must be measured to the row's real shape, so that every\n * one of its seats reads as being exactly on it.\n */\n pts: Point[];\n /** Row centroid, and the radius of a circle about it containing the row. */\n cx: number;\n cy: number;\n radius: number;\n /** The row's depth ordinate — mean distance of its seats from the focal. */\n depth: number;\n}\n\n/**\n * Reduce a row to a segment plus a depth.\n *\n * The direction comes from the row's own extent (the two furthest-apart seats),\n * not from a fitted line: it is exact for a straight row, good enough for a\n * gently curved one, and cannot be thrown off by an outlier the way a\n * least-squares fit through few points can.\n */\nfunction fitRow(points: Point[], focal: Point): RowFit | null {\n if (!points.length) return null;\n let cx = 0, cy = 0, depth = 0;\n for (const p of points) {\n cx += p.x; cy += p.y;\n depth += Math.hypot(p.x - focal.x, p.y - focal.y);\n }\n const n = points.length;\n cx /= n; cy /= n; depth /= n;\n\n // Order the seats along the row so consecutive pairs are real segments. The\n // chord direction is only used for this ORDERING, never for distance, so a\n // curved row orders correctly even though its chord is a poor fit to it.\n let ax = points[0], far = -1;\n for (const p of points) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > far) { far = d; ax = p; }\n }\n let dx = ax.x - cx, dy = ax.y - cy;\n const len = Math.hypot(dx, dy);\n if (len > 1e-9) { dx /= len; dy /= len; } else { dx = 1; dy = 0; }\n const pts = [...points].sort((p, q) =>\n ((p.x - cx) * dx + (p.y - cy) * dy) - ((q.x - cx) * dx + (q.y - cy) * dy));\n\n let radius = 0;\n for (const p of pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > radius) radius = d;\n }\n return { pts, cx, cy, radius, depth };\n}\n\n/** Distance from a point to a row's polyline. */\nfunction distToRow(r: RowFit, x: number, y: number): number {\n const pts = r.pts;\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** How many rows survive the cheap bounding-circle prefilter. */\nconst CANDIDATE_ROWS = 8;\n\n/**\n * Inverse-distance blend of the nearest rows' depths.\n *\n * Only the nearest few rows contribute, so a distant block on the far side of the\n * venue cannot drag a section's near rows. Squared inverse distance makes the\n * nearest row dominate quickly, which keeps the field flat ALONG a row and\n * varying across it — the shape a rake actually has.\n */\nfunction sampleRows(rows: RowFit[], x: number, y: number): number {\n // Prefilter on each row's bounding circle. `centroidDist - radius` is a true\n // LOWER bound on the polyline distance, so this cannot discard a row that would\n // have won — it just keeps the exact polyline test off ~90 % of the rows, which\n // is what makes the field affordable per cap vertex.\n const candD = new Array<number>(CANDIDATE_ROWS).fill(Infinity);\n const candI = new Array<number>(CANDIDATE_ROWS).fill(-1);\n for (let i = 0; i < rows.length; i++) {\n const r = rows[i];\n const lower = Math.hypot(x - r.cx, y - r.cy) - r.radius;\n for (let k = 0; k < CANDIDATE_ROWS; k++) {\n if (lower < candD[k]) {\n for (let j = CANDIDATE_ROWS - 1; j > k; j--) { candD[j] = candD[j - 1]; candI[j] = candI[j - 1]; }\n candD[k] = lower; candI[k] = i;\n break;\n }\n }\n }\n\n // Nearest BLEND_ROWS by insertion (the list is short and this avoids a sort).\n const bestD = new Array<number>(BLEND_ROWS).fill(Infinity);\n const bestI = new Array<number>(BLEND_ROWS).fill(-1);\n for (const i of candI) {\n if (i < 0) continue;\n const d = distToRow(rows[i], x, y);\n for (let k = 0; k < BLEND_ROWS; k++) {\n if (d < bestD[k]) {\n for (let j = BLEND_ROWS - 1; j > k; j--) { bestD[j] = bestD[j - 1]; bestI[j] = bestI[j - 1]; }\n bestD[k] = d; bestI[k] = i;\n break;\n }\n }\n }\n if (bestI[0] < 0) return 0;\n // Exactly on a row: take its depth, so a seat is never blended off its own row.\n if (bestD[0] < 1e-6) return rows[bestI[0]].depth;\n\n // Blend only across rows at COMPARABLE distance. Absolute inverse-distance\n // weighting was tried and produced the cap's worst error: in the empty parts of\n // a section outline — beyond the last row, or the gap between two blocks — the\n // two nearest rows can both be hundreds of units away, so their weights stay\n // comparable and the field goes on interpolating between them across the void.\n // Measured, 79 of the gallery's 97 out-of-tolerance sample points sat more than\n // 100 units from ANY seat, and a cap triangle spanning that region missed the\n // surface by up to 10.84 m.\n //\n // A relative cutoff makes the far field settle to the nearest row's depth, i.e.\n // locally CONSTANT, which a flat triangle interpolates exactly. Inside the\n // seating the nearest rows are all within the cutoff, so blending is unchanged\n // and rows stay level.\n const cutoff = bestD[0] * BLEND_DISTANCE_RATIO;\n let num = 0, den = 0;\n for (let k = 0; k < BLEND_ROWS; k++) {\n const i = bestI[k];\n if (i < 0 || bestD[k] > cutoff) continue;\n const w = 1 / (bestD[k] * bestD[k]);\n num += w * rows[i].depth;\n den += w;\n }\n return den > 0 ? num / den : rows[bestI[0]].depth;\n}\n\nfunction rowsRake(rows: RowFit[]): SectionRake {\n const depthAt = (x: number, y: number): number => sampleRows(rows, x, y);\n const nearestRow = (x: number, y: number): number => {\n let best = Infinity, bestI = -1;\n for (let i = 0; i < rows.length; i++) {\n // Bounding-circle lower bound first; only test the polyline if it can win.\n const r = rows[i];\n if (Math.hypot(x - r.cx, y - r.cy) - r.radius >= best) continue;\n const d = distToRow(r, x, y);\n if (d < best) { best = d; bestI = i; }\n }\n return bestI;\n };\n return {\n kind: 'rows',\n rowCount: rows.length,\n rows,\n nearestRow,\n depthAt,\n gradientAt: (x, y) => {\n // Central differences: the blend has no closed form, and shading only needs\n // the direction. A half-unit step is far below row pitch, so this tracks\n // the field rather than smoothing across rows.\n const gx = (depthAt(x + GRAD_STEP, y) - depthAt(x - GRAD_STEP, y)) / (2 * GRAD_STEP);\n const gy = (depthAt(x, y + GRAD_STEP) - depthAt(x, y - GRAD_STEP)) / (2 * GRAD_STEP);\n const len = Math.hypot(gx, gy);\n return len < 1e-9 ? [0, 0] : [gx / len, gy / len];\n },\n };\n}\n\n/**\n * Build a section's rake field from its rows.\n *\n * `rows` should be the section's member seats grouped by row. With fewer than two\n * usable rows there is nothing to build a field from, so the radial model is kept\n * — which is also how every chart behaved before this, leaving a row-less or\n * single-row section unchanged.\n */\nexport function buildSectionRake(rows: RakeRow[], focal: Point): SectionRake {\n const fits: RowFit[] = [];\n for (const r of rows) {\n const f = fitRow(r.points, focal);\n if (f) fits.push(f);\n }\n if (fits.length < 2) return radialRake(focal);\n // Front-first ordering is the contract `rows` promises, and the deck builder\n // relies on it to pair each ribbon with the riser below it.\n fits.sort((a, b) => a.depth - b.depth);\n return rowsRake(fits);\n}\n","/**\n * The raked deck, built from a section's ROWS instead of from its outline.\n *\n * ## Why the deck is not a tessellated surface any more\n *\n * The previous deck was one cap over the section outline, tessellated against a\n * height field and refined until it tracked it. That works only while the field\n * is smooth and convex. Once height is defined so that every row is LEVEL — the\n * fix for rows visibly tilting, see `core/rake.ts` — the field stops being\n * either:\n *\n * - A section commonly holds several blocks at different heights (the\n * amphitheatre gallery is one section holding six wedges). Any field that is\n * level on each row must JUMP between blocks. Measured, that jump drove the\n * cap's worst error to 11 m against a 0.15 m bound.\n * - Smoothing the jump away puts the tilt straight back. The two requirements\n * are contradictory, so no amount of refinement resolves it.\n *\n * The jump is not an artefact — it is a step between two blocks, which is a\n * WALL. Walls are geometry. So the deck is emitted as geometry that already has\n * the right shape rather than as a surface something has to approximate:\n *\n * - one **ribbon** per row, horizontal, at that row's level;\n * - a **riser** dropping from each ribbon's front edge to the level below;\n * - a flat **landing** under everything, for the parts of the outline that hold\n * no rows (aisles, margins, the gaps between blocks).\n *\n * Every deck vertex therefore lies on a row, where the height is exactly defined.\n * A seat's clearance above its own ribbon is exact by construction instead of\n * measured against a tessellation error, and because every ribbon is horizontal\n * the whole deck shades with a single up normal.\n */\n\nimport type { Point } from '../../core/types';\nimport type { RGB } from '../palette';\nimport polygonClipping from 'polygon-clipping';\nimport { CHART_UNITS_PER_METRE } from '../../core/units';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport earcut from 'earcut';\nimport { MeshBuilder, M } from './geometry';\n\n/** A row ready to draw: its seats as a polyline, and the level it sits at. */\nexport interface BandRow {\n readonly pts: readonly Point[];\n /** World-metre height of this row's ribbon. */\n readonly y: number;\n /**\n * The row's depth ordinate, chart units — its MEAN distance from the focal.\n *\n * Carried through rather than recomputed here. Deriving it from the row's first\n * seat was tried and buried 20 seats: in a section holding several blocks, one\n * row's first seat and the next row's first seat can be in different blocks\n * entirely, so the implied row gap was wrong by a large factor and a ribbon\n * reached forward far enough to cover the row in front of it.\n */\n readonly depth: number;\n /**\n * Which block (stand) this row belongs to.\n *\n * Neighbour and riser resolution must stay inside a block. Across blocks the\n * nearest row is often the one across an aisle, at a different level, so a\n * ribbon would size itself against a stand it is not part of and a riser would\n * step down onto the wrong deck.\n */\n readonly blockId: number;\n /**\n * A ready-made deck FOOTPRINT for this entry, chart units, convex and closed —\n * used instead of the row-ribbon path when the entry is not a row at all.\n *\n * A table is the case this exists for. Its seats ring a centre rather than\n * running across the view, so \"offset the polyline sideways\" describes nothing:\n * whichever way the ribbon is offset it covers an arc of the ring and leaves\n * the rest of it hanging. Measured on the nightclub VIP terrace (14 six-seat\n * round tables), authoring any rake at all left 5 seats off-deck at 1°, 18 at\n * 5° and 24 at 10°. The count barely moves with the rake because the fault is\n * in the deck's PLAN SHAPE, not in its height — a taller step just exposes the\n * ground that was already missing.\n *\n * The patch is the convex hull of the entry's seats grown outward by the same\n * clearance a ribbon carries ({@link MIN_REACH_U}), so a table's deck reaches\n * past its outermost seat CENTRE by a full seat-dot allowance exactly as a\n * ribbon does past its end seat. It is supplied by the surface builder, which\n * is where the authored objects are known — deriving \"is this a table\" by\n * guessing at seat geometry here would re-answer a question the chart already\n * answers.\n */\n readonly patch?: readonly Point[];\n}\n\n/**\n * How far a ribbon reaches FORWARD, as a fraction of the gap to the row in front.\n *\n * Deliberately past the halfway line. A ribbon is built by offsetting its own\n * row's polyline, and the ribbon in front is built by offsetting a different\n * polyline with a different vertex count, so their edges do not land on exactly\n * the same curve. Meeting them at 0.5 each would leave hairline gaps showing the\n * landing through the deck. Overlapping instead is free: the ribbon behind is\n * HIGHER, so it simply covers the seam, and the riser closes the vertical face.\n */\nconst FRONT_REACH = 0.58;\n\n/**\n * Smallest step worth drawing a riser for, world metres.\n *\n * A 1e-4 threshold was tried and emitted 142 needle triangles with aspect ratios\n * to 17,776: risers 5 m long and 0.3 mm tall, where two rows resolved to almost\n * the same level. Such a step is invisible, and the ribbons already overlap\n * enough to close the seam without it. 1 cm is below anything a viewer can see\n * and far above the degenerate range.\n */\nconst MIN_RISER_M = 0.01;\n\n/**\n * Smallest half-width a ribbon may have, chart units.\n *\n * A seat's dot has a real world radius, so a ribbon narrower than that leaves the\n * dot's rim hanging over the aisle beside it — the same defect the deck padding\n * (`outsetRing`) exists to prevent on the cap path, and it is the same 1.5x\n * margin here. Two orchestra seats were still failing on ribbon width alone\n * before this floor; row pitch happened to be barely over a dot diameter there.\n */\nexport const MIN_REACH_U = SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE;\n\n/** How far a ribbon reaches BACK, as a fraction of the gap to the row behind. */\nconst BACK_REACH = 0.5;\n\n/**\n * How far a ribbon extends past its end seats, as a fraction of the SEAT spacing\n * along the row — not of the row gap.\n *\n * The row gap was tried and buried 23 seats. A section can hold several blocks\n * side by side (the amphitheatre gallery holds six), and the aisle between two\n * blocks is often narrower than a row gap, so a ribbon extended by a row gap\n * reached over its neighbour's seats — which sit at a DIFFERENT level, so the\n * overhanging ribbon painted over them. Seat spacing is the right measure: it is\n * exactly enough to carry the end seat's own dot and cannot cross an aisle.\n */\nconst END_REACH = 0.6;\n\n/**\n * Outward normals along a polyline, pointing in the direction of increasing\n * depth (away from `focal`).\n *\n * Taken from the row's own local direction rather than from the height field's\n * gradient: the field is a step function now, so its gradient is zero almost\n * everywhere and undefined on the steps.\n */\nfunction rowNormals(pts: readonly Point[], focal: Point): Array<readonly [number, number]> {\n const n = pts.length;\n const raw: Array<readonly [number, number]> = [];\n for (let i = 0; i < n; i++) {\n // Central difference gives a smooth normal along a curved row; the ends fall\n // back to their single adjacent segment.\n const a = pts[Math.max(0, i - 1)];\n const b = pts[Math.min(n - 1, i + 1)];\n let dx = b.x - a.x, dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-9) { raw.push([0, 0]); continue; }\n dx /= len; dy /= len;\n raw.push([-dy, dx]);\n }\n\n // Orient the WHOLE ROW at once, from the sum of the per-vertex tests, rather\n // than flipping each vertex on its own.\n //\n // Per-vertex orientation was tried and left a hole in the deck. The test is\n // \"does this normal point away from the focal\", and its value passes through\n // zero when a row runs radially — straight at the stage rather than across it.\n // The amphitheatre orchestra has such rows, and there the sign flipped between\n // two adjacent vertices, so the quad between them was built with its two ends\n // offset in OPPOSITE directions: a bow-tie that covers neither side properly.\n // One seat's dot rim ended up over the void.\n //\n // A row is a single object with one front and one back, so the decision belongs\n // to the row. Summing is also the stable form of the same test: it is dominated\n // by the vertices where the answer is unambiguous.\n let vote = 0;\n for (let i = 0; i < n; i++) {\n vote += (pts[i].x - focal.x) * raw[i][0] + (pts[i].y - focal.y) * raw[i][1];\n }\n const flip = vote < 0;\n return flip ? raw.map(([x, y]) => [-x, -y] as const) : raw;\n}\n\n/** Extend a polyline past both ends along its end tangents by `by` chart units. */\nfunction extendEnds(pts: readonly Point[], by: number): Point[] {\n if (pts.length < 2 || by <= 0) return [...pts];\n const out = [...pts];\n const dir = (p: Point, q: Point): Point => {\n const dx = q.x - p.x, dy = q.y - p.y;\n const len = Math.hypot(dx, dy) || 1;\n return { x: dx / len, y: dy / len };\n };\n const head = dir(out[1], out[0]);\n const tail = dir(out[out.length - 2], out[out.length - 1]);\n out.unshift({ x: out[0].x + head.x * by, y: out[0].y + head.y * by });\n out.push({\n x: out[out.length - 1].x + tail.x * by,\n y: out[out.length - 1].y + tail.y * by,\n });\n return out;\n}\n\n/**\n * Angular step used when rounding a patch's corners, radians (~15°).\n *\n * The offset of a convex hull by a fixed distance is that hull swept by a DISC,\n * so every corner is a circular arc. Sampling it is the only approximation in\n * the patch, and it is taken on the OUTSIDE (see `CORNER_SEGMENTS` below) so the\n * sampled polygon still contains the true offset rather than cutting inside it.\n */\nconst CORNER_STEP_RAD = Math.PI / 12;\n\n/**\n * Convex hull of a point set (Andrew's monotone chain), counter-clockwise.\n *\n * Returns fewer than 3 points when the input is a point or a segment; callers\n * fall back to a circle there, which is the correct offset of both.\n */\nfunction convexHull(pts: readonly Point[]): Point[] {\n if (pts.length < 3) return [...pts];\n const s = [...pts].sort((a, b) => (a.x - b.x) || (a.y - b.y));\n const cross = (o: Point, a: Point, b: Point): number =>\n (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);\n const half = (src: readonly Point[]): Point[] => {\n const out: Point[] = [];\n for (const p of src) {\n while (out.length >= 2 && cross(out[out.length - 2], out[out.length - 1], p) <= 0) out.pop();\n out.push(p);\n }\n out.pop();\n return out;\n };\n const hull = [...half(s), ...half([...s].reverse())];\n return hull.length >= 3 ? hull : [...pts];\n}\n\n/**\n * The deck footprint a cluster of seats needs: their convex hull grown outward\n * by `pad` chart units, as a closed counter-clockwise ring.\n *\n * Exact rather than approximate, and that matters: the property the caller is\n * buying is \"every seat centre lies at least `pad` inside this ring\", which is\n * what makes a table's deck carry its own seat dots. A hull offset is the\n * Minkowski sum of the hull with a disc of radius `pad`, and that sum contains\n * every seat's own disc by construction. Sampling the corner arcs is the one\n * place accuracy could leak, so the samples are placed on the CIRCUMSCRIBED\n * radius — the sampled polygon then contains the true arc instead of chording\n * across it, and the guarantee survives the discretisation.\n */\nexport function seatClusterPatch(pts: readonly Point[], pad: number): Point[] {\n if (!pts.length || !(pad > 0)) return [];\n const hull = convexHull(pts);\n // Round a corner from one outward normal to the next, on the circumscribed\n // radius so the chord between samples still clears `pad`.\n const arc = (v: Point, from: number, to: number, out: Point[]): void => {\n let sweep = to - from;\n while (sweep < 0) sweep += Math.PI * 2;\n while (sweep > Math.PI * 2) sweep -= Math.PI * 2;\n const steps = Math.max(1, Math.ceil(sweep / CORNER_STEP_RAD));\n const r = pad / Math.cos(sweep / steps / 2);\n for (let k = 0; k <= steps; k++) {\n const a = from + (sweep * k) / steps;\n out.push({ x: v.x + Math.cos(a) * r, y: v.y + Math.sin(a) * r });\n }\n };\n\n if (hull.length < 3) {\n // A single seat, or two seats (a collinear \"hull\"). The bounding circle grown\n // by `pad` covers the true offset in both cases — it over-covers a segment\n // slightly, which is harmless at the scale of one or two chairs and avoids a\n // second geometric path for a case a real table never hits.\n let cx = 0, cy = 0;\n for (const p of hull) { cx += p.x; cy += p.y; }\n cx /= hull.length || 1; cy /= hull.length || 1;\n let far = 0;\n for (const p of hull) far = Math.max(far, Math.hypot(p.x - cx, p.y - cy));\n const out: Point[] = [];\n const steps = Math.max(3, Math.ceil((Math.PI * 2) / CORNER_STEP_RAD));\n const r = (far + pad) / Math.cos(Math.PI / steps);\n for (let k = 0; k < steps; k++) {\n const a = (k / steps) * Math.PI * 2;\n out.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r });\n }\n return out;\n }\n\n const n = hull.length;\n // Outward normal of the edge leaving vertex i, for a CCW hull.\n const edgeAngle: number[] = [];\n for (let i = 0; i < n; i++) {\n const a = hull[i], b = hull[(i + 1) % n];\n edgeAngle.push(Math.atan2(-(b.x - a.x), b.y - a.y));\n }\n const out: Point[] = [];\n for (let i = 0; i < n; i++) {\n // At each vertex sweep from the previous edge's normal to this edge's, then\n // walk this edge offset outward. Consecutive vertices' runs join along the\n // shared edge, so the concatenation closes into one ring.\n arc(hull[i], edgeAngle[(i + n - 1) % n], edgeAngle[i], out);\n }\n return dedupeAdjacent(out);\n}\n\n/** Perpendicular distance from a point to a row's polyline, chart units. */\nfunction distToPolyline(pts: readonly Point[], x: number, y: number): number {\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** A row's SPATIAL neighbours: how wide its ribbon is, and what it steps down to. */\ninterface Neighbourhood {\n /** Perpendicular distance to the nearest other row, chart units. */\n pitch: number;\n /** World-metre level of the nearest row in FRONT, or null if this row is first. */\n belowY: number | null;\n}\n\n/**\n * Resolve each row's neighbours by POSITION, not by order in the array.\n *\n * `rows` arrives sorted by depth across the whole section, and a section\n * routinely holds several blocks. Two consecutive entries in that ordering are\n * usually the same row index in two DIFFERENT blocks, at almost the same depth —\n * so treating them as neighbours gave a row pitch of nearly zero. Measured, that\n * produced ribbons a couple of centimetres wide (so seat rims hung off the deck\n * in 394 of 400 samples) and risers 5 m long by 0.3 mm tall.\n *\n * The spatially nearest row is the real neighbour whatever block it is in, and\n * the nearest row that is closer to the focal is the one this ribbon steps down\n * onto.\n */\nexport function rowNeighbourhoods(rows: readonly BandRow[]): Neighbourhood[] {\n // Bounding circle per row, so the exact polyline test is skipped for rows that\n // cannot possibly be nearest. This is O(rows^2) at heart and a big venue has\n // thousands of rows in a section.\n const bounds = rows.map((r) => {\n let cx = 0, cy = 0;\n for (const p of r.pts) { cx += p.x; cy += p.y; }\n const n = r.pts.length || 1;\n cx /= n; cy /= n;\n let rad = 0;\n for (const p of r.pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > rad) rad = d;\n }\n return { cx, cy, rad };\n });\n // Probe from points ON the row, at a quarter, a half and three quarters along\n // it. The centroid was tried and broke the arena: its rows are strong arcs, and\n // an arc's centroid lies well inside the curve rather than on it, so distances\n // measured from there are not the row pitch at all (arena rims went 0 -> 272\n // off deck). A point on the polyline measures the true perpendicular gap, and\n // three of them keep one odd row end from setting the whole ribbon's width.\n const probesOf = (pts: readonly Point[]): Point[] => {\n const n = pts.length;\n if (n <= 2) return [...pts];\n return [pts[Math.floor(n * 0.25)], pts[Math.floor(n * 0.5)], pts[Math.floor(n * 0.75)]];\n };\n\n const out: Neighbourhood[] = [];\n for (let i = 0; i < rows.length; i++) {\n const probes = probesOf(rows[i].pts);\n const pitches: number[] = [];\n let bestFrontD = Infinity, belowY: number | null = null;\n for (const c of probes) {\n let nearest = Infinity;\n for (let j = 0; j < rows.length; j++) {\n // Neighbours are rows at a DIFFERENT LEVEL, not rows in the same block.\n //\n // \"Same block\" was tried and broke the concert hall: block detection\n // legitimately fragments one stand into several (sec-terr-0's 12 rows\n // resolved into 4), and a fragment's front neighbour then sits in another\n // block and was skipped — so the pitch came from a distant row, the\n // ribbon reached far forward, and it buried the seats of the row in front\n // by 0.20 m.\n //\n // Level is the property that actually distinguishes the two cases: a\n // lateral neighbour across an aisle shares this row's level (it is the\n // same row, split), while the row in front or behind does not.\n if (j === i || Math.abs(rows[j].y - rows[i].y) < 1e-3) continue;\n // Admissible lower bound: the true distance can never be smaller than\n // this, so a row that cannot beat EITHER accumulator can be skipped\n // outright. Guarding on only one of them left the prune almost never\n // firing, which is the whole cost of this function.\n const b = bounds[j];\n const lower = Math.hypot(c.x - b.cx, c.y - b.cy) - b.rad;\n if (lower >= nearest && lower >= bestFrontD) continue;\n const d = distToPolyline(rows[j].pts, c.x, c.y);\n if (d < nearest) nearest = d;\n // \"In front\" = nearer the focal, i.e. a smaller depth ordinate.\n if (rows[j].depth < rows[i].depth && d < bestFrontD) {\n bestFrontD = d;\n belowY = rows[j].y;\n }\n }\n if (Number.isFinite(nearest) && nearest > 0) pitches.push(nearest);\n }\n pitches.sort((a, b) => a - b);\n // Median: robust to one probe landing beside an aisle or a short row.\n const pitch = pitches.length ? pitches[Math.floor(pitches.length / 2)] : 1;\n out.push({ pitch, belowY });\n }\n return out;\n}\n\nexport interface BandColors {\n /** Ribbon (tread) top colour, AO already applied. */\n tread: RGB;\n /** Riser face colour, AO already applied. */\n riser: RGB;\n}\n\n\n/** One row's ribbon: the polyline it is built on, its normals, and its reaches. */\ninterface Ribbon {\n pts: Point[];\n nrm: Array<readonly [number, number]>;\n front: number;\n back: number;\n}\n\n/**\n * Resolve one row's ribbon geometry.\n *\n * Shared by the mesh emitter and the footprint builder so the drawn ribbons and\n * the block outline extruded beneath them are derived from ONE computation. They\n * disagreeing is the same class of bug as the cap and the seats disagreeing.\n */\nfunction ribbonOf(rows: readonly BandRow[], i: number, nbrs: Neighbourhood[], focal: Point): Ribbon | null {\n const row = rows[i];\n // A \"row\" of one seat is a free-standing seat, not a row — it still needs a\n // deck under it, so give it a short segment across the view direction and let\n // the normal ribbon path carry it. Skipping these left their dots hanging\n // over the landing.\n const rowPts = row.pts.length >= 2\n ? [...row.pts]\n : row.pts.length === 1\n ? ((): Point[] => {\n const p = row.pts[0];\n let dx = p.x - focal.x, dy = p.y - focal.y;\n const len = Math.hypot(dx, dy) || 1;\n dx /= len; dy /= len;\n // Across the line of sight, half a pitch each way.\n const h = Math.max(nbrs[i].pitch, 1e-3) * 0.5;\n return [{ x: p.x + dy * h, y: p.y - dx * h }, { x: p.x - dy * h, y: p.y + dx * h }];\n })()\n : [];\n if (rowPts.length < 2) return null;\n\n let seatSpan = 0, spanN = 0;\n for (let k = 1; k < rowPts.length; k++) {\n const d = Math.hypot(rowPts[k].x - rowPts[k - 1].x, rowPts[k].y - rowPts[k - 1].y);\n if (d > 1e-6) { seatSpan += d; spanN++; }\n }\n const spacing = spanN > 0 ? seatSpan / spanN : 0;\n const pts = extendEnds(rowPts, Math.max(spacing * END_REACH, MIN_REACH_U));\n return {\n pts,\n nrm: rowNormals(pts, focal),\n front: Math.max(nbrs[i].pitch * FRONT_REACH, MIN_REACH_U),\n back: Math.max(nbrs[i].pitch * BACK_REACH, MIN_REACH_U),\n };\n}\n\n/** A block: the footprint its ribbons cover, and the level its base sits at. */\nexport type { Neighbourhood };\n\nexport interface DeckFootprint {\n /** Outer ring, chart units. */\n outline: Point[];\n /** Any interior rings (an enclosed gap between rows). */\n holes: Point[][];\n /** World-metre height of this block's lowest ribbon — the top of its base. */\n topY: number;\n}\n\n/**\n * The footprint of each BLOCK in a section, as the union of its ribbons.\n *\n * This replaces extruding the section's whole authored outline under the deck.\n * That outline is drawn generously — it reaches past the last row and across the\n * gaps between blocks — so a single flat plate at the front row's level stuck out\n * around the seating as a large slab, and every block's end read as a square cut\n * through that slab rather than as the edge of a stand.\n *\n * Unioning the ribbons instead yields one polygon per block automatically (the\n * amphitheatre's three sections resolve to 4, 5 and 6 blocks — exactly their\n * wedge counts), each hugging its own seating, and each carrying its own base\n * level rather than sharing the section's. Costs 7–13 ms per section at build\n * time, which is paid once.\n */\n/**\n * Extra width given to a ribbon in the block footprint, chart units (~3 cm).\n *\n * The footprint is the base the ribbons stand on, so a small margin keeps its\n * edge from coinciding exactly with a tread edge and z-fighting it.\n */\nconst FOOTPRINT_MARGIN_U = 1.5;\n\n/**\n * Douglas-Peucker tolerance for a block outline, chart units (~7 mm).\n *\n * The base carries no detail at this scale — the SEATS are carried by the\n * ribbons above it — and long runs of near-collinear vertices are what earcut\n * fans into needle triangles.\n */\nconst FOOTPRINT_TOLERANCE_U = 0.3;\n\n/** Douglas-Peucker on an open point run. */\nfunction simplifyRun(pts: Point[], tol: number): Point[] {\n if (pts.length < 3) return pts;\n const a = pts[0], b = pts[pts.length - 1];\n const dx = b.x - a.x, dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n let worst = -1, worstI = -1;\n for (let i = 1; i < pts.length - 1; i++) {\n const p = pts[i];\n const d = len > 1e-12\n ? Math.abs((p.x - a.x) * dy - (p.y - a.y) * dx) / len\n : Math.hypot(p.x - a.x, p.y - a.y);\n if (d > worst) { worst = d; worstI = i; }\n }\n if (worst <= tol || worstI < 0) return [a, b];\n const left = simplifyRun(pts.slice(0, worstI + 1), tol);\n const right = simplifyRun(pts.slice(worstI), tol);\n return [...left.slice(0, -1), ...right];\n}\n\n/** Simplify a closed ring, keeping it closed. */\nfunction simplifyRing(ring: Point[], tol: number): Point[] {\n if (ring.length < 4) return ring;\n const out = simplifyRun([...ring, ring[0]], tol);\n out.pop();\n return out.length >= 3 ? out : ring;\n}\n\nexport function deckFootprints(rows: readonly BandRow[], focal: Point, shared?: Neighbourhood[]): DeckFootprint[] {\n if (rows.length < 2) return [];\n const nbrs = shared ?? rowNeighbourhoods(rows);\n\n // Group by the block the structure resolver already identified. The first\n // implementation instead recovered blocks by UNIONING every ribbon and reading\n // off the resulting polygons — correct, but it made a boolean solve the\n // question a resolved field already answers, and ribbons overlap each other by\n // design (FRONT_REACH is past the halfway line), which is the worst case for a\n // sweep-line union. Profiled at 48 % of scene build on a 50k venue and 58 % on\n // 99k, growing superlinearly: 1,024 ms -> 5,285 ms for twice the seats.\n const byBlock = new Map<number, number[]>();\n for (let i = 0; i < rows.length; i++) {\n const a = byBlock.get(rows[i].blockId);\n if (a) a.push(i); else byBlock.set(rows[i].blockId, [i]);\n }\n\n const out: DeckFootprint[] = [];\n for (const [, allIndices] of byBlock) {\n // A patch entry (a table) stands on its own base. It is not part of the\n // ribbon stack — including it in the front-to-back trace would drag the\n // block's outline through a ring of seats that has no front or back — and a\n // pedestal per table is what a raked table terrace physically is.\n const indices: number[] = [];\n for (const i of allIndices) {\n const patch = rows[i].patch;\n if (patch && patch.length >= 3) out.push({ outline: [...patch], holes: [], topY: rows[i].y });\n else indices.push(i);\n }\n if (!indices.length) continue;\n // Front to back, so the ring below traces the block's real outline.\n indices.sort((a, b) => rows[a].depth - rows[b].depth);\n const ribbons = indices.map((i) => ribbonOf(rows, i, nbrs, focal));\n const usable = ribbons.filter((r): r is Ribbon => r !== null);\n if (!usable.length) continue;\n\n const frontOf = (r: Ribbon, k: number): Point => ({\n x: r.pts[k].x - r.nrm[k][0] * (r.front + FOOTPRINT_MARGIN_U),\n y: r.pts[k].y - r.nrm[k][1] * (r.front + FOOTPRINT_MARGIN_U),\n });\n const backOf = (r: Ribbon, k: number): Point => ({\n x: r.pts[k].x + r.nrm[k][0] * (r.back + FOOTPRINT_MARGIN_U),\n y: r.pts[k].y + r.nrm[k][1] * (r.back + FOOTPRINT_MARGIN_U),\n });\n\n // Trace the boundary directly: the front row's front edge, down the right\n // ends, the back row's back edge reversed, and up the left ends. O(rows)\n // instead of a boolean over every ribbon.\n const first = usable[0], last = usable[usable.length - 1];\n const ring: Point[] = [];\n for (let k = 0; k < first.pts.length; k++) ring.push(frontOf(first, k));\n for (const r of usable) ring.push(backOf(r, r.pts.length - 1));\n for (let k = last.pts.length - 1; k >= 0; k--) ring.push(backOf(last, k));\n for (let i = usable.length - 1; i >= 0; i--) ring.push(frontOf(usable[i], 0));\n\n const deduped = dedupeAdjacent(ring);\n let topY = Infinity;\n for (const i of indices) if (rows[i].y < topY) topY = rows[i].y;\n if (!Number.isFinite(topY)) continue;\n\n // VERIFY the traced ring before trusting it. Tracing assumes a block's rows\n // stack cleanly front to back; a block that folds or wraps can trace a\n // self-intersecting ring, which still has positive area but covers the wrong\n // ground — on the West End theatre that left seats standing over a base that\n // had moved out from under them. The property that matters is that the base\n // covers its own ribbons, so test exactly that and fall back to the boolean\n // union for the rare block that fails.\n const covers = deduped.length >= 3\n && Math.abs(ringArea(deduped)) > 1e-6\n && usable.every((r) => {\n const mid = r.pts[Math.floor(r.pts.length / 2)];\n return pointInRing(deduped, mid.x, mid.y);\n });\n\n if (covers) {\n out.push({ outline: simplifyRing(deduped, FOOTPRINT_TOLERANCE_U), holes: [], topY });\n continue;\n }\n for (const poly of unionRibbons(usable)) out.push({ outline: poly, holes: [], topY });\n }\n return out;\n}\n\n/**\n * The original boolean union, kept as the fallback for blocks the direct trace\n * cannot describe. Correct but expensive, so it now runs for a handful of blocks\n * rather than for every one.\n */\nfunction unionRibbons(ribbons: readonly Ribbon[]): Point[][] {\n const rings: Array<[number, number][][]> = [];\n for (const r of ribbons) {\n const f: [number, number][] = [];\n const b: [number, number][] = [];\n const rf = r.front + FOOTPRINT_MARGIN_U;\n const rb = r.back + FOOTPRINT_MARGIN_U;\n for (let k = 0; k < r.pts.length; k++) {\n const p = r.pts[k], n = r.nrm[k];\n f.push([p.x - n[0] * rf, p.y - n[1] * rf]);\n b.push([p.x + n[0] * rb, p.y + n[1] * rb]);\n }\n const ring = [...f, ...b.reverse()];\n if (ring.length < 3) continue;\n ring.push(ring[0]);\n rings.push([ring]);\n }\n if (!rings.length) return [];\n try {\n const merged = polygonClipping.union(rings[0], ...rings.slice(1));\n const out: Point[][] = [];\n for (const poly of merged) {\n if (!poly.length || poly[0].length < 4) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n const first = pts[0], last = pts[pts.length - 1];\n if (pts.length > 1 && Math.abs(first.x - last.x) < 1e-9 && Math.abs(first.y - last.y) < 1e-9) pts.pop();\n if (pts.length >= 3) out.push(simplifyRing(pts, FOOTPRINT_TOLERANCE_U));\n }\n return out;\n } catch {\n return [];\n }\n}\n\n/** Shoelace area of a ring. */\nfunction ringArea(ring: readonly Point[]): number {\n let a = 0;\n for (let i = 0, n = ring.length; i < n; i++) {\n const p = ring[i], q = ring[(i + 1) % n];\n a += p.x * q.y - q.x * p.y;\n }\n return a / 2;\n}\n\n/** Drop consecutive duplicates, which the traced ring produces at its corners. */\nfunction dedupeAdjacent(ring: readonly Point[]): Point[] {\n const out: Point[] = [];\n for (const p of ring) {\n const last = out[out.length - 1];\n if (last && Math.hypot(p.x - last.x, p.y - last.y) < 1e-9) continue;\n out.push(p);\n }\n while (out.length > 2 && Math.hypot(out[0].x - out[out.length - 1].x, out[0].y - out[out.length - 1].y) < 1e-9) out.pop();\n return out;\n}\n\n/** Even-odd point-in-ring test. */\nfunction pointInRing(ring: readonly Point[], x: number, y: number): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const a = ring[i], b = ring[j];\n if ((a.y > y) !== (b.y > y) && x < ((b.x - a.x) * (y - a.y)) / (b.y - a.y) + a.x) inside = !inside;\n }\n return inside;\n}\n\n/**\n * Cheap containment test for the clip region, so the boolean is only run for the\n * quads that actually straddle a boundary.\n */\nclass ClipTest {\n private rings: Point[][];\n private minX = Infinity; private minY = Infinity;\n private maxX = -Infinity; private maxY = -Infinity;\n\n constructor(rings: Point[][]) {\n this.rings = rings;\n for (const r of rings) {\n for (const p of r) {\n if (p.x < this.minX) this.minX = p.x;\n if (p.y < this.minY) this.minY = p.y;\n if (p.x > this.maxX) this.maxX = p.x;\n if (p.y > this.maxY) this.maxY = p.y;\n }\n }\n }\n\n /** True when every point lies strictly inside ONE ring of the clip region. */\n containsAll(pts: readonly Point[]): boolean {\n for (const p of pts) {\n if (p.x < this.minX || p.x > this.maxX || p.y < this.minY || p.y > this.maxY) return false;\n }\n // A quad spanning two disjoint rings must go through the boolean, so the\n // test demands a SINGLE ring contain all of it.\n for (const ring of this.rings) {\n let all = true;\n for (const p of pts) {\n if (!pointInRing(ring, p.x, p.y)) { all = false; break; }\n }\n if (all) return true;\n }\n return false;\n }\n}\n\n/**\n * Emit a horizontal CONVEX polygon clipped to `clipRing`, triangulated.\n *\n * Treads are horizontal, so every piece shades with the same up normal whatever\n * shape the clip leaves behind — which is what makes clipping cheap here.\n *\n * Convex is all the callers produce: a ribbon segment is a quad and a table\n * patch is an offset convex hull, so the unclipped fast path can fan from one\n * vertex instead of running a triangulator.\n */\nfunction emitClippedPoly(\n builder: MeshBuilder,\n clipRing: [number, number][][],\n clipTest: ClipTest,\n poly: Point[],\n y: number,\n color: RGB,\n): void {\n if (poly.length < 3) return;\n // FAST PATH: a polygon wholly inside the clip contributes nothing to clip, and\n // the overwhelming majority are — only ribbons at a section's edge straddle it.\n // Running a polygon boolean per ribbon SEGMENT regardless cost ~100,000\n // intersections on a 99k-seat venue and dominated scene build (8.5 s of 11 s).\n if (clipTest.containsAll(poly)) {\n const UPF = [0, 1, 0] as const;\n const a = poly[0];\n for (let i = 1; i + 1 < poly.length; i++) {\n const b = poly[i], c = poly[i + 1];\n builder.tri([a.x * M, y, a.y * M], [b.x * M, y, b.y * M], [c.x * M, y, c.y * M], UPF, color);\n }\n return;\n }\n const ring: [number, number][] = poly.map((p) => [p.x, p.y]);\n ring.push(ring[0]);\n let pieces: ReturnType<typeof polygonClipping.intersection>;\n try {\n pieces = polygonClipping.intersection([ring], clipRing);\n } catch {\n return; // a degenerate quad simply contributes nothing\n }\n const UP = [0, 1, 0] as const;\n for (const poly of pieces) {\n if (!poly.length || poly[0].length < 4) continue;\n const outer = poly[0];\n const flat: number[] = [];\n const pts: Array<[number, number]> = [];\n for (let i = 0; i < outer.length - 1; i++) { // drop the repeated closing point\n flat.push(outer[i][0], outer[i][1]);\n pts.push([outer[i][0], outer[i][1]]);\n }\n if (pts.length < 3) continue;\n const tris = earcut(flat, undefined, 2);\n for (let i = 0; i < tris.length; i += 3) {\n const a = pts[tris[i]], b = pts[tris[i + 1]], c = pts[tris[i + 2]];\n builder.tri(\n [a[0] * M, y, a[1] * M],\n [b[0] * M, y, b[1] * M],\n [c[0] * M, y, c[1] * M],\n UP, color,\n );\n }\n }\n}\n\n/**\n * Emit one section's ribbons and risers.\n *\n * `rows` must be ordered front first (nearest the focal), which is the contract\n * `SectionRake.rows` provides.\n */\nexport function emitDeckBands(\n builder: MeshBuilder,\n rows: readonly BandRow[],\n focal: Point,\n landingY: number,\n colors: BandColors,\n clip?: readonly Point[],\n shared?: Neighbourhood[],\n): void {\n if (rows.length < 2) return;\n // Reused from the footprint pass when the caller has one: this is the O(rows^2)\n // step and computing it twice per section doubled it for nothing.\n const nbrs = shared ?? rowNeighbourhoods(rows);\n const UP = [0, 1, 0] as const;\n const clipRing: [number, number][][] | null = clip && clip.length >= 3\n ? [[...clip.map((p) => [p.x, p.y] as [number, number]), [clip[0].x, clip[0].y]]]\n : null;\n const clipTest = clip && clip.length >= 3 ? new ClipTest([[...clip]]) : null;\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n\n // A table: draw its own footprint, not a ribbon. See `BandRow.patch`.\n if (row.patch && row.patch.length >= 3) {\n const patch = [...row.patch];\n const belowY = nbrs[i].belowY ?? landingY;\n if (clipRing && clipTest) emitClippedPoly(builder, clipRing, clipTest, patch, row.y, colors.tread);\n else {\n const a = patch[0];\n for (let k = 1; k + 1 < patch.length; k++) {\n const b = patch[k], c = patch[k + 1];\n builder.tri([a.x * M, row.y, a.y * M], [b.x * M, row.y, b.y * M], [c.x * M, row.y, c.y * M], UP, colors.tread);\n }\n }\n // A skirt all the way round, not just across the front. A patch is a\n // free-standing pedestal rather than a step in a stack, so every side of\n // it is exposed; leaving the sides open showed the void under the deck\n // from any angle but dead ahead.\n if (row.y > belowY + MIN_RISER_M) {\n let cx = 0, cy = 0;\n for (const p of patch) { cx += p.x; cy += p.y; }\n cx /= patch.length; cy /= patch.length;\n for (let k = 0; k < patch.length; k++) {\n const p = patch[k], q = patch[(k + 1) % patch.length];\n const dx = q.x - p.x, dy = q.y - p.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-6) continue;\n // Outward = the side of the edge the patch centre is NOT on.\n let nx = dy / len, ny = -dx / len;\n if ((p.x - cx) * nx + (p.y - cy) * ny < 0) { nx = -nx; ny = -ny; }\n const rn: readonly [number, number, number] = [nx, 0, ny];\n const pt: [number, number, number] = [p.x * M, row.y, p.y * M];\n const qt: [number, number, number] = [q.x * M, row.y, q.y * M];\n const pb: [number, number, number] = [p.x * M, belowY, p.y * M];\n const qb: [number, number, number] = [q.x * M, belowY, q.y * M];\n builder.tri(pt, qt, qb, rn, colors.riser);\n builder.tri(pt, qb, pb, rn, colors.riser);\n }\n }\n continue;\n }\n\n const rib = ribbonOf(rows, i, nbrs, focal);\n if (!rib) continue;\n const { pts, nrm, front, back } = rib;\n\n // The level below this ribbon: the spatially nearest row in front of it, or\n // the landing when nothing is in front (this row is its block's first).\n const belowY = nbrs[i].belowY ?? landingY;\n\n for (let k = 0; k + 1 < pts.length; k++) {\n const p = pts[k], q = pts[k + 1];\n const np = nrm[k], nq = nrm[k + 1];\n // Skip degenerate input: a duplicated seat position, or a vertex whose\n // normal collapsed. Emitting these produced needle triangles with aspect\n // ratios to 17,776 — meaningless geometry that flat-shades as dark streaks,\n // the exact defect the ring cleanup removed from the cap path.\n if (Math.hypot(q.x - p.x, q.y - p.y) < 1e-6) continue;\n if ((np[0] === 0 && np[1] === 0) || (nq[0] === 0 && nq[1] === 0)) continue;\n\n const pF: [number, number, number] = [(p.x - np[0] * front) * M, row.y, (p.y - np[1] * front) * M];\n const qF: [number, number, number] = [(q.x - nq[0] * front) * M, row.y, (q.y - nq[1] * front) * M];\n const pB: [number, number, number] = [(p.x + np[0] * back) * M, row.y, (p.y + np[1] * back) * M];\n const qB: [number, number, number] = [(q.x + nq[0] * back) * M, row.y, (q.y + nq[1] * back) * M];\n\n // Tread: horizontal, so one shared up normal is exact.\n if (clipRing && clipTest) {\n // A ribbon reaches past its own row on all four sides, and near a section\n // boundary that reach crosses into the NEXT section. Where the neighbour\n // sits lower, the overhang covers its seats: 27 concert-hall seats were\n // buried by 0.20 m under the terrace beside them, which is 0.60 m higher.\n // Clipping to the section's own padded outline keeps a deck inside the\n // section that drew it.\n emitClippedPoly(builder, clipRing, clipTest!, [\n { x: p.x - np[0] * front, y: p.y - np[1] * front },\n { x: q.x - nq[0] * front, y: q.y - nq[1] * front },\n { x: q.x + nq[0] * back, y: q.y + nq[1] * back },\n { x: p.x + np[0] * back, y: p.y + np[1] * back },\n ], row.y, colors.tread);\n } else {\n builder.tri(pF, qF, qB, UP, colors.tread);\n builder.tri(pF, qB, pB, UP, colors.tread);\n }\n\n // 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 seating surface — one function per section, consumed by everything.\n *\n * Before this module the venue had three independently-computed surfaces (tier\n * cap, row treads, seat dots) held apart by hardcoded offsets that had to agree,\n * and never did across chart shapes. Two measured defects came out of that:\n *\n * - The tier cap was a raw earcut triangulation of the section outline with a\n * *radial, nonlinear* height evaluated only at outline vertices. A 90 m chord\n * across a curved bowl is a flat plane through the seating: 23 % of arena\n * seats and 48 % of amphitheatre seats rendered UNDER their own tier cap\n * (worst overshoot 5.54 m).\n * - The cap took each section's front-edge datum from its OUTLINE vertices and\n * the doc focal, while `layout.ts`'s `assignEyeHeights` takes it from the\n * section's SEATS and each seat's own `focalPoint`. Different datum ⇒ a\n * per-section constant disagreement even where tessellation was exact.\n *\n * `deckAt(x, y)` here is the single definition of \"the height of the walkable\n * seating surface\". It mirrors `assignEyeHeights` exactly — same front-edge\n * datum, same drawn-radial-depth rise — so a seat dot and the deck it stands on\n * can no longer be computed from different models. The cap is tessellated\n * against this same function (see `extrudePrism`'s `maxCapError`), so the drawn\n * triangle mesh tracks it rather than chording across it.\n *\n * Pure: no OGL, no DOM. Unit-tested in workers/api/test/view3dSurface.test.ts.\n */\n\nimport type { ChartObject, ExpandedSeat, Point, SectionObject } from '../../core/types';\nimport { pointInPolygonWithHoles } from '../../core/layout';\nimport { buildSectionRake } from '../../core/rake';\nimport { resolveSection, distanceToPolyline, type ResolvedRow } from '../../core/venueStructure';\nimport { CHART_UNITS_PER_METRE, METRES_PER_CHART_UNIT, SEATED_EYE_HEIGHT_M, sectionGeometry } from '../../core/units';\nimport { outsetRing } from './geometry';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport { MIN_REACH_U, seatClusterPatch } from './deckBands';\n\n/** Top of the thin slab drawn for a section with no authored height/rake. Seats\n * on a flat chart rest on this, so it is part of the surface definition rather\n * than a constant duplicated by the seat builder. */\nexport const FLAT_SLAB_TOP_M = 0.05;\n\n/**\n * Subdivision TARGET for the drawn tier cap, metres — how closely it tries to\n * track `deckAt`. Consumed by `sceneModel` when it extrudes a tier.\n *\n * A target, not a guarantee: refinement is uniform per section (the only way to\n * stay watertight — see `emitCapUniform`) and bounded by a triangle budget, so a\n * section whose surface folds sharply stops refining before it reaches this.\n * {@link SEAT_CLEARANCE_M} is what actually protects the seats.\n */\nexport const CAP_MAX_ERROR_M = 0.05;\n\n/**\n * Clearance between a seat dot's origin and the deck it stands on, metres.\n *\n * The cap's error is ONE-SIDED and that is what makes a single clearance work.\n * `deckAt` is a cone — front-edge height plus a linear function of radial\n * distance — which is convex, so a triangle interpolating it lies at or above it\n * everywhere inside the triangle, never below. The cap can therefore only ever\n * float above the true surface, and only a seat needs protecting from it.\n *\n * The size, however, is MEASURED rather than proven. Refinement aims at\n * {@link CAP_MAX_ERROR_M} but is uniform per section and capped by a triangle\n * budget, and the residual peaks at the surface's fold, where a section's flat\n * front plateau turns into its rake. Measured worst cap error across all four\n * harness charts is 13 cm. 15 cm carries that while staying under the ~22 cm\n * seat-dot radius, so it is invisible. THIS is the number that must exceed the\n * cap's real error — the test suite asserts exactly that relationship.\n *\n * The test suite enforces the real invariant directly — zero buried seats on\n * every harness chart — so a regression in either number fails loudly rather\n * than silently eating the margin. If a chart ever needs a tighter cap, the fix\n * is fold-aware refinement (splitting triangles that straddle `frontU`), not a\n * bigger clearance.\n */\nexport const SEAT_CLEARANCE_M = 0.15;\n\n/**\n * How far outside its authored outline a section still owns seats, chart units.\n *\n * Matches the deck padding in `buildTier` exactly (1.5 seat-dot radii), because\n * it answers the same question: the deck is drawn at that padded ring, so a seat\n * standing on drawn deck must belong to the section that drew it. Any smaller and\n * boundary seats are orphaned onto the floor beneath their own tier.\n */\nexport const SEAT_OWNERSHIP_PAD_U = SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE;\n\n/** One section's resolved seating surface. */\nexport interface SectionSurface {\n readonly sectionId: string;\n /** World-metre height of the seating deck at a chart-unit point. */\n deckAt(x: number, y: number): number;\n /**\n * ANALYTIC unit normal of the deck at a chart-unit point, world axes (y up).\n *\n * The drawn cap is a piecewise-linear approximation of `deckAt`, so its FACE\n * normals are discontinuous across every shared edge — measured at 46 % of cap\n * edges breaking >2°, worst 110°, which flat-shades into the dark streaks\n * across a raked deck. Worse, the near-degenerate triangles earcut emits at a\n * densified boundary (aspect ratios to 8.7e6) have numerically meaningless\n * face normals, so they shade as random dark needles.\n *\n * `deckAt` is a cone and therefore differentiable everywhere except its fold,\n * so the true normal is available in closed form and costs nothing. Shading the\n * cap with it instead of the face normal makes the surface read as smooth\n * regardless of tessellation AND makes a sliver's shading harmless — it now\n * matches its neighbours even when its own geometry is degenerate.\n */\n normalAt(x: number, y: number): [number, number, number];\n /** True when the section carries no rake — the deck is a constant plane. */\n readonly flat: boolean;\n /** Bottom of the section prism (floor base), world metres. */\n readonly bottomY: number;\n /**\n * The section's rows with the world-metre LEVEL each one sits at, front first.\n *\n * This is what the deck is actually built from. A row is a level ribbon and\n * consecutive ribbons are joined by a riser, so the drawn surface passes\n * exactly through every row instead of approximating a field between them —\n * and because a ribbon is horizontal, its cap error against the seats it\n * carries is zero by construction rather than by measurement.\n *\n * Empty for a flat section, and for a section with fewer than two rows (which\n * keeps the old smooth-cone path, since there is no row structure to build on).\n */\n readonly rowLevels: readonly {\n readonly pts: readonly Point[];\n readonly y: number;\n readonly depth: number;\n readonly blockId: number;\n /** Deck footprint for an entry that is a TABLE rather than a row — see\n * `BandRow.patch` in deckBands.ts for why a ring of seats cannot be decked\n * by offsetting a polyline. */\n readonly patch?: readonly Point[];\n }[];\n /** The landing height for the parts of the outline that hold no rows. */\n readonly landingY: number;\n}\n\nexport interface VenueSurfaces {\n /** Surface by section object id (NOT logical id — heights are per object). */\n bySection: Map<string, SectionSurface>;\n /** Owning section object id per seat index, or null when a seat sits outside\n * every section (free-standing rows). Parallel to the input seat array. */\n seatOwner: Array<string | null>;\n /** Deck height in world metres for seat index i (falls back to the seat's own\n * resolved eye height when it owns no section). */\n seatDeckY(i: number): number;\n /**\n * Seat pitch in CHART UNITS for seat index i, or undefined when its section\n * has no resolved rows.\n *\n * The spacing a seat actually has to itself — the smaller of its own row's\n * seat spacing and the gap to the neighbouring row. Callers size a seat's\n * marker from this.\n *\n * It is emphatically NOT the distance to the nearest other seat. That measure\n * shrank 80 amphitheatre dots to as little as 0.089 m, every one of them at a\n * WEDGE BOUNDARY: the last seat of one wedge sits 0.212 m from the first seat\n * of the next across an aisle, far closer than the 0.55 m spacing inside either\n * row. Two seats being near each other across an aisle says nothing about how\n * much room either one has.\n */\n seatPitchU(i: number): number | undefined;\n}\n\ninterface Bbox { minX: number; minY: number; maxX: number; maxY: number }\n\nfunction bboxOf(pts: Point[]): Bbox {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const p of pts) {\n if (p.x < minX) minX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.x > maxX) maxX = p.x;\n if (p.y > maxY) maxY = p.y;\n }\n return { minX, minY, maxX, maxY };\n}\n\n/** Even-odd point-in-ring test, for the closed table patches above. */\nfunction pointInRing(ring: readonly Point[], x: number, y: number): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const a = ring[i], b = ring[j];\n if ((a.y > y) !== (b.y > y) && x < ((b.x - a.x) * (y - a.y)) / (b.y - a.y) + a.x) inside = !inside;\n }\n return inside;\n}\n\n/** Hard ceiling on rake rise so a mis-authored or focal-wrapping section can\n * never produce a runaway spike (defect guard, carried over from buildTier). */\nconst MAX_TIER_RISE_M = 25;\n\nexport interface SurfaceUnit {\n objects: ChartObject[];\n focal: Point;\n baseHeightM: number;\n}\n\n/**\n * Resolve every section's seating surface and each seat's owning section.\n *\n * The front-edge datum is taken from the section's member SEATS when it has any\n * (matching `assignEyeHeights`), and only falls back to the outline's nearest\n * vertex for a section with no seats — where nothing can disagree with it.\n */\nexport function buildVenueSurfaces(units: SurfaceUnit[], seats: ExpandedSeat[]): VenueSurfaces {\n const bySection = new Map<string, SectionSurface>();\n const seatOwner = new Array<string | null>(seats.length).fill(null);\n const seatDeck = new Float64Array(seats.length);\n /** Level of the seat's own row, when its section resolved into rows. */\n const seatRowLevel = new Array<number | undefined>(seats.length).fill(undefined);\n /** Pitch of the seat's own row (chart units), when resolved. */\n const seatPitch = new Array<number | undefined>(seats.length).fill(undefined);\n\n // --- Pass 1: ownership + per-section seat front distance -------------------\n interface Acc {\n section: SectionObject;\n unit: SurfaceUnit;\n /** Nearest drawn focal distance among member seats (chart units). */\n frontU: number;\n hasSeats: boolean;\n /** Member seat positions grouped by row — the input the rake axis is fitted from. */\n rows: Map<string, Point[]>;\n /** Indices of this section's member seats, for the structure resolver. */\n seatIndices: number[];\n }\n const acc = new Map<string, Acc>();\n const boxes: Array<{ id: string; box: Bbox; section: SectionObject; unit: SurfaceUnit; outline: Point[] }> = [];\n /**\n * Object ids that expand into a RING of seats rather than a run of them.\n *\n * `expandTable` stamps every chair with `rowId = table.id`, so the structure\n * resolver — which only ever sees seats — groups a whole table into what it\n * calls a row. Geometrically it is nothing of the kind, and the deck builder\n * has to know the difference. Reading it off the authored objects is exact;\n * inferring \"these seats look like a ring\" from the points would be a second\n * opinion about a fact the chart already states.\n */\n const tableRowIds = new Set<string>();\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'table') tableRowIds.add(o.id);\n if (o.type !== 'section' || !o.outline || o.outline.length < 3) continue;\n // Ownership is tested against the PADDED outline — the same ring the deck\n // is actually drawn with (see `buildTier`'s `outsetRing`). Testing the raw\n // outline instead left seats authored just outside it unowned, so they fell\n // to the floor while the drawn deck covered them: measured as 9 buried\n // cinema seats and a 0.20 m drop on the uber arena. A seat resting on drawn\n // deck belongs to the section that drew it.\n const owned = outsetRing(o.outline, SEAT_OWNERSHIP_PAD_U);\n acc.set(o.id, { section: o, unit, frontU: Infinity, hasSeats: false, rows: new Map(), seatIndices: [] });\n boxes.push({ id: o.id, box: bboxOf(owned), section: o, unit, outline: owned });\n }\n }\n\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n for (const b of boxes) {\n // Bbox prefilter keeps the 14k-seat charts cheap; the polygon test only\n // runs for genuine candidates.\n if (s.x < b.box.minX || s.x > b.box.maxX || s.y < b.box.minY || s.y > b.box.maxY) continue;\n if (!pointInPolygonWithHoles({ x: s.x, y: s.y }, b.outline, b.section.holes)) continue;\n seatOwner[i] = b.id;\n const a = acc.get(b.id)!;\n a.hasSeats = true;\n const f = s.focalPoint ?? b.unit.focal;\n const d = Math.hypot(s.x - f.x, s.y - f.y);\n if (d < a.frontU) a.frontU = d;\n // Group by row, exactly as assignEyeHeights does — the rake axis is fitted\n // from the same grouping in both files, or the two models diverge again.\n a.seatIndices.push(i);\n const rowKey = s.rowId || `__seat-${i}`;\n const arr = a.rows.get(rowKey);\n if (arr) arr.push({ x: s.x, y: s.y }); else a.rows.set(rowKey, [{ x: s.x, y: s.y }]);\n break; // first containing section wins, exactly as assignEyeHeights does\n }\n }\n\n // --- Pass 2: build one surface function per section ------------------------\n for (const [id, a] of acc) {\n const geo = sectionGeometry(a.section, { floorBaseHeightM: a.unit.baseHeightM });\n const bottomY = a.unit.baseHeightM;\n const rakeTan = geo.rake > 0 ? Math.tan((geo.rake * Math.PI) / 180) : 0;\n // An authored override wins over inference (see `SectionObject.surfaceKind`).\n // `rakedRows` cannot manufacture relief a section does not carry, so it only\n // prevents a raked section from being flattened, never the reverse.\n const inferredFlat = geo.rake <= 0.01 && geo.height <= bottomY + 0.001;\n const kind = a.section.surfaceKind;\n const flat = kind === 'flat' ? true : kind === 'rakedRows' ? geo.rake > 0.01 ? false : inferredFlat : inferredFlat;\n\n // Resolve the structure FIRST: when it yields row levels, the rake field is\n // never consulted and building it is pure waste. Profiled at 7 % of scene\n // build on a 50k venue — `sampleRows` doing inverse-distance work for a field\n // nothing would read.\n const structure = a.hasSeats\n ? resolveSection(id, seats, a.seatIndices, a.unit.focal)\n : { sectionId: id, rows: [] as ResolvedRow[], blockCount: 0 };\n const needsRake = structure.rows.length < 2;\n const rake = needsRake\n ? buildSectionRake([...a.rows.values()].map((points) => ({ points })), a.unit.focal)\n : null;\n\n let frontU = Infinity;\n if (rake) {\n if (a.hasSeats) {\n for (const pts of a.rows.values()) {\n for (const p of pts) {\n const d = rake.depthAt(p.x, p.y);\n if (d < frontU) frontU = d;\n }\n }\n }\n if (!a.hasSeats || !Number.isFinite(frontU)) {\n frontU = Infinity;\n for (const p of a.section.outline) {\n const d = rake.depthAt(p.x, p.y);\n if (d < frontU) frontU = d;\n }\n }\n }\n const baseFloor = bottomY + FLAT_SLAB_TOP_M;\n // A level deck sits at the section's AUTHORED height, not on the floor.\n //\n // This used to be `bottomY + FLAT_SLAB_TOP_M` outright, which meant asking\n // for `surfaceKind: 'flat'` silently threw the authored `height` away: a\n // 7.4 m loge box and a 2.6 m choir terrace both rendered with every seat at\n // 0.20 m (slab top plus seat clearance). It made `flat` unusable for the\n // exact things it exists for — boxes, suites and terraces, which are genuinely\n // LEVEL platforms — so both curated venues had to fake a shallow rake instead.\n //\n // The datum is now taken from the same expression the raked path uses\n // (`levelFor`/`levelForBlockDepth` below, at zero rise), so \"flat\" and\n // \"raked at 0°\" agree by construction rather than by coincidence. A section\n // with no authored height still lands on `baseFloor` exactly as before, which\n // is why legacy height-less charts do not move: `inferredFlat` only fires when\n // `geo.height <= bottomY + 0.001`, so this max can only bite when a height was\n // explicitly authored alongside `surfaceKind: 'flat'`.\n const flatTop = Math.max(baseFloor, geo.height);\n\n /** Height for a depth ordinate measured from the venue focal (fallback path). */\n const levelFor = (depthU: number): number => {\n const depthM = Math.max(0, depthU - frontU) * METRES_PER_CHART_UNIT;\n const rise = Math.min(depthM * rakeTan, MAX_TIER_RISE_M);\n return Math.max(baseFloor, geo.height + rise);\n };\n\n /** Height for a depth measured back from a BLOCK's own front row. */\n const levelForBlockDepth = (blockDepthU: number): number => {\n const depthM = Math.max(0, blockDepthU) * METRES_PER_CHART_UNIT;\n const rise = Math.min(depthM * rakeTan, MAX_TIER_RISE_M);\n return Math.max(baseFloor, geo.height + rise);\n };\n\n // One level per row, from the resolved structure. THIS is the deck.\n //\n // Depth is measured inside the row's own BLOCK, so every block of a tier\n // starts at the tier's authored height. Measuring it from the venue focal\n // (which is what `rake.depthAt` does, and what this used to do) raised a\n // bowl's side wedges as though they were further back: sec-gall's six blocks\n // are one tier authored at 6.20 m and started at 6.35 to 10.65 m.\n // A table's deck is its own footprint, grown past its outermost seat by the\n // same clearance a ribbon carries past its end seat (MIN_REACH_U). The\n // radius is taken from where the seats ACTUALLY are, not from the table's\n // authored `radius`: `expandTable` seats a round table at `radius + 16`\n // chart units, and per-chair `dx/dy` overrides move individual seats again,\n // so the authored radius understates the ring it has to cover. On the\n // nightclub that is 50 u of real reach against an authored 34.\n const rowLevels = flat\n ? []\n : structure.rows.map((r) => ({\n pts: r.pts,\n y: levelForBlockDepth(r.blockDepth),\n depth: r.blockDepth,\n blockId: r.blockId,\n ...(tableRowIds.has(r.id)\n ? { patch: seatClusterPatch(r.pts, MIN_REACH_U) }\n : {}),\n }));\n const landingY = rowLevels.length ? rowLevels[0].y : flatTop;\n\n // Bounding circles over the rows, so the nearest-row lookup below prunes\n // instead of walking every polyline in the section. A 50k venue has sections\n // with thousands of rows and this is called per cap sample.\n // Bounded over the entry's DRAWN extent — the patch when it has one, since\n // that is the ground it actually covers and a bound taken from the seat ring\n // alone would prune away the deck at the patch's rim.\n const rowBounds = rowLevels.map((r) => {\n const ext = r.patch && r.patch.length >= 3 ? r.patch : r.pts;\n let cx = 0, cy = 0;\n for (const p of ext) { cx += p.x; cy += p.y; }\n const n = ext.length || 1;\n cx /= n; cy /= n;\n let rad = 0;\n for (const p of ext) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > rad) rad = d;\n }\n return { cx, cy, rad };\n });\n\n const deckAt = flat\n ? (): number => flatTop\n : rowLevels.length >= 2\n ? (x: number, y: number): number => {\n // The deck is the level of the row you are standing on. A step\n // function, deliberately: it is the drawn geometry, not an\n // approximation of it, so a seat and its ribbon cannot disagree.\n let best = Infinity, bestY = landingY;\n for (let i = 0; i < rowLevels.length; i++) {\n const b = rowBounds[i];\n // Admissible lower bound — cannot discard a row that would have won.\n if (Math.hypot(x - b.cx, y - b.cy) - b.rad >= best) continue;\n // Over a table, the deck is the table's patch — and a point inside\n // the patch is ON it, at distance zero. Measuring to the seat ring's\n // polyline instead would hand the middle of a table to whichever\n // ROW happened to be nearest, so the deck height and the drawn patch\n // would disagree by a whole step.\n const patch = rowLevels[i].patch;\n const d = patch && patch.length >= 3\n ? (pointInRing(patch, x, y) ? 0 : distanceToPolyline(patch, x, y))\n : distanceToPolyline(rowLevels[i].pts, x, y);\n if (d < best) { best = d; bestY = rowLevels[i].y; }\n }\n return bestY;\n }\n : (x: number, y: number): number => (rake ? levelFor(rake.depthAt(x, y)) : flatTop);\n\n // The gradient of the same expression. Height rises with radial distance\n // only, so the surface tilts purely along the outward radial direction and\n // the slope is exactly `rakeTan` — except on the flat front plateau\n // (d <= frontU), past the runaway clamp, and where the baseFloor floor wins,\n // all of which are locally horizontal.\n const UP: [number, number, number] = [0, 1, 0];\n // Every ribbon and every landing is HORIZONTAL, so the deck's normal is up\n // everywhere and the analytic-normal machinery has nothing left to correct.\n // The only non-horizontal deck surfaces are the risers, and those carry their\n // own normals from the band builder.\n const normalAt = rowLevels.length >= 2 || flat\n ? (): [number, number, number] => UP\n : (x: number, y: number): [number, number, number] => {\n if (!rake) return UP;\n const d = rake.depthAt(x, y);\n if (d <= frontU) return UP;\n const depthM = (d - frontU) * METRES_PER_CHART_UNIT;\n if (depthM * rakeTan >= MAX_TIER_RISE_M) return UP; // clamped: flat again\n if (geo.height + depthM * rakeTan <= baseFloor) return UP; // floored: flat\n const [gx, gy] = rake.gradientAt(x, y);\n if (gx === 0 && gy === 0) return UP;\n const inv = 1 / Math.hypot(rakeTan, 1);\n return [-gx * rakeTan * inv, inv, -gy * rakeTan * inv];\n };\n\n // A seat takes its own row's level DIRECTLY. Searching for the nearest row\n // would give the same answer almost always and the wrong one occasionally\n // (where two blocks interleave), and it would cost a polyline search per seat\n // on a chart that may hold 50,000 of them. Row membership is already known.\n if (!flat) {\n for (const r of structure.rows) {\n const y = levelForBlockDepth(r.blockDepth);\n for (const si of r.seatIndices) seatRowLevel[si] = y;\n }\n }\n\n // Per-row pitch: the median spacing ALONG the row (immune to the aisle, which\n // falls between rows, not inside one) capped by the gap to the nearest other\n // row, so a dot cannot grow into the row in front either.\n for (const r of structure.rows) {\n const gaps: number[] = [];\n for (let k = 1; k < r.pts.length; k++) {\n const d = Math.hypot(r.pts[k].x - r.pts[k - 1].x, r.pts[k].y - r.pts[k - 1].y);\n if (d > 1e-6) gaps.push(d);\n }\n gaps.sort((x, y) => x - y);\n const along = gaps.length ? gaps[Math.floor(gaps.length / 2)] : Infinity;\n\n // Measured against rows in the SAME BLOCK only. A row in a neighbouring\n // block runs BESIDE this one, not in front of it, and near a block boundary\n // it can pass within a hair of the probe: measured across the catalog that\n // collapsed the pitch to 0.04 mm on the opera house and 0.8 mm on the\n // esports arena, which then drove every dot there to the visibility floor.\n // This is the same mistake as sizing a seat against a neighbour across an\n // aisle, one level up.\n let across = Infinity;\n const probe = r.pts[Math.floor(r.pts.length / 2)];\n if (probe) {\n for (const other of structure.rows) {\n if (other === r || other.blockId !== r.blockId) continue;\n const d = distanceToPolyline(other.pts, probe.x, probe.y);\n if (d > 1e-6 && d < across) across = d;\n }\n }\n // Never below half the in-row spacing. Two rows can genuinely be authored\n // on top of each other (chairs around a table cross their neighbours'\n // arcs), and a seat's own row spacing is still real when that happens.\n const pitch = Math.min(along, Math.max(across, along * 0.5));\n if (Number.isFinite(pitch) && pitch > 0) {\n for (const si of r.seatIndices) seatPitch[si] = pitch;\n }\n }\n\n bySection.set(id, { sectionId: id, deckAt, normalAt, flat, bottomY, rowLevels, landingY });\n }\n\n // --- Pass 3: bake each seat's deck height from its owner's surface ---------\n for (let i = 0; i < seats.length; i++) {\n const ownerId = seatOwner[i];\n const s = seats[i];\n if (ownerId) {\n const own = seatRowLevel[i];\n seatDeck[i] = (own ?? bySection.get(ownerId)!.deckAt(s.x, s.y)) + SEAT_CLEARANCE_M;\n continue;\n }\n // Seat outside every section: fall back to its own resolved eye height, the\n // only surface information it carries.\n const eye = s.eyeHeightM;\n seatDeck[i] = Number.isFinite(eye) ? Math.max(0, (eye as number) - SEATED_EYE_HEIGHT_M) : 0;\n }\n\n return {\n bySection,\n seatOwner,\n seatDeckY: (i: number): number => seatDeck[i],\n seatPitchU: (i: number): number | undefined => seatPitch[i],\n };\n}\n","/**\n * The DOM half of venue labels: one absolutely-positioned element per visible\n * label, repositioned from projected 3D anchors as the camera moves.\n *\n * Kept out of `index.ts` so the render loop stays about rendering, and out of\n * `labels.ts` so the anchor/LOD logic stays pure and testable.\n *\n * ## Accessibility\n *\n * These are real DOM text nodes, which is the point. A screen reader can read the\n * venue's structure — zones, sections, booth names — where a GPU-drawn glyph is\n * invisible to it. The overlay itself is `aria-hidden` only for the decorative\n * pointer-events layer; the labels are a live region-free list of static text,\n * announced in document order.\n *\n * Pointer events pass straight through: a label must never eat a seat tap.\n */\n\nimport {\n DENSE_LABEL_BUDGET, cullOverlapping, focusScore, pickDenseLabels, projectToScreen,\n visibleLabelKinds, type SceneLabel,\n} from './labels';\n\n/**\n * Minimum gap before the farther label is dropped, CSS px — wide and short,\n * matching the shape of a line of text rather than a disc around it.\n */\nconst SEPARATION_X_PX = 88;\nconst SEPARATION_Y_PX = 20;\n\n/** Per-kind styling. Sizes are CSS px at a nominal viewport. */\nconst KIND_STYLE: Record<SceneLabel['kind'], { size: number; weight: string; opacity: number }> = {\n zone: { size: 15, weight: '600', opacity: 0.95 },\n section: { size: 12, weight: '500', opacity: 0.88 },\n ga: { size: 12, weight: '500', opacity: 0.88 },\n booth: { size: 11, weight: '500', opacity: 0.85 },\n annotation: { size: 11, weight: '400', opacity: 0.75 },\n // Row and seat identity are quieter than the structure they sit inside: at\n // this range the venue is already understood and the label is a detail, so it\n // must not compete with the seating it is printed over.\n row: { size: 10.5, weight: '600', opacity: 0.8 },\n seat: { size: 9.5, weight: '500', opacity: 0.72 },\n};\n\n/**\n * Per-kind declutter spacing, CSS px. Seat and row labels are short strings sat\n * on a tight grid, so the wide section-name box would throw away almost all of\n * them — a row of seat numbers needs to survive side by side.\n */\nconst DENSE_KINDS = new Set<SceneLabel['kind']>(['row', 'seat']);\n/**\n * Per-kind, because the two are different shapes of text. A seat number is 1–3\n * characters and wants to survive shoulder to shoulder along a row; a row name\n * is a word (\"ORCH-L\") and needs roughly twice the width before the next one is\n * legible. One shared value crowded the row names into each other.\n */\nconst DENSE_SEPARATION: Record<'row' | 'seat', { x: number; y: number }> = {\n row: { x: 62, y: 16 },\n // Widened from 24: at close range seat labels stopped overlapping at all, so\n // the separation had no work left to do and the budget was carrying the whole\n // load. A wider box means the few labels that ARE kept are spread across the\n // seating instead of clustering into one stack.\n seat: { x: 46, y: 18 },\n};\n\nexport interface LabelOverlayOptions {\n /** `ChartTheme.fontFamily`, when the chart authors one. */\n fontFamily?: string;\n /** Ink colour for labels that carry no authored colour. */\n ink?: string;\n}\n\nexport class LabelOverlay {\n private root: HTMLDivElement;\n private nodes = new Map<string, HTMLDivElement>();\n private labels: SceneLabel[] = [];\n private opts: LabelOverlayOptions;\n\n constructor(container: HTMLElement, opts: LabelOverlayOptions = {}) {\n this.opts = opts;\n this.root = document.createElement('div');\n this.root.setAttribute('data-view3d-labels', '');\n const s = this.root.style;\n s.position = 'absolute';\n s.inset = '0';\n // Never intercept a seat tap — the canvas below owns all pointer input.\n s.pointerEvents = 'none';\n s.overflow = 'hidden';\n if (opts.fontFamily) s.fontFamily = opts.fontFamily;\n container.appendChild(this.root);\n }\n\n /**\n * Hide or show the whole overlay without disturbing which labels exist.\n *\n * The in-scene panorama sphere is GL, so it draws BEHIND every DOM label —\n * row and seat labels floated on top of a 360 photo until this existed. The\n * old DOM panorama never needed it because its opaque div covered them.\n *\n * Deliberately not `setLabels([])`: that destroys the nodes and the declutter\n * state, so closing the panorama would rebuild and re-rank the whole overlay\n * and flash. Visibility is a view concern, not a data one.\n */\n setVisible(visible: boolean): void {\n this.root.style.visibility = visible ? '' : 'hidden';\n }\n\n setLabels(labels: SceneLabel[]): void {\n this.labels = labels;\n for (const [id, node] of this.nodes) {\n if (!labels.some((l) => l.id === id)) { node.remove(); this.nodes.delete(id); }\n }\n }\n\n /**\n * Reposition every label for the current camera.\n *\n * `viewProjection` is column-major, as OGL supplies it.\n */\n update(\n viewProjection: ArrayLike<number>,\n width: number,\n height: number,\n cameraDistance: number,\n venueRadius: number,\n /**\n * Camera world position, for ranking the dense rungs by real distance.\n * Optional so callers that only need structure labels need not supply it;\n * without it the dense rungs fall back to ranking by screen centre alone.\n */\n cameraWorld?: readonly [number, number, number],\n ): void {\n if (!this.labels.length) return;\n const kinds = visibleLabelKinds(cameraDistance, venueRadius);\n\n const candidates: Array<{\n label: SceneLabel;\n screen: ReturnType<typeof projectToScreen>;\n focus: number;\n }> = [];\n for (const label of this.labels) {\n if (!kinds.has(label.kind)) continue;\n const screen = projectToScreen(viewProjection, label.anchor, width, height);\n if (!screen.visible) continue;\n const world = cameraWorld\n ? Math.hypot(\n label.anchor[0] - cameraWorld[0],\n label.anchor[1] - cameraWorld[1],\n label.anchor[2] - cameraWorld[2],\n )\n : 1;\n candidates.push({ label, screen, focus: focusScore(screen, world, width, height) });\n }\n\n // Nearest-wins declutter, then paint. Everything not kept is hidden rather\n // than removed, so a small camera move does not thrash the DOM. Dense kinds\n // declutter against their OWN spacing and separately from the structure\n // labels, so a section name never suppresses the seat numbers under it.\n const structure = candidates.filter((c) => !DENSE_KINDS.has(c.label.kind));\n const kept = [\n ...cullOverlapping(structure, SEPARATION_X_PX, SEPARATION_Y_PX),\n // The dense rungs are BUDGETED, not merely deduplicated — see\n // DENSE_LABEL_BUDGET for why the overlap test alone gets worse the closer\n // the camera gets.\n ...(['row', 'seat'] as const).flatMap((kind) => pickDenseLabels(\n candidates.filter((c) => c.label.kind === kind),\n DENSE_SEPARATION[kind].x,\n DENSE_SEPARATION[kind].y,\n DENSE_LABEL_BUDGET[kind],\n )),\n ];\n const keptIds = new Set(kept.map((k) => k.label.id));\n\n for (const { label, screen } of kept) {\n const node = this.nodeFor(label);\n const st = node.style;\n st.display = '';\n st.transform = `translate(-50%, -50%) translate(${screen.x.toFixed(1)}px, ${screen.y.toFixed(1)}px)`;\n }\n for (const [id, node] of this.nodes) {\n if (!keptIds.has(id)) node.style.display = 'none';\n }\n }\n\n private nodeFor(label: SceneLabel): HTMLDivElement {\n let node = this.nodes.get(label.id);\n if (node) 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, plus\n * ONE more instanced draw for the near-field chairs whenever the camera is close\n * enough that any seat qualifies. That mesh is attached and detached from the\n * scene graph rather than drawn empty, so an overview frame still costs exactly\n * the three calls it always did.\n */\n\nimport { Geometry, Mesh, Program, Transform, type OGLRenderingContext } from 'ogl';\nimport { SEAT_STATES, upholsteryTone, type RGB } from '../palette';\nimport { createBackgroundProgram, createChairProgram, createSeatProgram, createSolidProgram } from './materials';\nimport { CHAIR_MAX_INSTANCES } from '../lod';\nimport { buildChairMesh } from './seatChair';\nimport type { SceneModel } from './sceneModel';\nimport { seatOccupantSeed, type DirtyRun } from './seatInstances';\n\n/**\n * Fill an iColor buffer range from the current iState values (state → colour).\n *\n * Reads the THEME's colours, not the module palette, so an organizer's brand\n * selection colour applies to the 3D seats as it already does in the picker.\n */\nfunction writeSeatColors(\n iColor: Float32Array,\n iState: Float32Array,\n start: number,\n count: number,\n states: readonly RGB[],\n /** Per-instance category colours; an AVAILABLE seat wears its own. */\n iCategory?: Float32Array,\n): void {\n for (let i = start; i < start + count; i++) {\n // State index 0 is 'available'. It is the only state that defers to the\n // category, exactly as 2D does: `paintSeat` fills a free seat with\n // `seatBaseColor(categoryKey)` and only overrides once it is held, sold or\n // selected. Those overrides carry information the category cannot, so they\n // must keep winning.\n const useCategory = iCategory && iState[i] === 0;\n const c = useCategory ? null : (states[iState[i]] ?? states[0]);\n iColor[i * 3] = c ? c[0] : iCategory![i * 3];\n iColor[i * 3 + 1] = c ? c[1] : iCategory![i * 3 + 1];\n iColor[i * 3 + 2] = c ? c[2] : iCategory![i * 3 + 2];\n }\n}\n\n// Two-triangle quad in [-1,1] (billboard base).\nconst SEAT_QUAD = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);\n// Fullscreen triangle.\nconst BG_TRI = new Float32Array([-1, -1, 3, -1, -1, 3]);\n\nexport interface GpuScene {\n /** Main scene (solids + seats), drawn with the camera. */\n main: Transform;\n /** Background scene, drawn first without depth. */\n background: Transform;\n seatProgram: Program;\n solidProgram: Program;\n /** Near-field chair program (shares uFocusFloor / fade-band uniforms). */\n chairProgram: Program;\n /** Shared instanced seat geometry (reused by the pick pass — no buffer copy). */\n seatGeometry: Geometry;\n /** Merged solid geometry (reused as the pick occluder). */\n solidGeometry: Geometry;\n drawCalls: number;\n /** Upload only the changed instance-state ranges (never the whole buffer). */\n uploadSeatStateRuns(runs: DirtyRun[]): void;\n /**\n * Hand the chair mesh the seat indices the near-field gather selected.\n *\n * `indices[0…count)` are instance indices into the seat cloud — the SAME\n * indices the dots and the pick pass use, which is what guarantees a chair and\n * its dot are always the same seat. Passing count 0 detaches the mesh.\n */\n setNearSeats(indices: Int32Array, count: number): void;\n /** How many chairs are currently instanced (0 = the near field is off). */\n nearSeatCount(): number;\n dispose(): void;\n}\n\nexport function buildGpuScene(gl: OGLRenderingContext, model: SceneModel): GpuScene {\n const main = new Transform();\n const background = new Transform();\n\n // --- Background ---\n const bgGeo = new Geometry(gl, { position: { size: 2, data: BG_TRI } });\n // The chart's own background, not the library's — see theme.ts.\n const bgProg = createBackgroundProgram(gl, model.theme.background.top as number[], model.theme.background.bottom as number[]);\n const bgMesh = new Mesh(gl, { geometry: bgGeo, program: bgProg });\n bgMesh.frustumCulled = false;\n bgMesh.setParent(background);\n\n // --- Solids (floor + tiers + stage + décor + GA, merged) ---\n const solidGeo = new Geometry(gl, {\n position: { size: 3, data: model.solids.position },\n normal: { size: 3, data: model.solids.normal },\n color: { size: 3, data: model.solids.color },\n floorIndex: { size: 1, data: model.solids.floor },\n });\n const solidProg = createSolidProgram(gl);\n const solidMesh = new Mesh(gl, { geometry: solidGeo, program: solidProg });\n solidMesh.frustumCulled = false;\n solidMesh.setParent(main);\n\n // --- Seats (one instanced billboard mesh) ---\n // Per-instance colour resolved CPU-side from iState (no dynamically-indexed\n // array uniform — OGL only binds an array uniform whose value is a plain\n // Array, and a dynamic LUT index is best avoided anyway).\n const seatProg = createSeatProgram(gl);\n const iColor = new Float32Array(model.seats.count * 3);\n const stateColors: RGB[] = SEAT_STATES.map((st) => model.theme.seatStates[st]);\n writeSeatColors(iColor, model.seats.iState, 0, model.seats.count, stateColors, model.seats.iCategory);\n const seatGeo = new Geometry(gl, {\n position: { size: 2, data: SEAT_QUAD },\n iOffset: { size: 3, data: model.seats.iPosition, instanced: 1 },\n iColor: { size: 3, data: iColor, instanced: 1 },\n // Per-seat world-radius ceiling: what stops distant rows merging into one\n // mass when the shader grows a dot to hold its minimum pixel size.\n iMaxRadius: { size: 1, data: model.seats.iMaxRadius, instanced: 1 },\n // Accommodation ring colour; (0,0,0) means the seat carries no access type.\n iRing: { size: 3, data: model.seats.iRing, instanced: 1 },\n iFloor: { size: 1, data: model.seats.iFloor, instanced: 1 },\n });\n const seatMesh = new Mesh(gl, { geometry: seatGeo, program: seatProg });\n seatMesh.frustumCulled = false;\n if (model.seats.count > 0) seatMesh.setParent(main);\n\n const colorAttr = seatGeo.attributes.iColor;\n\n // --- Near-field chairs (one extra instanced draw, only when in range) ------\n // Fixed-capacity instance buffers: the near set is bounded by\n // CHAIR_MAX_INSTANCES whatever the venue holds, so these are allocated once at\n // ~200 KB total and only ever refilled — never grown, never reallocated per\n // frame. `instancedCount` is what varies.\n const chairBase = buildChairMesh();\n const chairProg = createChairProgram(gl);\n const CAP = CHAIR_MAX_INSTANCES;\n const cOffset = new Float32Array(CAP * 3);\n const cColor = new Float32Array(CAP * 3);\n const cRadius = new Float32Array(CAP);\n const cYaw = new Float32Array(CAP);\n const cRing = new Float32Array(CAP * 3);\n const cFloor = new Float32Array(CAP);\n // Occupancy + a stable per-person variation, in one attribute.\n //\n // Negative means the seat is EMPTY and the occupant geometry collapses. A\n // non-negative value is a per-seat hash in [0,1) that varies build and skin\n // tone, so a full row is not a rank of identical mannequins — the same trick\n // the generated panorama used on its silhouettes.\n const cSeed = new Float32Array(CAP);\n const chairGeo = new Geometry(gl, {\n position: { size: 3, data: chairBase.position },\n normal: { size: 3, data: chairBase.normal },\n part: { size: 1, data: chairBase.part },\n index: { data: chairBase.index },\n iOffset: { size: 3, data: cOffset, instanced: 1 },\n iColor: { size: 3, data: cColor, instanced: 1 },\n iRadius: { size: 1, data: cRadius, instanced: 1 },\n iYaw: { size: 1, data: cYaw, instanced: 1 },\n iRing: { size: 3, data: cRing, instanced: 1 },\n iFloor: { size: 1, data: cFloor, instanced: 1 },\n iSeed: { size: 1, data: cSeed, instanced: 1 },\n });\n const chairMesh = new Mesh(gl, { geometry: chairGeo, program: chairProg });\n chairMesh.frustumCulled = false;\n let nearCount = 0;\n /** The gathered set, kept so a state change can recolour it without a re-gather. */\n let nearIndices: Int32Array = new Int32Array(0);\n\n const writeChairColors = (): void => {\n const src = model.seats;\n for (let k = 0; k < nearCount; k++) {\n const i = nearIndices[k];\n // An available chair wears its category as UPHOLSTERY, not as the map's\n // full-strength tier colour. Only the category case is muted: held, sold\n // and selected are saying something the buyer needs to see from where they\n // are sitting, and quietening those would lose real information.\n const useCategory = src.iCategory && src.iState[i] === 0;\n const c = useCategory ? null : (stateColors[src.iState[i]] ?? stateColors[0]);\n if (c) {\n cColor[k * 3] = c[0];\n cColor[k * 3 + 1] = c[1];\n cColor[k * 3 + 2] = c[2];\n } else {\n const u = upholsteryTone([src.iCategory[i * 3], src.iCategory[i * 3 + 1], src.iCategory[i * 3 + 2]]);\n cColor[k * 3] = u[0];\n cColor[k * 3 + 1] = u[1];\n cColor[k * 3 + 2] = u[2];\n }\n }\n chairGeo.attributes.iColor.needsUpdate = true;\n };\n\n const scene: GpuScene = {\n main,\n background,\n seatProgram: seatProg,\n solidProgram: solidProg,\n chairProgram: chairProg,\n seatGeometry: seatGeo,\n solidGeometry: solidGeo,\n drawCalls: 3,\n setNearSeats(indices: Int32Array, count: number): void {\n const n = Math.min(count, CAP);\n nearIndices = indices;\n nearCount = n;\n if (n === 0) {\n // Detached rather than drawn with instancedCount 0: an overview frame\n // must cost the same three draw calls it did before the near field\n // existed, and a zero-instance draw is still a state change + a call.\n if (chairMesh.parent) chairMesh.setParent(null);\n chairGeo.instancedCount = 0;\n scene.drawCalls = 3;\n return;\n }\n const src = model.seats;\n for (let k = 0; k < n; k++) {\n const i = indices[k];\n cOffset[k * 3] = src.iPosition[i * 3];\n cOffset[k * 3 + 1] = src.iPosition[i * 3 + 1];\n cOffset[k * 3 + 2] = src.iPosition[i * 3 + 2];\n // The chair's OWN pitch rule, not the dot's ceiling — see iChairWidth.\n cRadius[k] = src.iChairWidth[i];\n cYaw[k] = src.iYaw[i];\n cRing[k * 3] = src.iRing[i * 3];\n cRing[k * 3 + 1] = src.iRing[i * 3 + 1];\n cRing[k * 3 + 2] = src.iRing[i * 3 + 2];\n cFloor[k] = src.iFloor[i];\n cSeed[k] = seatOccupantSeed(src.iState[i], i);\n }\n writeChairColors();\n chairGeo.attributes.iOffset.needsUpdate = true;\n chairGeo.attributes.iRadius.needsUpdate = true;\n chairGeo.attributes.iYaw.needsUpdate = true;\n chairGeo.attributes.iRing.needsUpdate = true;\n chairGeo.attributes.iFloor.needsUpdate = true;\n chairGeo.attributes.iSeed.needsUpdate = true;\n chairGeo.instancedCount = n;\n if (!chairMesh.parent) chairMesh.setParent(main);\n scene.drawCalls = 4;\n },\n nearSeatCount(): number {\n return nearCount;\n },\n uploadSeatStateRuns(runs: DirtyRun[]): void {\n if (!runs.length) return;\n // A selection or availability change has to reach the chairs too, or a\n // seat picked at close range stays available-green under the buyer. The\n // gathered set is small and bounded, so it is simply rewritten rather than\n // diffed against the runs.\n if (nearCount) writeChairColors();\n // Refresh only the changed instance colours from the (already-mutated)\n // iState, then upload just those contiguous ranges — never the whole buffer.\n // iCategory must be passed HERE too, not just on the initial fill: a seat\n // whose hold expires goes back to 'available', and without it that seat\n // would repaint flat green while every untouched neighbour kept its\n // category colour.\n for (const run of runs) {\n writeSeatColors(iColor, model.seats.iState, run.start, run.length, stateColors, model.seats.iCategory);\n }\n const buffer = colorAttr.buffer;\n if (!buffer) {\n // Not uploaded yet (no draw has happened) — full upload on next draw.\n colorAttr.needsUpdate = true;\n return;\n }\n // Direct bufferSubData: OGL's render-state boundBuffer cache is not touched\n // here, which is safe because OGL rebinds attribute buffers per draw via the\n // geometry's VAO; if a future dynamic attribute relies on the cache, rebind\n // through OGL instead. 3 floats per instance (vec3 iColor).\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n for (const run of runs) {\n const sub = iColor.subarray(run.start * 3, (run.start + run.length) * 3);\n gl.bufferSubData(gl.ARRAY_BUFFER, run.start * 3 * Float32Array.BYTES_PER_ELEMENT, sub);\n }\n },\n dispose(): void {\n // OGL geometries/programs delete their GL resources on remove().\n bgGeo.remove();\n bgProg.remove();\n solidGeo.remove();\n solidProg.remove();\n seatGeo.remove();\n seatProg.remove();\n chairGeo.remove();\n chairProg.remove();\n },\n };\n return scene;\n}\n","/**\n * Inline GLSL (WebGL2 / GLSL ES 3.00) for the three scene programs. Zero\n * textures, zero shadow maps, zero post: a procedural matcap-style hemisphere +\n * warm key + fresnel rim on solids, a soft top-lit round dot for seats, and a\n * vertical-gradient + vignette background. OGL injects the built-in matrix\n * uniforms (modelViewMatrix / projectionMatrix / normalMatrix) by name.\n */\n\nimport { Program } from 'ogl';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport { CHAIR_FULL_M, CHAIR_NONE_M, SEAT_MIN_PIXELS_NEAR } from '../lod';\nimport { BACK_BASE_M, BACK_RAKE_SLOPE } from './seatChair';\nimport type { OGLRenderingContext } from 'ogl';\n\n/**\n * The near-field weight, shared verbatim by the chair and the dot so the two\n * always sum to one at every depth and no seat can be drawn twice or not at all.\n * Mirrors `chairWeight()` in lod.ts.\n */\nconst CHAIR_WEIGHT_GLSL = /* glsl */ `\nfloat chairWeight(float depth) {\n return 1.0 - smoothstep(uChairFull, uChairNone, depth);\n}`;\n\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\nuniform float uChairFull; // view depth at which the chair mesh is full size\nuniform float uChairNone; // ...and at which it has scaled away entirely\nout vec2 vUv;\nout vec3 vColor;\nout float vBudget; // 1 = dot holds its minimum pixel size, <1 = it cannot\nout vec3 vRing;\nout float vDim;\nout float vDotWeight; // 1 = the dot IS this seat, 0 = the chair has taken over\n${CHAIR_WEIGHT_GLSL}\nvoid main() {\n vec4 mv = modelViewMatrix * vec4(iOffset, 1.0);\n float depth = max(-mv.z, 0.001);\n // Hand the seat over to the chair mesh as it comes into range. Derived from\n // this instance's OWN depth rather than from a global uniform, so a row two\n // metres away and the far side of the bowl resolve differently in the same\n // frame — which is the entire point of a ladder over a switch.\n vDotWeight = 1.0 - chairWeight(depth);\n float minR = uMinPixels * depth * uPixelToWorld; // screen-space floor\n // Grow to hold the pixel floor, but never past this seat's own pitch ceiling:\n // unbounded growth is what merges neighbouring rows into one mass at range.\n float r = min(max(uSeatRadius * uSeatScale, minR), iMaxRadius);\n // 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;\nin float vDotWeight;\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 // Yield to the chair. The chair grows out of this exact point, so through the\n // band the dot is always at least as big as the chair inside it and the seat\n // never thins out to nothing in between.\n alpha *= vDotWeight;\n if (alpha <= 0.0) discard;\n fragColor = vec4(c, alpha);\n}`;\n\n// --- Near-field seat chairs ------------------------------------------------\n// One instanced draw over the 72-vertex base mesh in ./seatChair.ts, carrying\n// only the seats the CPU gathered as near (see ./nearField.ts). Lit by the same\n// rig as the solids so a chair reads as part of the room and not as a decal.\nconst CHAIR_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position; // local: x/z in units of the seat radius, y in METRES\nin vec3 normal;\nin float part; // 0 = pedestal, 1 = pad, 2 = back, 3 = body, 4 = head\nin vec3 iOffset; // per-instance world deck point (identical to the dot's)\nin vec3 iColor; // per-instance state colour\nin float iRadius; // per-instance horizontal half-width, world metres\nin float iYaw; // per-instance facing, radians (local +Z -> facing dir)\nin vec3 iRing; // accommodation ring colour; (0,0,0) = not accessible\nin float iFloor;\nin float iSeed; // <0 = seat is empty; else per-person hash in [0,1)\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uChairFull;\nuniform float uChairNone;\nuniform float uFocusFloor;\nuniform float uBackRake; // metres of z per metre of rise, above uBackBase\nuniform float uBackBase; // local height at which the back starts\nout vec3 vColor;\nout vec3 vNormalWorld;\nout vec3 vNormalView;\nout vec3 vPosView;\nout float vPart;\nout float vHeight; // local height in metres, for the vertical occlusion ramp\nout vec3 vRing;\nout float vDim;\nout float vOccupant; // 1 = this vertex belongs to a person, not to the chair\nout vec3 vOccupantTint;\n${CHAIR_WEIGHT_GLSL}\nvoid main() {\n vec4 anchor = modelViewMatrix * vec4(iOffset, 1.0);\n float w = chairWeight(max(-anchor.z, 0.001));\n // 1. Local units -> world metres. Only x/z scale: narrow rows get narrow\n // chairs, but nobody gets a short one (people are the same height at every\n // seat pitch).\n vec3 p = position;\n // The occupant. Parts 3 and 4 are a person; everything below is furniture.\n //\n // One instanced mesh draws both an empty seat and a taken one, because the\n // alternative — a second geometry and a second draw call gathered per frame —\n // would double the near-field cost to show what is already known per instance.\n // An unoccupied seat collapses its person to a point at the seat, which the\n // rasteriser discards, exactly as the chair itself collapses at w=0.\n float occupant = step(2.5, part);\n float taken = step(0.0, iSeed);\n vOccupant = occupant;\n if (occupant > 0.5) {\n if (taken < 0.5) {\n p = vec3(0.0); // empty seat: no person\n } else {\n // Vary the build so a sold-out row is people rather than a rank of\n // identical mannequins: +/-6% height and +/-8% width off the hash.\n p.y *= 0.94 + 0.12 * iSeed;\n p.xz *= 0.92 + 0.16 * fract(iSeed * 7.13);\n }\n }\n p.xz *= iRadius;\n // 2. Lean the back. Done here rather than in the base mesh so the lean is a\n // real angle in METRES — baked into the mesh it would scale with the seat's\n // width and the same chair would lean 20 degrees on a wide stadium row and\n // 6 on a tight theatre one.\n // Only the BACK panel rakes. The old test (part > 1.5) meant \"the back\", and\n // now also catches the occupant — leaning a person by the panel's rule would\n // translate their head backwards by a rake measured from the panel's base.\n float rake = (part > 1.5 && part < 2.5) ? max(p.y - uBackBase, 0.0) * uBackRake : 0.0;\n p.z -= rake;\n // 3. Scale-in. At w=0 the chair is a point at the seat, under a dot at full\n // opacity — which is what makes the handover invisible. sqrt front-loads\n // the growth so the chair is already near full size while the dot is still\n // half there; see chairScale() in lod.ts.\n p *= sqrt(w);\n float c = cos(iYaw), s = sin(iYaw);\n vec3 rp = vec3(p.x * c + p.z * s, p.y, -p.x * s + p.z * c);\n // Normals under the same two transforms, in reverse and inverted-transposed.\n // The xz scale is non-uniform, so an axis-aligned normal does NOT survive it\n // unchanged; and the rake is a shear, whose normal transform adds a y term.\n // Skipping either lights the raked back as though it were still vertical.\n vec3 n = vec3(normal.x / iRadius, normal.y, normal.z / iRadius);\n if (part > 1.5 && part < 2.5) n.y += uBackRake * n.z;\n n = normalize(n);\n vec3 rn = vec3(n.x * c + n.z * s, n.y, -n.x * s + n.z * c);\n vec4 mv = modelViewMatrix * vec4(iOffset + rp, 1.0);\n vPosView = mv.xyz;\n vNormalWorld = rn;\n vNormalView = normalize(mat3(modelViewMatrix) * rn);\n vColor = iColor;\n // Occupant colour, resolved here so the fragment stage needs no extra\n // varyings beyond this one. A person is NOT painted in the seat's state\n // colour: a sold seat is red, and a hall of red people reads as a warning,\n // not an audience. Hair/clothing for the body, a warm tone for the head,\n // both varied by the same per-person hash.\n float t = fract(iSeed * 3.71);\n vec3 clothes = mix(vec3(0.13, 0.15, 0.20), vec3(0.34, 0.30, 0.36), t);\n vec3 skin = mix(vec3(0.52, 0.38, 0.29), vec3(0.86, 0.70, 0.58), fract(iSeed * 11.3));\n vOccupantTint = (part > 3.5) ? skin : clothes;\n vPart = part;\n vHeight = position.y;\n vRing = iRing;\n vDim = (uFocusFloor < -0.5 || abs(iFloor - uFocusFloor) < 0.5) ? 0.0 : 1.0;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst CHAIR_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 vColor;\nin vec3 vNormalWorld;\nin vec3 vNormalView;\nin vec3 vPosView;\nin float vPart;\nin float vHeight;\nin vec3 vRing;\nin float vDim;\nin float vOccupant;\nin vec3 vOccupantTint;\nuniform vec3 uKeyDir;\nuniform vec3 uFadeColor;\nout vec4 fragColor;\nvoid main() {\n vec3 N = normalize(vNormalWorld);\n vec3 V = normalize(-vPosView);\n // The solids' rig, unchanged, so the chairs sit in the venue's light.\n float hemi = 0.5 + 0.5 * N.y;\n float key = max(dot(N, uKeyDir), 0.0);\n vec3 fillDir = normalize(vec3(-uKeyDir.x, 0.25, -uKeyDir.z));\n float fill = max(dot(N, fillDir), 0.0);\n vec3 tint = vColor;\n if (vOccupant > 0.5) tint = vOccupantTint;\n // The accommodation ring, kept legible once the dot (which drew it) is gone:\n // an accessible seat's PEDESTAL is painted in the ring colour, so the marker\n // survives to close range instead of vanishing exactly when the buyer arrives.\n float ringMask = step(0.001, dot(vRing, vRing));\n if (vPart < 0.5 && ringMask > 0.5) tint = vRing;\n // Pad brightest, back a step below it, pedestal darkest. Three untextured\n // boxes only read as one object if they are separated tonally — with a single\n // flat colour the chair silhouettes as a crate.\n // A person is lit as a person, not as upholstery: no part shading, and less\n // of the pad's sheen, so a head does not read as a polished box.\n float partShade = vOccupant > 0.5 ? 0.95 : (vPart < 0.5 ? 0.50 : (vPart < 1.5 ? 1.10 : 0.80));\n // Cheap vertical occlusion: a chair is in a dense row, so the closer a surface\n // sits to the deck the less sky it can actually see. This is the depth cue —\n // without it the pad top, the back and the deck all resolve to the same flat\n // value and the row loses its form entirely.\n // Occupants rise above the 0.92 m chair back, so their ramp uses their own\n // height or every head would clamp to full brightness and float.\n float ao = mix(0.58, 1.0, clamp(vHeight / (vOccupant > 0.5 ? 1.30 : 0.92), 0.0, 1.0));\n vec3 base = tint * partShade * ao * (0.52 + 0.40 * hemi) + tint * key * 0.38 + tint * fill * 0.10;\n float fres = pow(1.0 - max(dot(normalize(vNormalView), V), 0.0), 3.0);\n // A brighter rim than the solids get: it picks out every chair's own edge,\n // which is what stops a block of them merging into one mass up close.\n base += vec3(0.26, 0.31, 0.38) * fres * 0.55;\n base = mix(base, uFadeColor, vDim * 0.75);\n fragColor = vec4(base, 1.0);\n}`;\n\nconst BG_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 position;\nout vec2 vUv;\nvoid main() {\n vUv = position * 0.5 + 0.5;\n gl_Position = vec4(position, 0.999, 1.0);\n}`;\n\nconst BG_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform vec3 uTop;\nuniform vec3 uBottom;\nout vec4 fragColor;\nvoid main() {\n vec3 col = mix(uBottom, uTop, vUv.y);\n vec2 c = vUv - 0.5;\n float vig = 1.0 - dot(c, c) * 0.85; // soft vignette\n fragColor = vec4(col * vig, 1.0);\n}`;\n\n// --- GPU pick pass ---------------------------------------------------------\n// Seats encode gl_InstanceID+1 as an RGB colour (no extra per-instance buffer);\n// solids write pure black + depth first so a seat occluded by a tier reads as\n// \"no hit\". Same billboard maths as the display seat program.\nconst SEAT_PICK_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec3 iOffset;\nin float iMaxRadius;\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: SEAT_MIN_PIXELS_NEAR },\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: SEAT_MIN_PIXELS_NEAR },\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 uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n },\n });\n}\n\n/**\n * The near-field chair program.\n *\n * OPAQUE and depth-writing, unlike the dots. That is the reason the transition\n * is a scale-in and not a cross-fade: a translucent chair either writes depth\n * and punches its own silhouette through the chairs behind it, or does not and\n * shows its own back panel through its own seat pad. Growing an opaque chair out\n * of the seat point avoids both, and it composes correctly with the dot, which\n * still draws afterwards in the transparent pass.\n *\n * `cullFace` stays off to match the rest of the renderer — the meshes are closed\n * opaque boxes, so the depth test already suppresses the back faces' cost, and a\n * winding mistake here would silently blank the chairs rather than fail loudly.\n */\nexport function createChairProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: CHAIR_VERT,\n fragment: CHAIR_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n uniforms: {\n uKeyDir: { value: new Float32Array([0.38, 0.86, 0.34]) },\n uFadeColor: { value: new Float32Array([0.32, 0.37, 0.43]) },\n uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n uBackRake: { value: BACK_RAKE_SLOPE },\n uBackBase: { value: BACK_BASE_M },\n uFocusFloor: { value: -1 },\n },\n });\n}\n\nexport function createBackgroundProgram(gl: OGLRenderingContext, top: number[], bottom: number[]): Program {\n return new Program(gl, {\n vertex: BG_VERT,\n fragment: BG_FRAG,\n depthTest: false,\n depthWrite: false,\n cullFace: false,\n uniforms: {\n uTop: { value: new Float32Array(top) },\n uBottom: { value: new Float32Array(bottom) },\n },\n });\n}\n","/**\n * GPU color-pick (Slice 2). On a TAP (not hover, not drag) the seat instance\n * index is rendered as RGB into a small scissored offscreen target and a single\n * pixel is read back → O(1) regardless of seat count. Solids are drawn first as\n * black + depth so a seat occluded by a tier reads as \"no hit\".\n *\n * The pick meshes reuse the display geometry buffers (iOffset / position), so no\n * per-seat data is duplicated on the GPU.\n */\n\nimport { Geometry, Mesh, Program, RenderTarget, Transform } from 'ogl';\nimport type { Camera, OGLRenderingContext, Renderer } from 'ogl';\nimport { 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 /**\n * True when `url` is a SYNTHESISED panorama rather than a real photograph.\n *\n * It decides which of two very different things the buyer gets, so the caller\n * must say rather than the renderer guess. A generated equirect is a picture\n * of a 3D scene the renderer already has: at 2048×1024 it covers 106° of\n * horizontal view in ~600 source pixels, which a 3072-device-pixel viewport\n * magnifies FIVE times, and no amount of art survives that. So a generated\n * view is not drawn as an image at all — the camera simply looks around the\n * real scene, which has no resolution ceiling.\n *\n * A real photo has no such substitute and goes on the sphere, where a 5760 or\n * 8192-wide capture has three to four times the detail to give.\n */\n generated?: boolean;\n /** 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 * The equirectangular panorama, as real geometry inside the venue scene.\n *\n * What this replaces, and why it had to change:\n *\n * `crossfade/panorama.ts` draws the 360 as a DOM div with `background-repeat:\n * repeat-x`, panned by hand and windowed to a 70° vertical slice. To show it,\n * `openPanorama` calls `loop.stop()` — it FREEZES the WebGL venue and fades a\n * flat image over the corpse. So the immersive payoff of the whole 3D flight is\n * the moment the product stops using its renderer.\n *\n * A `repeat-x` background is also not a sphere. It is a cylinder at best: pan is\n * linear in longitude (correct), but pitch is a vertical translation of a flat\n * bitmap, so verticals bow and the image stretches away from the horizon. A real\n * 360 photo looks right in the 2D popover — which uses an actual sphere via\n * Photo Sphere Viewer — and subtly wrong here, on the surface the marketing\n * story leads with.\n *\n * The fix is not to import Photo Sphere Viewer. That is Three.js-based, and this\n * chunk already owns a WebGL stack (OGL) and is deliberately kept at ~71 KB\n * gzipped because it loads only when a buyer opens 3D. Pulling in a second\n * renderer to draw one textured sphere would roughly triple it.\n *\n * So: a unit sphere, inward-facing, sampled equirectangularly, drawn by the\n * scene's own renderer with the scene's own camera. The panorama stops being an\n * overlay and becomes somewhere the camera can be, which is also what makes the\n * fly-to-seat handoff a continuous move rather than a cut between two different\n * rendering technologies.\n *\n * Everything below the `mount` function is pure and unit-tested; the GL parts\n * are a thin wrapper over it.\n */\n\nimport { Geometry, Mesh, Program, Texture } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\n\n/**\n * Longitude/latitude tessellation of the sphere.\n *\n * The sphere is viewed from its exact centre, so silhouette smoothness never\n * matters — only how much the equirect sampling is distorted by interpolating\n * UVs across a triangle. 64×32 keeps the worst-case angular error under a third\n * of a degree at the horizon, which is far below a pixel at any sane FOV, for\n * 4,096 triangles. Going finer costs vertices to fix an error nothing can see.\n */\nexport const PANO_SEGMENTS_LON = 64;\nexport const PANO_SEGMENTS_LAT = 32;\n\n/**\n * Radius in metres. Arbitrary in principle — the camera sits at the centre and\n * an equirect sphere has no parallax — but it must be large enough to enclose\n * the near clip plane and small enough to stay inside the far plane, or the\n * panorama clips against its own geometry. 500 m clears both for every venue in\n * the catalog, including the 50k-seat stadium.\n */\nexport const PANO_RADIUS_M = 500;\n\nexport interface SphereVertexData {\n position: Float32Array;\n uv: Float32Array;\n index: Uint16Array;\n}\n\n/**\n * A UV sphere with INWARD-facing triangles and equirectangular UVs.\n *\n * Inward-facing is done by winding, not by disabling culling: the scene draws\n * with back-face culling on, and turning it off for one mesh would leak that\n * state onto everything drawn after it in the same pass. Reversing the triangle\n * winding makes the inside surface the front face, so the sphere obeys exactly\n * the same pipeline state as the venue.\n *\n * Mapping, matching `generatePanorama.ts` and the 2D viewer so one image reads\n * identically in all three:\n * u = 0.5 + yaw/360 (u 0.5 is yaw 0, the direction the camera faces)\n * v = (90 - pitch)/180 (v 0 is the top of the image)\n *\n * The seam at u=0/1 is duplicated rather than wrapped — a shared vertex would\n * have to carry both u=0 and u=1 and would interpolate the whole image backwards\n * across that one column of triangles.\n */\nexport function buildPanoSphere(\n segmentsLon: number = PANO_SEGMENTS_LON,\n segmentsLat: number = PANO_SEGMENTS_LAT,\n radius: number = PANO_RADIUS_M,\n): SphereVertexData {\n const lonCount = segmentsLon + 1; // +1 duplicates the seam column\n const latCount = segmentsLat + 1; // +1 closes both poles\n const vertexCount = lonCount * latCount;\n const position = new Float32Array(vertexCount * 3);\n const uv = new Float32Array(vertexCount * 2);\n\n for (let iLat = 0; iLat < latCount; iLat++) {\n // v runs 0 (top) → 1 (bottom); polar angle 0 at +Y.\n const v = iLat / segmentsLat;\n const polar = v * Math.PI;\n const sinPolar = Math.sin(polar);\n const cosPolar = Math.cos(polar);\n for (let iLon = 0; iLon < lonCount; iLon++) {\n const u = iLon / segmentsLon;\n // u 0.5 must land on yaw 0. The scene's yaw 0 faces -Z (the camera looks\n // down -Z by default), so offset the azimuth by half a turn.\n const azimuth = (u - 0.5) * Math.PI * 2;\n const i = iLat * lonCount + iLon;\n position[i * 3] = radius * sinPolar * Math.sin(azimuth);\n position[i * 3 + 1] = radius * cosPolar;\n position[i * 3 + 2] = -radius * sinPolar * Math.cos(azimuth);\n uv[i * 2] = u;\n uv[i * 2 + 1] = v;\n }\n }\n\n // Two triangles per quad, wound so the INSIDE is the front face.\n const index = new Uint16Array(segmentsLon * segmentsLat * 6);\n let w = 0;\n for (let iLat = 0; iLat < segmentsLat; iLat++) {\n for (let iLon = 0; iLon < segmentsLon; iLon++) {\n const a = iLat * lonCount + iLon;\n const b = a + lonCount;\n index[w++] = a; index[w++] = b; index[w++] = a + 1;\n index[w++] = a + 1; index[w++] = b; index[w++] = b + 1;\n }\n }\n return { position, uv, index };\n}\n\n/**\n * Direction (unit vector, scene axes) the camera looks along for a given\n * bearing and pitch in degrees. Bearing 0 faces the focal/stage, matching\n * `SeatView.initialBearingDeg`; positive pitch looks up.\n *\n * Exported because the transition from the flight has to hand the sphere the\n * camera's final yaw, and a panorama that opens on a different bearing than the\n * flight ended on reads as a cut.\n */\nexport function bearingPitchToDirection(bearingDeg: number, pitchDeg: number): [number, number, number] {\n const yaw = (bearingDeg * Math.PI) / 180;\n const pitch = (pitchDeg * Math.PI) / 180;\n const cosPitch = Math.cos(pitch);\n return [cosPitch * Math.sin(yaw), Math.sin(pitch), -cosPitch * Math.cos(yaw)];\n}\n\nconst PANO_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nin vec2 uv;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst PANO_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform sampler2D uPano;\nuniform float uOpacity;\nout vec4 fragColor;\nvoid main() {\n vec3 rgb = texture(uPano, vUv).rgb;\n fragColor = vec4(rgb, uOpacity);\n}`;\n\nexport interface PanoSphereHandle {\n mesh: Mesh;\n /** Cross-fade control: 0 hides the sphere, 1 fully replaces the venue. */\n setOpacity(value: number): void;\n dispose(): void;\n}\n\n/**\n * Build the textured sphere mesh. The caller owns adding it to a scene graph and\n * driving `setOpacity` — this module deliberately knows nothing about the flight\n * or the overlay chrome.\n *\n * `depthTest: false` and `depthWrite: false`: the sphere encloses the entire\n * venue, so with depth on it would be occluded by every piece of geometry\n * between the camera and its far wall. It is a backdrop that replaces the view,\n * not a solid in it — so it is drawn without consulting depth, and the caller\n * draws it after the venue when fading in.\n */\nexport function createPanoSphere(\n gl: OGLRenderingContext,\n // Narrower than `TexImageSource` on purpose: the panorama always arrives as a\n // decoded <img> (a URL or the generator's data URL) or a canvas. OGL's texture\n // type does not accept ImageBitmap, and widening here would only move the\n // failure to runtime.\n image: HTMLImageElement | HTMLCanvasElement,\n): PanoSphereHandle {\n const { position, uv, index } = buildPanoSphere();\n const geometry = new Geometry(gl, {\n position: { size: 3, data: position },\n uv: { size: 2, data: uv },\n index: { data: index },\n });\n const texture = new Texture(gl, {\n image,\n // The seam column is duplicated in geometry, so CLAMP is correct and avoids\n // a wrapped bilinear tap bleeding the far edge of the image into it.\n wrapS: gl.CLAMP_TO_EDGE,\n wrapT: gl.CLAMP_TO_EDGE,\n generateMipmaps: true,\n // MUST be false, and this is the one line that decides whether the whole\n // panorama is upside down.\n //\n // OGL defaults `flipY` to true for 2D textures, which is right for the usual\n // case: a UV of 0 means the BOTTOM of a quad, an image's first row is its\n // TOP, and flipping on upload reconciles the two. This sphere is the other\n // case. Its `v` is authored in IMAGE space — v=0 is the top of the equirect\n // and maps to the top of the sphere (`buildPanoSphere`, and the mapping\n // `generatePanorama` and the 2D viewer both use). Flipping on upload as well\n // applies the correction twice: the stadium pitch renders on the ceiling and\n // the audience sits overhead.\n //\n // The geometry tests state the v mapping and pass either way — they never\n // touch GL — so this is not something a unit test can defend. It was caught\n // by looking at a stadium.\n flipY: false,\n });\n const program = new Program(gl, {\n vertex: PANO_VERT,\n fragment: PANO_FRAG,\n uniforms: { uPano: { value: texture }, uOpacity: { value: 0 } },\n transparent: true,\n depthTest: false,\n depthWrite: false,\n });\n const mesh = new Mesh(gl, { geometry, program });\n return {\n mesh,\n setOpacity(value: number): void {\n program.uniforms.uOpacity.value = Math.max(0, Math.min(1, value));\n },\n dispose(): void {\n mesh.setParent(null);\n geometry.remove();\n if (texture.texture) gl.deleteTexture(texture.texture);\n program.remove();\n },\n };\n}\n","/**\n * The panorama, mounted as scene geometry instead of as a div over a corpse.\n *\n * `panorama.ts` (still here, still used as the fallback) freezes the render loop\n * and fades a `repeat-x` bitmap over the last WebGL frame. This mounts the same\n * image on the inward-facing sphere from `scene/panoSphere.ts`, leaves the loop\n * running, and moves the camera instead of a background-position. Three things\n * follow from that, and they are the reason for the rewrite:\n *\n * - It is a real sphere. Pitch is a camera rotation, not a vertical translation\n * of a flat bitmap, so verticals stay vertical and nothing stretches away\n * from the horizon.\n * - The venue is still being drawn underneath, so the arrival is a CROSS-FADE\n * between two views of the same place from the same camera, not a cut\n * between two rendering technologies.\n * - Escape/close costs nothing to reverse: the camera is already where it needs\n * to be, because it never went anywhere.\n *\n * The DOM chrome (close button, hint, dialog semantics) is kept deliberately —\n * it is the accessible surface, and none of it belongs in GL.\n */\n\nimport { createPanoSphere, bearingPitchToDirection, type PanoSphereHandle } from '../scene/panoSphere';\nimport type { PanoramaHandle, PanoramaOptions, SeatView } from './panorama';\nimport { MAX_PITCH_DEG, VFOV_DEG } from './panorama';\nimport type { OGLRenderingContext, Transform, Camera } from 'ogl';\n\n/**\n * Degrees of look per pixel dragged. Matched to the DOM viewer's feel: a drag\n * across the full width of a 1200 px viewport turns roughly 180°.\n */\nconst DEG_PER_PX = 0.15;\n\nexport interface PanoramaSceneDeps {\n gl: OGLRenderingContext;\n /** The scene the venue is drawn into; the sphere joins it and leaves on close. */\n scene: Transform;\n camera: Camera;\n /** Ask for another frame — the loop is render-on-demand, not free-running. */\n requestRender(): void;\n /**\n * The venue focal (stage/pitch centre) in world metres.\n *\n * Only scene mode needs it, and it genuinely cannot do without it. A generated\n * panorama is DRAWN with the stage at bearing 0, so opening at bearing 0 faces\n * the stage for free. The real scene has no such convention — bearing 0 is\n * world −Z, which points wherever the venue happened to be authored — so\n * opening there stares at whatever is behind the buyer. The bearing is derived\n * from the seat's own line to the focal instead.\n */\n focalWorld?: readonly [number, number, number];\n}\n\n/** Load an image element, resolving only once it is decoded and safe to upload. */\nfunction loadImage(url: string): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n // Panoramas are served from R2/CDN on another origin; without this the\n // texture upload taints nothing but simply fails on some browsers.\n img.crossOrigin = 'anonymous';\n img.onload = () => resolve(img);\n img.onerror = () => reject(new Error('panorama_image_failed'));\n img.src = url;\n });\n}\n\n/**\n * Mount the panorama sphere. Returns null if the image cannot be decoded, so the\n * caller can fall back to the DOM viewer rather than stranding the buyer on a\n * black screen — a 360 that fails to load must never be worse than no 360.\n */\nexport async function mountPanoramaSphere(\n container: HTMLElement,\n // NULL means \"look around the real scene\": no image, no sphere, just the\n // camera at the seat. That is the right answer for a generated panorama,\n // which is only ever a low-resolution picture of the geometry already on\n // screen — see `SeatView.generated`. Everything else below (framing,\n // look-around, Escape, restoring the camera) is identical, which is why the\n // two modes share one code path rather than diverging into two viewers that\n // drift apart.\n view: SeatView | null,\n deps: PanoramaSceneDeps,\n opts: PanoramaOptions = {},\n): Promise<PanoramaHandle | null> {\n const fadeMs = opts.fadeMs ?? 400;\n let bearing = view?.initialBearingDeg ?? 0;\n let pitch = 0;\n let disposed = false;\n\n // Scene mode opens on the stage, derived from where this seat actually is.\n // The pitch matters as much as the yaw: from a balcony the stage is well below\n // eye level, and opening level puts the whole venue in the bottom of the frame\n // with dead air above it.\n if (!view && deps.focalWorld) {\n const p = deps.camera.position;\n const dx = deps.focalWorld[0] - p.x;\n const dy = deps.focalWorld[1] - p.y;\n const dz = deps.focalWorld[2] - p.z;\n const flat = Math.hypot(dx, dz);\n if (flat > 1e-3) {\n bearing = (Math.atan2(dx, -dz) * 180) / Math.PI;\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, (Math.atan2(dy, flat) * 180) / Math.PI));\n }\n }\n\n let sphere: PanoSphereHandle | null = null;\n if (view) {\n let image: HTMLImageElement;\n try {\n image = await loadImage(view.url);\n } catch {\n return null;\n }\n try {\n sphere = createPanoSphere(deps.gl, image);\n } catch {\n return null;\n }\n // Drawn last and without depth, so it composites over the venue during the\n // fade instead of being occluded by whatever sits between the camera and\n // the far wall of the sphere.\n sphere.mesh.renderOrder = 999;\n sphere.mesh.setParent(deps.scene);\n }\n\n // A cinematic flight ENDS pushed in — it narrows the FOV to close the last of\n // the distance to the seat, and `resumeAfterFlight` is what normally restores\n // it. The DOM viewer never noticed because it never used the camera; drawing\n // the panorama through that same camera inherits the zoom and magnifies the\n // equirect to a few unreadable degrees of arc.\n //\n // So the sphere sets its own field of view — the same 70° the DOM viewer\n // windowed to, which is what makes the two paths frame a panorama alike — and\n // puts back whatever it found on the way out, so closing hands the flight's\n // camera back exactly as it was.\n const priorFov = deps.camera.fov;\n deps.camera.perspective({ fov: VFOV_DEG, aspect: deps.camera.aspect });\n\n // Looking around REPLACES the camera's orientation, and orbit only re-aims on\n // a frame it considers \"moving\". So closing the panorama used to hand back a\n // camera still pointing wherever the buyer had dragged, at a venue that was no\n // longer in front of it — a black screen until the first orbit drag snapped it\n // home. Capture the orientation on the way in and put it back on the way out,\n // exactly as the FOV is handled.\n const priorQuat = deps.camera.quaternion.slice() as unknown as number[];\n\n /** Keep the camera at the sphere's exact centre — an equirect has no parallax,\n * and any offset would shear the image. */\n const aim = (): void => {\n const p = deps.camera.position;\n sphere?.mesh.position.set(p.x, p.y, p.z);\n const [dx, dy, dz] = bearingPitchToDirection(bearing, pitch);\n deps.camera.lookAt([p.x + dx, p.y + dy, p.z + dz]);\n deps.requestRender();\n };\n aim();\n\n // --- chrome -------------------------------------------------------------\n const root = document.createElement('div');\n 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',\n cursor: 'grab', touchAction: 'none',\n } as CSSStyleDeclaration);\n\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.textContent = '✕';\n Object.assign(closeBtn.style, {\n position: 'absolute', top: '12px', right: '12px', zIndex: '2',\n width: '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 container.appendChild(root);\n\n // --- look-around --------------------------------------------------------\n let dragging = false;\n let lastX = 0;\n let lastY = 0;\n const onDown = (e: PointerEvent): void => {\n if (e.target === closeBtn) return;\n dragging = true; lastX = e.clientX; lastY = e.clientY;\n root.setPointerCapture?.(e.pointerId);\n root.style.cursor = 'grabbing';\n };\n const onMove = (e: PointerEvent): void => {\n if (!dragging) return;\n // Horizontal follows the HAND: drag right, turn right. The vertical below\n // stays grab-style (drag down to look up), which is the mix the owner asked\n // for after using it — and worth stating because it looks inconsistent in\n // the source and is not. The DOM viewer this replaces dragged the image on\n // both axes; the yaw was the one that read backwards in a real venue.\n bearing += (e.clientX - lastX) * DEG_PER_PX;\n // Clamped to the same ±35° the DOM viewer allowed. A generated panorama has\n // nothing meaningful at the poles, and letting the buyer tumble to straight\n // up reads as a broken control rather than a feature.\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, pitch + (e.clientY - lastY) * DEG_PER_PX));\n lastX = e.clientX; lastY = e.clientY;\n aim();\n };\n const onUp = (): void => { dragging = false; root.style.cursor = 'grab'; };\n root.addEventListener('pointerdown', onDown);\n root.addEventListener('pointermove', onMove);\n root.addEventListener('pointerup', onUp);\n root.addEventListener('pointercancel', onUp);\n\n // Escape closes. On `window`, not on `root`, because the hint promises it\n // unconditionally and a div only receives keys while focused — the DOM viewer\n // this replaces bound it the same way, and dropping it made the hint lie.\n // `stopPropagation` so the host page's own Escape handling (closing a modal,\n // leaving 3D) does not also fire on the same press.\n const onKey = (e: KeyboardEvent): void => {\n if (e.key !== 'Escape' || disposed) return;\n e.stopPropagation();\n handle.close();\n };\n window.addEventListener('keydown', onKey);\n\n // --- fade ---------------------------------------------------------------\n let raf = 0;\n const fadeTo = (target: number, done?: () => void): void => {\n // Scene mode has nothing to fade — the buyer is already looking at the\n // venue — so the transition is the FOV widening, not a dissolve.\n if (!sphere) { done?.(); return; }\n const from = target === 1 ? 0 : 1;\n const start = performance.now();\n const step = (): void => {\n if (disposed) return;\n const t = fadeMs <= 0 ? 1 : Math.min(1, (performance.now() - start) / fadeMs);\n sphere?.setOpacity(from + (target - from) * t);\n deps.requestRender();\n if (t < 1) raf = requestAnimationFrame(step);\n else done?.();\n };\n raf = requestAnimationFrame(step);\n };\n fadeTo(1);\n\n const teardown = (): void => {\n if (disposed) return;\n disposed = true;\n cancelAnimationFrame(raf);\n root.removeEventListener('pointerdown', onDown);\n root.removeEventListener('pointermove', onMove);\n root.removeEventListener('pointerup', onUp);\n root.removeEventListener('pointercancel', onUp);\n window.removeEventListener('keydown', onKey);\n root.remove();\n sphere?.dispose();\n deps.camera.perspective({ fov: priorFov, aspect: deps.camera.aspect });\n deps.camera.quaternion.set(priorQuat[0], priorQuat[1], priorQuat[2], priorQuat[3]);\n deps.requestRender();\n };\n\n const handle: PanoramaHandle = {\n close(): void {\n if (disposed) return;\n const finish = (): void => { teardown(); opts.onClose?.(); };\n cancelAnimationFrame(raf);\n fadeTo(0, finish);\n },\n dispose: teardown,\n };\n closeBtn.addEventListener('click', () => handle.close());\n return handle;\n}\n","/**\n * view3d analytics — a tiny, decoupled event emitter for the venue view. The\n * caller (app/harness) supplies `onAnalytics`; this class owns the per-mount\n * state (first-orbit latch, panorama dwell timing) and, crucially, wraps EVERY\n * callback invocation in try/catch so a throwing analytics sink can never break\n * rendering. No DOM, no GL — unit-testable in isolation.\n */\n\nexport type Analytics3DCallback = (event: string, props?: Record<string, unknown>) => void;\n\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":["Quat","Vec3","norm","now","scale","scale","polygonClipping","polygonClipping","earcut","out","polygonClipping","poly","earcut","belowY","pointInRing","polygonClipping","sub","Mesh","Transform","Transform","Mesh","Vec3","Vec3","now","Geometry","Mesh","Program","DEG","moved","Vec3","Quat"]}
1
+ {"version":3,"sources":["../../src/view3d/index.ts","../../src/view3d/gl/context.ts","../../src/view3d/palette.ts","../../src/view3d/camera/orbit.ts","../../src/view3d/loop.ts","../../src/view3d/lod.ts","../../src/view3d/scene/nearField.ts","../../src/view3d/scene/geometry.ts","../../src/view3d/scene/seatChair.ts","../../src/view3d/scene/seatInstances.ts","../../src/view3d/theme.ts","../../src/view3d/labels.ts","../../src/view3d/scene/sceneModel.ts","../../src/core/rake.ts","../../src/view3d/scene/deckBands.ts","../../src/view3d/scene/surface.ts","../../src/view3d/labelOverlay.ts","../../src/view3d/scene/build.ts","../../src/view3d/scene/materials.ts","../../src/view3d/pick/pickPipeline.ts","../../src/view3d/pick/encode.ts","../../src/view3d/pick/selection.ts","../../src/view3d/camera/cinematic.ts","../../src/view3d/camera/cinematicMath.ts","../../src/view3d/camera/seatViewPose.ts","../../src/view3d/scene/panoSphere.ts","../../src/view3d/crossfade/panoramaSphere.ts","../../src/view3d/analytics.ts","../../src/view3d/positioningContext.ts","../../src/view3d/prepareScene.ts"],"sourcesContent":["/**\n * view3d — the sole dynamic-import boundary for the lazy OGL venue-view chunk.\n *\n * const { mountVenue3D } = await import('../view3d');\n * const handle = mountVenue3D(container, { doc, seats }, { onSeatPick, getSeatView });\n * await handle.flyToSeat(seatId);\n *\n * Read-only 3D of any chart, fed entirely from the existing height contract.\n * Slice 1: orbit camera, extruded tiers/stage/GA, instanced seat dots, sub-range\n * availability, dispose + context-loss survival. Slice 2: GPU color-pick. Slice\n * 3: the fly-to-seat cinematic that dissolves into the view-from-seat panorama.\n */\n\nimport { Quat, Vec3 } from 'ogl';\nimport type { ChartDoc, ExpandedSeat } from '../core/types';\nimport { GLContext } from './gl/context';\nimport { OrbitCamera } from './camera/orbit';\nimport { RenderLoop, type RenderLoopStats } from './loop';\nimport { computeSeatLod, CHAIR_GATHER_M, CHAIR_MAX_INSTANCES, CHAIR_REBUILD_M } from './lod';\nimport { NearFieldIndex } from './scene/nearField';\nimport { SEAT_DOT_RADIUS_M } from './scene/seatInstances';\nimport { buildSceneModel, type SceneModel, type SceneZone, type SceneSection, type SceneFloor, type SceneRow } from './scene/sceneModel';\nimport { LabelOverlay } from './labelOverlay';\nimport { projectToScreen } from './labels';\nimport { buildGpuScene, type GpuScene } from './scene/build';\nimport { applySeatStates } from './scene/seatInstances';\nimport { PickPipeline } from './pick/pickPipeline';\nimport { pickPixelCoords } from './pick/encode';\nimport { diffSelection, mergeAvailabilityIntoSelection } from './pick/selection';\nimport { Cinematic, buildWaypoints, lookAtQuat, FLIGHT_DURATION_MS, FOV_END, type Vec3Arr } from './camera/cinematic';\nimport { seatViewPose } from './camera/seatViewPose';\nimport { mountPanorama, seatViewDisclosure, type PanoramaHandle, type SeatView } from './crossfade/panorama';\nimport { mountPanoramaSphere } from './crossfade/panoramaSphere';\nimport { Analytics3D, type Analytics3DCallback } from './analytics';\nimport type { SeatState3D } from './palette';\nimport { establishPositioningContext } from './positioningContext';\nimport type { PreparedVenue3D } from './prepareScene';\n\nexport { prepareVenue3D } from './prepareScene';\n\nexport type { SeatState3D } from './palette';\nexport type { SeatView } from './crossfade/panorama';\nexport type { Analytics3DCallback } from './analytics';\nexport { buildSceneModel } from './scene/sceneModel';\n\nexport interface Venue3DInput {\n doc: ChartDoc;\n /** Expanded seats (from `expandChart`) — carry x/y + resolved eyeHeightM. */\n seats: ExpandedSeat[];\n /** Optional initial per-seat state (default all available). */\n initialState?: (seat: ExpandedSeat) => SeatState3D;\n /** CPU scene prepared by `prepareVenue3D`; avoids rebuilding on mount. */\n prepared?: PreparedVenue3D;\n}\n\nexport interface Venue3DOptions {\n /** Fired on a tap that hits a seat (GPU color-pick). Not fired on empty taps. */\n onSeatPick?: (seatId: string) => void;\n /** Overview → section drill state. A stand tap reports its owning section;\n * Overview reports null so host navigation can stay in sync. */\n onSectionFocusChange?: (sectionId: string | null) => void;\n /** Camera target changes, including internal next/previous and Overview UI. */\n onViewTargetChange?: (seatId: string | null) => void;\n /**\n * Supplies the view-from-seat panorama for the cinematic hand-off. Decoupled:\n * the caller (app/harness) owns panorama generation; view3d never imports it.\n * Called at PICK time to pre-render, so flyToSeat has zero wait on landing.\n */\n getSeatView?: (seatId: string) => SeatView | Promise<SeatView>;\n /**\n * Decoupled analytics sink. Emits the venue-view journey: `3d_opened`,\n * `3d_orbit_engaged` (first user gesture), `3d_seat_picked`,\n * `3d_cinematic_played`/`_skipped`/`_cancelled`, `3d_panorama_opened`/`_closed`.\n * Every invocation is wrapped in try/catch — a throwing sink never breaks\n * rendering. Absent = no events emitted.\n */\n onAnalytics?: Analytics3DCallback;\n /**\n * Let portrait overview framing crop the venue's far edges within a bounded\n * landscape fit so seats stay discoverable instead of shrinking the whole\n * bowl to fit. Opt-in because designer/editor previews still prefer strict\n * contain framing; validated for the buyer across `/dev/3d` fixtures.\n */\n portraitOverviewCrop?: boolean;\n}\n\nexport interface Venue3DStats extends RenderLoopStats {\n drawCalls: number;\n seatCount: number;\n}\n\nexport interface Venue3DHandle {\n dispose(): void;\n setAvailability(updates: { seatId: string; state: SeatState3D }[]): void;\n setSelection(seatIds: string[]): void;\n /** Fly the camera from the overview into `seatId` and dissolve into its\n * view-from-seat panorama. Resolves at flight end; a drag cancels it, a second\n * call retargets, dispose resolves early. Reduced-motion → a short fade. */\n flyToSeat(seatId: string): Promise<void>;\n /** Cancel a seat flight/panorama and frame the complete venue. */\n focusOverview(): void;\n resize(): void;\n stats(): Venue3DStats;\n loseContextForTest(): void;\n /** Test hook: force (or clear) the reduced-motion path. */\n setReducedMotionForTest(value: boolean | null): void;\n /** The venue's zones (id, label, colour, seat count) in authored order. */\n zones(): SceneZone[];\n /**\n * Frame a zone: the camera moves to sit over that zone looking at what the\n * zone faces. Returns false for an unknown or empty zone.\n *\n * This is the navigation the venue's own structure implies — a buyer picks\n * \"Grand Circle\", not a set of coordinates — and it is what the 2D renderer's\n * farthest LOD rung already offers. Approaching from the zone's focal side\n * means the seats face the camera rather than presenting their backs.\n */\n focusZone(zoneId: string): boolean;\n /** The venue's seated sections (id, name, seat count) in authored order. */\n sections(): SceneSection[];\n /**\n * Frame a section — the middle rung of the venue ladder, and the one every\n * chart has. Zones are optional and a chart may author none or one; sections\n * are what a buyer picks between when \"which part of the venue\" has already\n * been answered. Returns false for an unknown or empty section.\n */\n focusSection(sectionId: string): boolean;\n /** Rows in the venue, optionally narrowed to a section. */\n rows(sectionId?: string): SceneRow[];\n /** Seats in one row, in authored order. */\n seatsInRow(rowId: string): Array<{ id: string; label: string }>;\n /** Frame a row closely enough for its on-demand seat numbers to be legible. */\n focusRow(rowId: string): boolean;\n /** The venue's floors (id, name, seat count) in authored order. */\n floors(): SceneFloor[];\n /**\n * Isolate one floor, or pass null to show the whole venue.\n *\n * Every shipped multi-floor chart puts its floors at the same base height and\n * takes relief from the sections, so all three of an opera house draw at once\n * and the balcony sits over the parterre. Unfocused floors are DIMMED rather\n * than hidden, so the buyer keeps the venue as context while looking at the\n * level they are booking. Returns false for an unknown index.\n */\n focusFloor(index: number | null): boolean;\n}\n\nconst DEG = Math.PI / 180;\nconst TAP_SLOP = 6;\nconst TAP_MS = 500;\n\nexport function mountVenue3D(\n container: HTMLElement,\n input: Venue3DInput,\n opts: Venue3DOptions = {},\n): Venue3DHandle {\n const buildStartedAt = performance.now();\n const model: SceneModel = input.prepared?.model ?? buildSceneModel(input);\n const analytics = new Analytics3D(opts.onAnalytics);\n\n const seatIdByIndex: string[] = new Array(model.seats.count);\n for (const [id, idx] of model.seats.idToIndex) seatIdByIndex[idx] = id;\n const expandedSeatById = new Map(input.seats.map((seat) => [seat.id, seat]));\n const seatIdsByRow = new Map<string, string[]>();\n for (const seat of input.seats) {\n if (!seat.rowId) continue;\n const row = seatIdsByRow.get(seat.rowId);\n if (row) row.push(seat.id);\n else seatIdsByRow.set(seat.rowId, [seat.id]);\n }\n\n // Seat → owning section (for the 3d_seat_picked event); resolved from the\n // expanded seats that already carry sectionId.\n const sectionIdBySeatId = new Map<string, string | undefined>();\n for (const s of input.seats) sectionIdBySeatId.set(s.id, s.sectionId);\n\n // Whether the chart carries any real 3D relief (authored heights/rake or\n // elevated floors) vs. degrading to flat slabs — reported with 3d_opened.\n const hasHeights = ((): boolean => {\n if (input.doc.floors?.some((f) => (f.baseHeightM ?? 0) > 0)) return true;\n const objs = input.doc.floors?.flatMap((f) => f.objects) ?? input.doc.objects;\n return objs.some((o) => o.type === 'section'\n && (((o as { height?: number }).height ?? 0) > 0 || ((o as { rake?: number }).rake ?? 0) > 0));\n })();\n\n let gpu: GpuScene | null = null;\n let pick: PickPipeline | null = null;\n let contextLost = false;\n let frozen = false; // GL render paused while the panorama is up\n let disposed = false;\n let selection = new Map<string, number>();\n let panorama: PanoramaHandle | null = null;\n let panoramaLoadAbort: AbortController | null = null;\n let panoramaPriorZIndex: string | null = null;\n const prefetch = new Map<string, Promise<SeatView>>();\n let flightGen = 0;\n let reducedForced: boolean | null = null;\n /** Focused floor, or -1 for the whole venue. Survives a context restore. */\n let focusedFloor = -1;\n\n const raisePanoramaLayer = (): void => {\n if (panoramaPriorZIndex !== null) return;\n panoramaPriorZIndex = container.style.zIndex;\n // Buyer chrome sits outside this stacking context at z-index 5. A panorama\n // is modal, so raise the complete 3D layer while it owns the interaction.\n container.style.zIndex = '20';\n };\n const restorePanoramaLayer = (): void => {\n if (panoramaPriorZIndex === null) return;\n if (container.style.zIndex === '20') container.style.zIndex = panoramaPriorZIndex;\n panoramaPriorZIndex = null;\n };\n\n const rebuildGpu = (): void => {\n gpu = buildGpuScene(glctx.gl, model);\n pick = new PickPipeline(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 gpu.chairProgram.uniforms.uFocusFloor.value = focusedFloor;\n // A rebuilt scene has an empty chair set; force the next frame to re-gather\n // instead of trusting the last camera position.\n lastGatherX = Infinity;\n };\n\n // --- near field ------------------------------------------------------------\n // The spatial index is constructed here but its grid is built lazily, on the\n // first gather — a session that never leaves the overview never pays for it.\n const nearIndex = new NearFieldIndex(model.seats.iPosition, model.seats.count);\n const nearBuf = new Int32Array(CHAIR_MAX_INSTANCES);\n let lastGatherX = Infinity;\n let lastGatherZ = Infinity;\n\n /**\n * Re-gather the near set when the camera has moved far enough to invalidate\n * it — not every frame.\n *\n * The cheap early-out is the venue bound: if the camera is further from the\n * whole seat cloud than the gather radius, nothing can qualify and the grid is\n * never even touched. That is the state the overview, the intro ease and every\n * frame of a wide orbit are in, so the common case costs one distance test.\n */\n const updateNearField = (): void => {\n if (!gpu) return;\n const cam = orbit.camera.position;\n const moved = Math.hypot(cam.x - lastGatherX, cam.z - lastGatherZ);\n if (moved < CHAIR_REBUILD_M) return;\n lastGatherX = cam.x;\n lastGatherZ = cam.z;\n const outside = Math.hypot(cam.x - model.bounds.center[0], cam.z - model.bounds.center[2])\n - model.bounds.radius;\n if (outside > CHAIR_GATHER_M) {\n if (gpu.nearSeatCount()) gpu.setNearSeats(nearBuf, 0);\n return;\n }\n const n = nearIndex.gather(cam.x, cam.z, CHAIR_GATHER_M, nearBuf);\n if (n === 0 && gpu.nearSeatCount() === 0) return;\n gpu.setNearSeats(nearBuf, n);\n };\n\n const glctx = new GLContext(container, {\n onContextLost: () => {\n if (!disposed) analytics.contextLost();\n contextLost = true;\n loop.stop();\n gpu = null;\n pick = null;\n },\n onContextRestored: () => {\n rebuildGpu();\n contextLost = false;\n if (!disposed) analytics.contextRestored();\n loop.requestRender();\n },\n });\n\n const orbit = new OrbitCamera(\n glctx.gl,\n glctx.canvas,\n () => loop.requestRender(),\n () => analytics.orbitEngaged(), // first real drag/wheel/pinch (not the intro ease)\n );\n // setAspect BEFORE frame so the fit clears the horizontal FOV too (centred with\n // margin on a wide designer canvas rather than parked low-left).\n orbit.setAspect(glctx.aspect);\n // Enter from the stage side (camera behind the stage, every tier facing you).\n // When the focal sits at the centre (in-the-round) there is no stage side —\n // fall back to the fixed architectural angle.\n const stageAzimuth = ((): number | undefined => {\n const dx = model.focalWorld[0] - model.bounds.center[0];\n const dz = model.focalWorld[2] - model.bounds.center[2];\n return Math.hypot(dx, dz) > model.bounds.radius * 0.12 ? Math.atan2(dx, dz) : undefined;\n })();\n orbit.frame(model.bounds, true, stageAzimuth, opts.portraitOverviewCrop === true);\n\n const cinematic = new Cinematic(orbit.camera);\n\n // Labels are DOM, projected from world anchors — see labels.ts for why. The\n // container must establish a positioning context or the overlay would anchor\n // to the page instead of the canvas.\n const restoreContainerPosition = establishPositioningContext(container);\n const labelOverlay = new LabelOverlay(container, {\n fontFamily: input.doc.theme?.fontFamily,\n ink: input.doc.theme?.textColor,\n });\n labelOverlay.setLabels(model.labels);\n const baseLabels = model.labels;\n // While a buyer is inside a section, taps are deliberately scoped to that\n // section. Nearby tiers remain visible as spatial context, but must not steal\n // a tap through the GPU depth buffer when their projected dots overlap.\n let focusedSectionId: string | null = null;\n const showSectionSeatLabels = (sectionId: string | null): void => {\n focusedSectionId = sectionId;\n labelOverlay.setForcedDense(!!sectionId);\n if (!sectionId) {\n labelOverlay.setLabels(baseLabels);\n return;\n }\n const sectionSeats = input.seats.filter((seat) => seat.sectionId === sectionId);\n const seatIds = new Set(sectionSeats.map((seat) => seat.id));\n const rowIds = new Set(sectionSeats.map((seat) => seat.rowId));\n const focusedBase = baseLabels.filter((label) => (\n label.kind !== 'row' && label.kind !== 'seat'\n ) || (\n label.kind === 'row' ? rowIds.has(label.id.slice('row:'.length)) : seatIds.has(label.id.slice('seat:'.length))\n ));\n if (model.seatCount <= 6_000) {\n labelOverlay.setLabels(focusedBase);\n return;\n }\n const labels = sectionSeats.flatMap((seat) => {\n const index = model.seats.idToIndex.get(seat.id);\n if (index === undefined) return [];\n const offset = index * 3;\n return [{\n id: `seat:${seat.id}`,\n kind: 'seat' as const,\n text: seat.displayLabel || seat.label,\n anchor: [\n model.seats.iPosition[offset],\n model.seats.iPosition[offset + 1] + 0.55,\n model.seats.iPosition[offset + 2],\n ] as [number, number, number],\n }];\n });\n labelOverlay.setLabels([...focusedBase, ...labels]);\n };\n\n rebuildGpu();\n\n const loop = new RenderLoop((/* dt */) => {\n if (contextLost || !gpu || frozen) return false;\n const flying = cinematic.active;\n const moving = flying ? cinematic.update(performance.now()) : orbit.update();\n\n const lod = computeSeatLod(orbit.currentDistance, model.bounds.radius);\n const u = gpu.seatProgram.uniforms;\n u.uSeatScale.value = lod.scale;\n u.uSeatFade.value = lod.fade;\n // Relax the dot's pixel floor as the venue recedes — see SeatLod.minPixels\n // for why holding 2.5 px all the way out is what welds distant rows into one\n // mass. The pick pass syncs this same value, so the hit mask never drifts\n // off the drawn dot.\n u.uMinPixels.value = lod.minPixels;\n u.uPixelToWorld.value = (2 * Math.tan((orbit.camera.fov * DEG) / 2)) / Math.max(1, glctx.pixelHeight);\n updateNearField();\n\n glctx.renderer.render({ scene: gpu.background, clear: true });\n glctx.renderer.render({ scene: gpu.main, camera: orbit.camera, clear: false });\n // After the render, so the camera's matrices are the ones just drawn with —\n // projecting from stale matrices makes labels lag the venue by a frame.\n labelOverlay.update(\n orbit.camera.projectionViewMatrix as unknown as ArrayLike<number>,\n glctx.canvas.clientWidth || 1,\n glctx.canvas.clientHeight || 1,\n orbit.currentDistance,\n model.bounds.radius,\n // The dense label rungs rank by real distance from the eye, not by the\n // orbit radius — at the arrival pose those are wildly different numbers.\n [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z],\n );\n return moving;\n });\n\n const ro = typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver(() => handle.resize())\n : null;\n ro?.observe(container);\n\n const setSelection = (ids: string[]): void => {\n const baseStateIndex = (id: string): number | undefined => {\n const idx = model.seats.idToIndex.get(id);\n return idx === undefined ? undefined : model.seats.iState[idx];\n };\n const { updates, next } = diffSelection(selection, ids, baseStateIndex);\n selection = next;\n if (updates.length) {\n const runs = applySeatStates(model.seats, updates);\n if (gpu) gpu.uploadSeatStateRuns(runs);\n loop.requestRender();\n }\n };\n\n // --- cinematic / panorama ---\n const reducedMotion = (): boolean => {\n if (reducedForced !== null) return reducedForced;\n return typeof window !== 'undefined' && !!window.matchMedia\n && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n };\n\n const PREFETCH_CAP = 8;\n const ensureSeatView = (seatId: string): Promise<SeatView> | null => {\n if (!opts.getSeatView) return null;\n let p = prefetch.get(seatId);\n if (!p) {\n p = Promise.resolve(opts.getSeatView(seatId));\n prefetch.set(seatId, p);\n // A network/decode failure must not poison this seat for the lifetime of\n // the 3D mount. Drop only the promise that actually failed; a newer retry\n // may already have replaced it by the time this rejection settles.\n void p.catch(() => {\n if (prefetch.get(seatId) === p) prefetch.delete(seatId);\n });\n // Bound the cache (LRU-ish): drop the oldest inserted entries past the cap.\n while (prefetch.size > PREFETCH_CAP) {\n const oldest = prefetch.keys().next().value as string | undefined;\n if (oldest === undefined) break;\n prefetch.delete(oldest);\n }\n }\n return p;\n };\n\n const resolvedSeatViewPose = (seatId: string, idx: number): { eye: Vec3Arr; focal: Vec3Arr } => {\n const seat = expandedSeatById.get(seatId);\n const floorIndex = Math.max(0, Math.round(model.seats.iFloor[idx] ?? 0));\n const floor = input.doc.floors?.[floorIndex];\n const focalPoint = seat?.focalPoint ?? floor?.focalPoint ?? input.doc.focalPoint;\n const deck: Vec3Arr = [\n model.seats.iPosition[idx * 3],\n model.seats.iPosition[idx * 3 + 1],\n model.seats.iPosition[idx * 3 + 2],\n ];\n return seatViewPose(deck, focalPoint, floor?.baseHeightM ?? 0);\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: HTMLDivElement | null = null;\n const layoutArriveChip = (): void => {\n if (!arriveChip) return;\n const narrow = (container.clientWidth || glctx.canvas.clientWidth) <= 520;\n Object.assign(arriveChip.style, narrow ? {\n left: '12px', right: '12px', bottom: '70px', transform: 'none', justifyContent: 'center',\n } : {\n left: '50%', right: 'auto', bottom: '18px', transform: 'translateX(-50%)', justifyContent: 'initial',\n });\n };\n const removeArriveChip = (): void => {\n arriveChip?.remove();\n arriveChip = null;\n };\n const showArriveChip = (seatId: string, gen: number, retry = false): HTMLDivElement | null => {\n removeArriveChip();\n if (disposed) return null;\n const controls = document.createElement('div');\n controls.setAttribute('role', 'group');\n Object.assign(controls.style, {\n position: 'absolute',\n display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '6px', zIndex: '4',\n } as Partial<CSSStyleDeclaration>);\n\n const seat = expandedSeatById.get(seatId);\n const section = seat?.sectionId ? model.sections.find((candidate) => candidate.id === seat.sectionId) : undefined;\n const seatLabel = seat?.label ?? seatId;\n controls.setAttribute('aria-label', `Explore views near ${section?.label ? `${section.label}, ` : ''}seat ${seatLabel}`);\n const location = document.createElement('span');\n location.textContent = ['Live 3D seat view', section?.label, seatLabel].filter(Boolean).join(' · ');\n Object.assign(location.style, {\n maxWidth: 'min(320px, calc(100vw - 32px))', padding: '5px 10px', borderRadius: '999px',\n overflow: 'hidden', color: '#d9e3f5', background: 'rgba(12,18,32,0.72)',\n font: '600 11px/1.2 inherit', textOverflow: 'ellipsis', whiteSpace: 'nowrap',\n backdropFilter: 'blur(6px)',\n } as Partial<CSSStyleDeclaration>);\n controls.appendChild(location);\n const actions = document.createElement('div');\n Object.assign(actions.style, {\n display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '6px',\n } as Partial<CSSStyleDeclaration>);\n controls.appendChild(actions);\n const row = seat?.rowId ? seatIdsByRow.get(seat.rowId) : undefined;\n const at = row?.indexOf(seatId) ?? -1;\n const neighbours = at >= 0 ? [row?.[at - 1], row?.[at + 1]] : [undefined, undefined];\n const addButton = (label: string, ariaLabel: string, action: () => void, primary = false): void => {\n const button = document.createElement('button');\n button.type = 'button';\n button.textContent = label;\n button.setAttribute('aria-label', ariaLabel);\n Object.assign(button.style, {\n minHeight: '44px', padding: primary ? '10px 16px' : '10px',\n borderRadius: '999px', background: 'rgba(12,18,32,0.78)', color: '#eef1f8',\n border: '1px solid rgba(150,165,205,0.4)', backdropFilter: 'blur(6px)',\n font: '600 13px/1 inherit', cursor: 'pointer', whiteSpace: 'nowrap',\n } as Partial<CSSStyleDeclaration>);\n if (!primary) button.style.minWidth = '44px';\n if (primary) button.dataset.panoramaTrigger = '';\n button.addEventListener('click', action);\n actions.appendChild(button);\n };\n if (neighbours[0]) {\n const label = expandedSeatById.get(neighbours[0])?.label ?? neighbours[0];\n addButton('‹', `View previous seat ${label}`, () => { void flyToSeat(neighbours[0]!); });\n }\n if (opts.getSeatView) {\n addButton(retry ? '↻ Retry 360° panorama' : '◉ Open 360° panorama', `${retry ? 'Retry' : 'Open'} the 360° panorama from seat ${seatLabel}`, () => {\n if (disposed || gen !== flightGen) { removeArriveChip(); return; }\n removeArriveChip();\n void openPanorama(seatId, 400, gen);\n }, true);\n }\n if (neighbours[1]) {\n const label = expandedSeatById.get(neighbours[1])?.label ?? neighbours[1];\n addButton('›', `View next seat ${label}`, () => { void flyToSeat(neighbours[1]!); });\n }\n if (!actions.childElementCount) return null;\n container.appendChild(controls);\n arriveChip = controls;\n layoutArriveChip();\n return controls;\n };\n\n // --- explicit navigation mode ----------------------------------------------\n // Panning existed from day one, but only behind Shift/right/middle drag and\n // two fingers. That made the venue feel bolted to its dark ground plane. Keep\n // both gestures visible and make Move the default once a stand is entered.\n const navigationModeChip = document.createElement('div');\n navigationModeChip.setAttribute('role', 'group');\n navigationModeChip.setAttribute('aria-label', '3D navigation mode');\n Object.assign(navigationModeChip.style, {\n position: 'absolute', left: '14px', top: '62px', display: 'flex', gap: '3px',\n padding: '3px', borderRadius: '999px', zIndex: '4',\n background: 'rgba(12,18,32,0.72)', border: '1px solid rgba(150,165,205,0.35)',\n backdropFilter: 'blur(6px)',\n } as Partial<CSSStyleDeclaration>);\n const rotateModeButton = document.createElement('button');\n rotateModeButton.type = 'button';\n rotateModeButton.textContent = '↻ Rotate';\n rotateModeButton.setAttribute('aria-label', 'Drag to rotate the 3D venue');\n const moveModeButton = document.createElement('button');\n moveModeButton.type = 'button';\n moveModeButton.textContent = '✥ Move';\n moveModeButton.setAttribute('aria-label', 'Drag to move the 3D venue left, right, up or down');\n const zoomOutButton = document.createElement('button');\n zoomOutButton.type = 'button';\n zoomOutButton.textContent = '−';\n zoomOutButton.setAttribute('aria-label', 'Zoom out of the 3D venue');\n zoomOutButton.title = 'Zoom out';\n const zoomInButton = document.createElement('button');\n zoomInButton.type = 'button';\n zoomInButton.textContent = '+';\n zoomInButton.setAttribute('aria-label', 'Zoom into the 3D venue');\n zoomInButton.title = 'Zoom in';\n for (const button of [rotateModeButton, moveModeButton, zoomOutButton, zoomInButton]) {\n Object.assign(button.style, {\n minHeight: '34px', padding: '6px 10px', border: '0', borderRadius: '999px',\n color: '#c9d4ea', background: 'transparent', font: '600 11px/1 inherit',\n cursor: 'pointer', whiteSpace: 'nowrap',\n } as Partial<CSSStyleDeclaration>);\n if (button === zoomOutButton || button === zoomInButton) {\n button.style.minWidth = '34px';\n button.style.padding = '6px';\n button.style.fontSize = '17px';\n }\n navigationModeChip.appendChild(button);\n }\n const setNavigationMode = (mode: 'orbit' | 'pan'): void => {\n orbit.setPrimaryDragMode(mode);\n const rotateActive = mode === 'orbit';\n rotateModeButton.setAttribute('aria-pressed', String(rotateActive));\n moveModeButton.setAttribute('aria-pressed', String(!rotateActive));\n rotateModeButton.style.background = rotateActive ? 'rgba(96,110,150,0.48)' : 'transparent';\n moveModeButton.style.background = rotateActive ? 'transparent' : 'rgba(96,110,150,0.48)';\n navigationModeChip.title = rotateActive\n ? 'Drag to rotate · Shift-drag to move'\n : 'Drag to move · Shift-drag to rotate';\n };\n rotateModeButton.addEventListener('click', () => setNavigationMode('orbit'));\n moveModeButton.addEventListener('click', () => setNavigationMode('pan'));\n zoomOutButton.addEventListener('click', () => orbit.zoomBy(1.22));\n zoomInButton.addEventListener('click', () => orbit.zoomBy(0.82));\n setNavigationMode('orbit');\n container.appendChild(navigationModeChip);\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 const focusOverview = (): void => {\n // The panorama owns the screen whenever it is up. Guarding on `frozen`\n // alone was equivalent only while every panorama froze the loop; the\n // in-scene sphere deliberately does not, so ask about the panorama itself.\n if (disposed) return;\n if (panorama) {\n panorama.dispose();\n panorama = null;\n analytics.panoramaClosed();\n }\n panoramaLoadAbort?.abort();\n panoramaLoadAbort = null;\n restorePanoramaLayer();\n overviewChip.style.display = '';\n labelOverlay.setVisible(true);\n frozen = false;\n cancelFlight();\n removeArriveChip();\n setNavigationMode('orbit');\n if (reducedMotion()) orbit.frame(model.bounds, false, stageAzimuth, opts.portraitOverviewCrop === true);\n else orbit.frameSoft(model.bounds, stageAzimuth, opts.portraitOverviewCrop === true);\n opts.onViewTargetChange?.(null);\n showSectionSeatLabels(null);\n opts.onSectionFocusChange?.(null);\n loop.requestRender();\n };\n overviewChip.addEventListener('click', focusOverview);\n container.appendChild(overviewChip);\n\n const openPanorama = async (seatId: string, fadeMs: number, gen: number): Promise<void> => {\n panoramaLoadAbort?.abort();\n const loadAbort = new AbortController();\n panoramaLoadAbort = loadAbort;\n const seatIndex = model.seats.idToIndex.get(seatId);\n if (seatIndex === undefined) { orbit.syncFromCamera(); return; }\n const seatLabel = expandedSeatById.get(seatId)?.label ?? seatId;\n const seatPose = resolvedSeatViewPose(seatId, seatIndex);\n const viewPromise = ensureSeatView(seatId);\n if (!viewPromise) {\n loadAbort.abort();\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n orbit.syncFromCamera();\n return;\n } // no panorama source\n frozen = true;\n loop.stop(); // freeze the GL at the seat pose; panorama fades in over it\n let view: SeatView;\n try {\n view = await viewPromise;\n } catch {\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n analytics.panoramaFailed(seatId, 'source');\n // Only unfreeze if we still own the flight — a retarget during the await\n // has already reset `frozen` and taken over the loop.\n if (!disposed && gen === flightGen) {\n frozen = false;\n orbit.resumeAfterFlight(seatPose.focal);\n loop.requestRender();\n const controls = showArriveChip(seatId, gen, true);\n requestAnimationFrame(() => controls?.querySelector<HTMLButtonElement>('[data-panorama-trigger]')?.focus());\n }\n return;\n }\n // Superseded during the await (retarget/cancel) or disposed: bail WITHOUT\n // touching frozen/loop — the newer flight owns the freeze state now, and\n // mounting this stale seat's panorama would be wrong.\n if (disposed || gen !== flightGen) {\n loadAbort.abort();\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n return;\n }\n removeArriveChip();\n const onClose = (): void => {\n loadAbort.abort();\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n panorama = null;\n restorePanoramaLayer();\n overviewChip.style.display = '';\n labelOverlay.setVisible(true);\n frozen = false;\n analytics.panoramaClosed();\n orbit.resumeAfterFlight(seatPose.focal);\n loop.requestRender();\n // Back in the live scene at the seat — offer the 360 again.\n const controls = showArriveChip(seatId, flightGen);\n requestAnimationFrame(() => controls?.querySelector<HTMLButtonElement>('[data-panorama-trigger]')?.focus());\n };\n // Prefer the in-scene sphere: it keeps the loop running, so the arrival is a\n // cross-fade between two views of the same place from the same camera\n // rather than a bitmap dropped over a frozen frame. It needs the image to\n // decode and a GL context that still exists, so it can decline — and the\n // DOM viewer stays as the fallback because a 360 that fails to mount must\n // never leave the buyer worse off than no 360 at all.\n // A GENERATED panorama is a 2048×1024 picture of the very scene already on\n // screen. Through a 106° window on a 3072-device-pixel viewport that is a\n // 5× magnification of ~600 source pixels, and it reads soft next to the\n // arrival frame the buyer was just looking at. So it is not drawn: the\n // camera looks around the real geometry instead, which is sharp at any\n // field of view and gets better every time the scene does. Real photographs\n // still go on the sphere, where their 5760–8192 px capture has detail worth\n // showing.\n const sceneMode = view.generated === true;\n const disclosure = seatViewDisclosure(view);\n raisePanoramaLayer();\n overviewChip.style.display = 'none';\n const effectiveFadeMs = reducedMotion() ? 0 : fadeMs;\n const spherical = gpu && !contextLost\n ? await mountPanoramaSphere(container, sceneMode ? null : view, {\n gl: glctx.gl,\n scene: gpu.main,\n camera: orbit.camera,\n requestRender: () => loop.requestRender(),\n cameraOriginWorld: sceneMode ? seatPose.eye : undefined,\n focalWorld: seatPose.focal,\n }, { fadeMs: effectiveFadeMs, seatLabel, disclosure, onClose, signal: loadAbort.signal })\n : null;\n // Superseded while the image was decoding — the newer flight owns the state.\n if (disposed || gen !== flightGen) {\n loadAbort.abort();\n if (panoramaLoadAbort === loadAbort) panoramaLoadAbort = null;\n spherical?.dispose();\n overviewChip.style.display = '';\n restorePanoramaLayer();\n return;\n }\n if (spherical) {\n // The venue keeps rendering behind the sphere, so the freeze that the DOM\n // path depends on must be lifted or the cross-fade has nothing to fade\n // from. The sphere itself pins the camera.\n //\n // Sync orbit to where the flight actually parked the camera FIRST. Its\n // damped pose is still whatever it was before the flight, so the next\n // `orbit.update()` would read that as movement, call `applyPosition()`\n // and yank the camera off the seat mid-fade. Syncing equalises pose and\n // target so `update()` reports \"not moving\" and leaves the camera alone —\n // which is what makes it safe to run the loop at all here.\n orbit.syncFromCamera();\n // Labels are DOM and the sphere is GL, so every row and seat label would\n // otherwise float on top of the panorama.\n labelOverlay.setVisible(false);\n frozen = false;\n loop.requestRender();\n panorama = spherical;\n } else {\n analytics.panoramaFallback(seatId);\n panorama = mountPanorama(container, view, {\n fadeMs: effectiveFadeMs, seatLabel, disclosure, onClose, signal: loadAbort.signal,\n });\n }\n analytics.panoramaOpened();\n };\n\n const cancelFlight = (): void => {\n flightGen++; // supersede any pending .then(openPanorama)\n panoramaLoadAbort?.abort();\n panoramaLoadAbort = null;\n if (cinematic.active) {\n cinematic.cancel();\n orbit.resumeAfterFlight(model.focalWorld);\n }\n };\n\n const flyToSeat = (seatId: string): Promise<void> => {\n if (disposed || !gpu) return Promise.resolve();\n const idx = model.seats.idToIndex.get(seatId);\n if (idx === undefined) return Promise.resolve();\n opts.onViewTargetChange?.(seatId);\n // Reset the freeze unconditionally: a previous flight may have set frozen=true\n // inside openPanorama's pre-await window without a panorama ever mounting.\n if (panorama) {\n panorama.dispose();\n panorama = null;\n overviewChip.style.display = '';\n restorePanoramaLayer();\n }\n panoramaLoadAbort?.abort();\n panoramaLoadAbort = null;\n frozen = false;\n\n const gen = ++flightGen;\n removeArriveChip();\n const { eye: seatEye, focal } = resolvedSeatViewPose(seatId, idx);\n const start: Vec3Arr = [orbit.camera.position.x, orbit.camera.position.y, orbit.camera.position.z];\n const { 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(focal);\n loop.requestRender();\n showArriveChip(seatId, gen);\n });\n };\n\n /** Enter the stand itself. This is intentionally separate from picking a\n * ticket: overview tap → section, focused-section tap → seat. */\n const focusSectionCamera = (sectionId: string): boolean => {\n const sec = model.sections.find((candidate) => candidate.id === sectionId);\n if (!sec || sec.seatCount === 0) return false;\n cinematic.cancel();\n removeArriveChip();\n setNavigationMode('pan');\n showSectionSeatLabels(sectionId);\n const dx = sec.focalWorld[0] - sec.center[0];\n const dz = sec.focalWorld[2] - sec.center[2];\n const azimuth = Math.hypot(dx, dz) > sec.radius * 0.12 ? Math.atan2(dx, dz) : undefined;\n // Deliberately crop the section edges: this is an ENTER-section action,\n // not a second overview. Seats become practical tap targets and the row /\n // sampled seat numbers are readable.\n orbit.frame({ center: sec.center, radius: Math.max(5, sec.radius * 0.5) }, false, azimuth);\n opts.onSectionFocusChange?.(sectionId);\n loop.requestRender();\n return true;\n };\n\n // --- Tap → pick / flight-cancel ---\n let downX = 0, downY = 0, downT = 0, downId = -1, moved = false, suppressTap = false;\n const onDown = (e: PointerEvent): void => {\n if (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 /**\n * Pick the visible seat target nearest a tap while drilled into a section.\n *\n * The overview uses the O(1) GPU colour pass. In a close raked section the\n * chair/deck depth geometry can win that pass even though the buyer clearly\n * tapped the large seat dot. Projecting just the focused section's seats is a\n * bounded, tap-only fallback (normally a few hundred points) and, critically,\n * prevents the neighbouring tier retained for context from stealing the tap.\n */\n const pickNearestProjectedSeat = (\n clientX: number,\n clientY: number,\n sectionId: string | null,\n maxDistance: number,\n bookableOnly: boolean,\n ): number => {\n const rect = glctx.canvas.getBoundingClientRect();\n const width = glctx.canvas.clientWidth || rect.width || 1;\n const height = glctx.canvas.clientHeight || rect.height || 1;\n const tapX = clientX - rect.left;\n const tapY = clientY - rect.top;\n // The caller chooses a section/overview tolerance. Distance wins; depth\n // only breaks near-ties.\n const maxDistanceSq = maxDistance * maxDistance;\n let bestIndex = -1;\n let bestDistanceSq = maxDistanceSq;\n let bestDepth = Infinity;\n for (const seat of input.seats) {\n if (sectionId && seat.sectionId !== sectionId) continue;\n const index = model.seats.idToIndex.get(seat.id);\n if (index === undefined) continue;\n // The focused view is a buying surface, not an inventory inspector. Snap\n // the finger target to the nearest bookable (or already-selected) seat;\n // held, sold and filtered seats stay visible but cannot produce a dead\n // optimistic pick that the authoritative picker immediately rejects.\n if (bookableOnly) {\n const state = Math.round(model.seats.iState[index] ?? 0);\n if (state !== 0 && state !== 3) continue;\n }\n const offset = index * 3;\n const screen = projectToScreen(\n orbit.camera.projectionViewMatrix as unknown as ArrayLike<number>,\n [\n model.seats.iPosition[offset],\n // Match the visible chair/number rather than the deck-level instance\n // origin. At strong zoom the vertical difference is dozens of pixels\n // and a perfectly reasonable click on the chair otherwise misses.\n model.seats.iPosition[offset + 1] + 0.55,\n model.seats.iPosition[offset + 2],\n ],\n width,\n height,\n );\n if (!screen.visible) continue;\n const dx = screen.x - tapX;\n const dy = screen.y - tapY;\n const distanceSq = dx * dx + dy * dy;\n if (\n distanceSq < bestDistanceSq\n || (Math.abs(distanceSq - bestDistanceSq) < 1 && screen.depth < bestDepth)\n ) {\n bestIndex = index;\n bestDistanceSq = distanceSq;\n bestDepth = screen.depth;\n }\n }\n return bestIndex;\n };\n const 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 let idx = focusedSectionId\n ? pickNearestProjectedSeat(e.clientX, e.clientY, focusedSectionId, 44, true)\n : -1;\n if (focusedSectionId && idx < 0) {\n // A buyer may wheel back out instead of pressing Overview. In that case\n // the first section is still the active seat-picking scope, but other\n // stands are visible and should remain valid navigation targets. If the\n // tap misses a bookable seat in the active section, transfer focus to the\n // visible stand under the pointer rather than silently retaining the old\n // section forever.\n // This is a stand-navigation target, not an exact ticket target: allow a\n // little space around dots so the tier/deck itself still feels clickable.\n const sectionIndex = pickNearestProjectedSeat(e.clientX, e.clientY, null, 72, false);\n const sectionSeatId = sectionIndex >= 0 ? seatIdByIndex[sectionIndex] : undefined;\n const nextSectionId = sectionSeatId ? sectionIdBySeatId.get(sectionSeatId) : undefined;\n if (nextSectionId && nextSectionId !== focusedSectionId && focusSectionCamera(nextSectionId)) return;\n }\n if (!focusedSectionId) {\n 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 idx = pick.pick(orbit.camera, x, y, radius);\n // Labels and the gaps between overview dots are still visibly part of a\n // stand. If the exact GPU mask misses, use a larger section-level target.\n if (idx < 0) idx = pickNearestProjectedSeat(e.clientX, e.clientY, null, 42, false);\n const overviewSeatId = idx >= 0 ? seatIdByIndex[idx] : undefined;\n const sectionId = overviewSeatId ? sectionIdBySeatId.get(overviewSeatId) : undefined;\n if (sectionId && focusSectionCamera(sectionId)) return;\n }\n if (idx < 0 || idx >= seatIdByIndex.length) {\n if (selection.size) 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 focusOverview,\n resize() {\n const { width, height } = glctx.resize();\n orbit.setAspect(width / Math.max(1, height));\n layoutArriveChip();\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 gpu.chairProgram.uniforms.uFocusFloor.value = value;\n }\n focusedFloor = value;\n setNavigationMode('orbit');\n if (index !== null) {\n const f = model.floors[index];\n if (f.seatCount > 0) {\n cinematic.cancel();\n orbit.frame({ center: f.center, radius: f.radius * 1.25 });\n }\n }\n loop.requestRender();\n return true;\n },\n zones(): SceneZone[] {\n return model.zones;\n },\n focusZone(zoneId: string): boolean {\n const zone = model.zones.find((z) => z.id === zoneId);\n if (!zone || zone.seatCount === 0) return false;\n cinematic.cancel();\n setNavigationMode('orbit');\n showSectionSeatLabels(null);\n opts.onSectionFocusChange?.(null);\n // Approach from the side the zone faces, so its seats present their fronts\n // rather than their backs — the same reasoning as the venue's intro shot.\n const dx = zone.focalWorld[0] - zone.center[0];\n const dz = zone.focalWorld[2] - zone.center[2];\n const azimuth = Math.hypot(dx, dz) > zone.radius * 0.12 ? Math.atan2(dx, dz) : undefined;\n // Padded past the zone's own radius: framing exactly to its edge reads as\n // cropped, and a buyer needs the neighbouring geometry to know where in the\n // venue they have landed.\n orbit.frame({ center: zone.center, radius: zone.radius * 1.25 }, false, azimuth);\n loop.requestRender();\n return true;\n },\n sections(): SceneSection[] {\n return model.sections;\n },\n focusSection(sectionId: string): boolean {\n return focusSectionCamera(sectionId);\n },\n rows(sectionId?: string): SceneRow[] {\n return sectionId ? model.rows.filter((row) => row.sectionId === sectionId) : model.rows;\n },\n seatsInRow(rowId: string): Array<{ id: string; label: string }> {\n return input.seats\n .filter((seat) => seat.rowId === rowId)\n .map((seat) => ({ id: seat.id, label: seat.displayLabel || seat.label }));\n },\n focusRow(rowId: string): boolean {\n const row = model.rows.find((candidate) => candidate.id === rowId);\n if (!row || row.seatCount === 0) return false;\n cinematic.cancel();\n setNavigationMode('pan');\n if (row.sectionId) {\n showSectionSeatLabels(row.sectionId);\n opts.onSectionFocusChange?.(row.sectionId);\n }\n orbit.frame({ center: row.center, radius: Math.max(2.5, row.radius * 0.65) });\n loop.requestRender();\n return true;\n },\n setReducedMotionForTest(value) {\n reducedForced = value;\n },\n dispose() {\n disposed = true;\n removeArriveChip();\n overviewChip.remove();\n navigationModeChip.remove();\n cancelFlight();\n loop.stop();\n labelOverlay.dispose();\n ro?.disconnect();\n if (panorama) { panorama.dispose(); panorama = null; }\n restorePanoramaLayer();\n restoreContainerPosition();\n glctx.canvas.removeEventListener('pointerdown', onDown);\n glctx.canvas.removeEventListener('pointermove', onMove);\n glctx.canvas.removeEventListener('pointerup', onUp);\n glctx.canvas.removeEventListener('pointercancel', onUp);\n orbit.dispose();\n if (pick) pick.dispose();\n if (gpu) gpu.dispose();\n gpu = null;\n pick = null;\n glctx.dispose();\n },\n };\n\n analytics.opened(model.seatCount, hasHeights, input.prepared?.buildMs ?? performance.now() - buildStartedAt);\n return handle;\n}\n","/**\n * OGL renderer + canvas lifecycle. Owns the single WebGL2 context (reused across\n * open/close so an embed never exhausts the browser's ~16-context cap), DPR\n * capping, resize, and WebGL context-loss survival.\n *\n * Context loss is handled by preventing the default (so the browser will restore)\n * and delegating rebuild to the caller: the JS-side SceneModel is the source of\n * truth, so `onContextRestored` re-uploads all GPU resources from it.\n */\n\nimport { Renderer } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\nimport { BACKGROUND } from '../palette';\n\nexport interface GLContextOptions {\n onContextLost: () => void;\n onContextRestored: () => void;\n}\n\n/** DPR ceiling: 2.0, or 1.5 on low-memory devices (fragment cost is DPR²). */\nfunction computeDpr(): number {\n const raw = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n const mem = (navigator as unknown as { deviceMemory?: number }).deviceMemory;\n const cap = typeof mem === 'number' && mem <= 4 ? 1.5 : 2.0;\n return Math.min(raw, cap);\n}\n\nexport class GLContext {\n readonly renderer: Renderer;\n readonly gl: OGLRenderingContext;\n readonly canvas: HTMLCanvasElement;\n private container: HTMLElement;\n /** Current clear colour, so a context restore repaints the themed background. */\n private clearRgb: [number, number, number] = [0, 0, 0];\n private lostHandler: (e: Event) => void;\n private restoredHandler: () => void;\n\n /**\n * Repaint the clear colour from the chart's theme.\n *\n * The clear shows for one frame before the background triangle draws, and on\n * any frame the scene does not cover — so leaving it at the library default\n * flashes SeatLayer grey into a white-labelled venue.\n */\n setClearColor(rgb: readonly number[]): void {\n this.clearRgb = [rgb[0], rgb[1], rgb[2]];\n this.gl.clearColor(rgb[0], rgb[1], rgb[2], 1);\n }\n\n /** The clear colour currently set (the pick pass restores through this). */\n get clearColor(): readonly number[] {\n return this.clearRgb;\n }\n\n constructor(container: HTMLElement, opts: GLContextOptions) {\n this.container = container;\n this.canvas = document.createElement('canvas');\n this.canvas.style.display = 'block';\n this.canvas.style.width = '100%';\n this.canvas.style.height = '100%';\n this.canvas.style.touchAction = 'none';\n this.canvas.tabIndex = 0;\n this.canvas.setAttribute('role', 'application');\n this.canvas.setAttribute(\n 'aria-label',\n 'Interactive 3D venue. Drag or use arrow keys to look around; pinch, scroll, plus or minus to zoom.',\n );\n\n this.renderer = new Renderer({\n canvas: this.canvas,\n dpr: computeDpr(),\n alpha: false,\n antialias: false,\n depth: true,\n stencil: false,\n powerPreference: 'high-performance',\n webgl: 2,\n });\n this.gl = this.renderer.gl;\n this.clearRgb = [BACKGROUND.top[0], BACKGROUND.top[1], BACKGROUND.top[2]];\n this.gl.clearColor(this.clearRgb[0], this.clearRgb[1], this.clearRgb[2], 1);\n\n container.appendChild(this.canvas);\n\n\n\n this.lostHandler = (e: Event) => {\n e.preventDefault();\n opts.onContextLost();\n };\n this.restoredHandler = () => opts.onContextRestored();\n this.canvas.addEventListener('webglcontextlost', this.lostHandler, false);\n this.canvas.addEventListener('webglcontextrestored', this.restoredHandler, false);\n\n this.resize();\n }\n\n /** Match the drawing buffer to the container's CSS box. */\n resize(): { width: number; height: number } {\n const w = Math.max(1, this.container.clientWidth || this.canvas.clientWidth || 1);\n const h = Math.max(1, this.container.clientHeight || this.canvas.clientHeight || 1);\n this.renderer.setSize(w, h);\n return { width: w, height: h };\n }\n\n get pixelHeight(): number {\n return this.renderer.height * this.renderer.dpr;\n }\n\n get aspect(): number {\n return this.renderer.width / Math.max(1, this.renderer.height);\n }\n\n dispose(): void {\n this.canvas.removeEventListener('webglcontextlost', this.lostHandler, false);\n this.canvas.removeEventListener('webglcontextrestored', this.restoredHandler, false);\n const ext = this.gl.getExtension('WEBGL_lose_context');\n if (ext) ext.loseContext();\n if (this.canvas.parentNode) this.canvas.parentNode.removeChild(this.canvas);\n }\n\n /**\n * Test hook: force a full loss→restore cycle. `restoreContext()` must be called\n * only AFTER the browser has dispatched `webglcontextlost` (calling it too soon\n * makes the browser drop the restore request), so we sequence it off a one-shot\n * listener rather than a fixed timeout.\n */\n simulateContextLossCycle(): void {\n const ext = this.gl.getExtension('WEBGL_lose_context') as\n | { loseContext(): void; restoreContext?: () => void }\n | null;\n if (!ext) return;\n ext.loseContext();\n // Chrome drops a restore requested too soon after loseContext(); a short\n // delay lets the loss settle before we ask for the context back.\n setTimeout(() => { if (ext.restoreContext) ext.restoreContext(); }, 300);\n }\n}\n","/**\n * view3d palette + seat-state model — the single source of colour truth for the\n * OGL venue view. Pure data (no GPU, no DOM) so the scene builder and the unit\n * tests share one definition. Colours are linear-ish RGB triplets in 0..1.\n *\n * Look brief (docs/3d-usp-strategy §3): desaturated cool greys for structure,\n * one warm accent for the stage, availability colours only on seats.\n */\n\nexport type SeatState3D = 'available' | 'held' | 'sold' | 'selected' | 'dimmed';\n\n/** Fixed LUT order — the per-instance `iState` float indexes this array, and the\n * fragment shader's `uStateColors` uniform is uploaded in exactly this order. */\nexport const SEAT_STATES: SeatState3D[] = ['available', 'held', 'sold', 'selected', 'dimmed'];\n\nexport function seatStateIndex(state: SeatState3D): number {\n const i = SEAT_STATES.indexOf(state);\n return i < 0 ? 0 : i;\n}\n\nexport type RGB = [number, number, number];\n\n/** Availability colours — the only saturated colours in the scene. */\nexport const SEAT_STATE_COLORS: Record<SeatState3D, RGB> = {\n available: [0.24, 0.82, 0.52],\n held: [0.95, 0.66, 0.22],\n sold: [0.34, 0.39, 0.45],\n selected: [0.24, 0.74, 1.0],\n dimmed: [0.28, 0.32, 0.37],\n};\n\n/**\n * A category colour as UPHOLSTERY — what that seat looks like from inside the\n * room rather than from above it.\n *\n * The same colour has two jobs at two ranges. On the map it is a price tier and\n * must be legible at a glance across a whole venue, so it is saturated. At eye\n * level it is a fabric-covered chair a metre away, and the saturated version\n * reads as moulded plastic: an amphitheatre whose tiers are pink, gold and blue\n * arrives at the seat looking like a toy.\n *\n * So the near-field chair wears a muted version — desaturated toward its own\n * luminance and darkened. Hue survives, which is the point: a buyer sitting in\n * the Terrace can still see where the Orchestra ends, they just are not sitting\n * in a bag of sweets. The DOT keeps the full colour, so nothing changes about\n * reading the map.\n */\nexport function upholsteryTone(rgb: RGB): RGB {\n const luma = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];\n const SAT = 0.42; // how much original hue survives\n const VALUE = 0.62; // and how much of its brightness\n return [\n (luma + (rgb[0] - luma) * SAT) * VALUE,\n (luma + (rgb[1] - luma) * SAT) * VALUE,\n (luma + (rgb[2] - luma) * SAT) * VALUE,\n ];\n}\n\n/** Flat LUT (5 × vec3) for the seat fragment shader uniform. */\nexport function seatStateColorLUT(): number[] {\n const out: number[] = [];\n for (const s of SEAT_STATES) out.push(...SEAT_STATE_COLORS[s]);\n return out;\n}\n\n/** Colour for a state index (from `iState`) — used to fill the per-instance\n * `iColor` attribute CPU-side, avoiding a dynamically-indexed array uniform. */\nexport function seatStateColorByIndex(index: number): RGB {\n const state = SEAT_STATES[index] ?? 'available';\n return SEAT_STATE_COLORS[state];\n}\n\n/** Structure palette — cool desaturated greys + one warm stage accent. */\nexport const STRUCTURE = {\n ground: [0.07, 0.085, 0.11] as RGB,\n tierTop: [0.24, 0.28, 0.34] as RGB,\n tierWall: [0.17, 0.20, 0.25] as RGB,\n /**\n * The lit performance surface.\n *\n * This was [0.42, 0.36, 0.26] and commented \"slightly emissive read\", which it\n * was not — under the scene's rig it resolved to a mid-brown slab, and from a\n * seat the stage was the DIMMEST large surface in a dark hall. That is exactly\n * backwards: the stage is the one place in a venue with light pointed at it,\n * and it is what a buyer's eye should land on when they arrive at their seat.\n *\n * Bright and warm enough to hold that role against the surrounding structure,\n * which sits around 0.24. An authored `fill` still tints it (see `buildShape`),\n * so an organizer's own stage colour survives — it is just no longer lit like\n * a basement.\n */\n stageTop: [0.86, 0.64, 0.36] as RGB,\n stageWall: [0.34, 0.26, 0.18] as RGB,\n decorTop: [0.22, 0.25, 0.29] as RGB,\n decorWall: [0.15, 0.17, 0.20] as RGB,\n gaTop: [0.24, 0.28, 0.33] as RGB,\n gaWall: [0.16, 0.19, 0.23] as RGB,\n /** Exhibition / trade-show booth stand. */\n boothTop: [0.30, 0.33, 0.38] as RGB,\n boothWall: [0.20, 0.23, 0.27] as RGB,\n /** Banquet table top — warmer, so a laid table reads apart from structure. */\n tableTop: [0.38, 0.34, 0.29] as RGB,\n tableWall: [0.24, 0.21, 0.18] as RGB,\n} as const;\n\n/** Background vertical gradient (matches the app's dark UI). */\nexport const BACKGROUND = {\n top: [0.05, 0.06, 0.08] as RGB,\n bottom: [0.10, 0.12, 0.15] as RGB,\n};\n\n/** Parse `#rrggbb` (or `#rgb`) to linear-ish 0..1 RGB; null on anything else. */\nexport function hexToRgb(hex: string | undefined): RGB | null {\n if (!hex) return null;\n let h = hex.trim();\n if (h[0] === '#') h = h.slice(1);\n if (h.length === 3) h = h.split('').map((c) => c + c).join('');\n if (h.length !== 6 || /[^0-9a-fA-F]/.test(h)) return null;\n const n = parseInt(h, 16);\n return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];\n}\n\n/** Mix two colours (a*(1-t) + b*t). */\nexport function mix(a: RGB, b: RGB, t: number): RGB {\n return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];\n}\n\n/** Desaturate toward its own luma by `amount` (0 = unchanged, 1 = grey). */\nexport function desaturate(c: RGB, amount: number): RGB {\n const l = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];\n return mix(c, [l, l, l], amount);\n}\n\n/** Scale a colour by a scalar (baked vertex AO), clamped to [0,1]. */\nexport function scaleRgb(c: RGB, k: number): RGB {\n return [Math.min(1, c[0] * k), Math.min(1, c[1] * k), Math.min(1, c[2] * k)];\n}\n","/**\n * Orbit + dolly + pan camera, damped. Drag = azimuth/polar; wheel/pinch = dolly;\n * right-drag, middle-drag or shift-drag = pan, as does a two-finger drag on\n * touch. Polar clamped [15°,80°]; distance clamped to bounds-derived limits; pan\n * clamped to a bounds-derived box; initial framing = a 3/4 view fitted to bounds.\n *\n * Pan was deliberately omitted in v1 (\"always centred on the venue focal point\"),\n * on the reasoning that a venue is a single object you orbit. That is true only\n * while the whole venue is in frame. Dolly in far enough to read seat labels — the\n * thing the low `minDist` exists to allow — and the pivot stays pinned to the\n * venue centre, so the upper tiers sit off-screen with no way to bring them back\n * except zooming out again. Reported from the marketing hero: \"if I want to move\n * this 3D down to see the upper part it is not possible.\"\n */\n\nimport { Camera, Vec3 } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\n\nconst DEG = Math.PI / 180;\nconst POLAR_MIN = 15 * DEG;\nconst POLAR_MAX = 80 * DEG;\nconst DAMP = 0.12;\nconst FOV = 35;\n/** Fit multiplier past a tight bounds-sphere fit. The 3/4 tilt makes the near\n * ground edge overhang below the fitted sphere, so a wide-shallow layout needs\n * more than a nominal 10% or its front row clips — this clears it while keeping\n * the venue centred with a comfortable margin. */\nconst FRAME_MARGIN = 1.25;\n\nexport interface OrbitBounds {\n center: [number, number, number];\n radius: number;\n}\n\nexport type PrimaryDragMode = 'orbit' | 'pan';\n\nexport class OrbitCamera {\n readonly camera: Camera;\n readonly fovY = FOV;\n private target = new Vec3();\n private azimuth = -30 * DEG;\n private polar = 55 * DEG;\n private distance = 10;\n private azT = -30 * DEG;\n private polT = 55 * DEG;\n private distT = 10;\n private minDist = 1;\n private maxDist = 100;\n private canvas: HTMLElement;\n private requestRender: () => void;\n /** Fired on the FIRST real user-driven orbit/dolly gesture (drag/wheel/pinch),\n * latched so it can drive a one-shot analytics event. Not the intro ease. */\n private onGesture?: () => void;\n private gestureFired = false;\n\n private dragging = false;\n private panning = false;\n private lastX = 0;\n private lastY = 0;\n private activePointers = new Map<number, { x: number; y: number }>();\n private pinchDist = 0;\n private pinchCx = 0;\n private pinchCy = 0;\n /** Damped pan pivot. `target` chases this the way azimuth chases `azT`. */\n private targetT = new Vec3();\n /** Venue centre + radius, so pan can be clamped to somewhere still useful. */\n private panAnchor = new Vec3();\n private panLimit = 0;\n /** What an unmodified primary-button / one-finger drag does. Section drill-in\n * switches this to pan so a buyer can slide hidden seats into view without\n * discovering a modifier gesture. Shift temporarily inverts the mode. */\n private primaryDragMode: PrimaryDragMode = 'orbit';\n\n private onPointerDown: (e: PointerEvent) => void;\n private onPointerMove: (e: PointerEvent) => void;\n private onPointerUp: (e: PointerEvent) => void;\n private onWheel: (e: WheelEvent) => void;\n private onKeyDown: (e: KeyboardEvent) => void;\n private onContextMenu: (e: Event) => void;\n\n constructor(gl: OGLRenderingContext, canvas: HTMLElement, requestRender: () => void, onGesture?: () => void) {\n this.camera = new Camera(gl, { fov: FOV, near: 0.1, far: 5000, aspect: 1 });\n this.canvas = canvas;\n this.requestRender = requestRender;\n this.onGesture = onGesture;\n\n this.onPointerDown = (e) => {\n // Guard: a synthetic/stale pointer id has no active pointer to capture.\n try { this.canvas.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (this.activePointers.size === 1) {\n // Secondary/middle always pans. For the primary button, Shift inverts\n // the visible Rotate/Move mode so both operations remain one gesture\n // away without a toolbar round-trip.\n const secondaryPan = e.button === 2 || e.button === 1;\n const primaryPan = e.button === 0\n && (e.shiftKey ? this.primaryDragMode === 'orbit' : this.primaryDragMode === 'pan');\n this.panning = secondaryPan || primaryPan;\n this.dragging = !this.panning;\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n } else if (this.activePointers.size === 2) {\n this.dragging = false;\n this.panning = false;\n this.pinchDist = this.currentPinchDistance();\n const c = this.pinchCentroid();\n this.pinchCx = c.x;\n this.pinchCy = c.y;\n }\n };\n this.onPointerMove = (e) => {\n if (!this.activePointers.has(e.pointerId)) return;\n this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (this.activePointers.size >= 2) {\n const d = this.currentPinchDistance();\n // Fingers apart (d grows) → zoom in (distance shrinks).\n if (this.pinchDist > 0) { this.dollyBy(Math.exp((this.pinchDist - d) * 0.005)); this.fireGesture(); }\n this.pinchDist = d;\n // Two fingers moving TOGETHER pan; the pinch term above already took the\n // spreading component, so the centroid carries the translation.\n const c = this.pinchCentroid();\n this.panBy(c.x - this.pinchCx, c.y - this.pinchCy);\n this.pinchCx = c.x;\n this.pinchCy = c.y;\n return;\n }\n const dx = e.clientX - this.lastX;\n const dy = e.clientY - this.lastY;\n if (this.panning) {\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n if (dx !== 0 || dy !== 0) { this.panBy(dx, dy); this.fireGesture(); }\n return;\n }\n if (!this.dragging) return;\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n if (dx !== 0 || dy !== 0) this.fireGesture();\n this.azT -= dx * 0.006;\n this.polT = Math.max(POLAR_MIN, Math.min(POLAR_MAX, this.polT - dy * 0.006));\n this.requestRender();\n };\n this.onPointerUp = (e) => {\n this.activePointers.delete(e.pointerId);\n try { this.canvas.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n if (this.activePointers.size < 2) this.pinchDist = 0;\n if (this.activePointers.size === 0) { this.dragging = false; this.panning = false; }\n };\n // Right-drag pans, so the context menu must not interrupt the gesture.\n this.onContextMenu = (e) => { e.preventDefault(); };\n this.onWheel = (e) => {\n e.preventDefault();\n // Normalise wheel delta across px / line / page modes, then clamp large\n // mouse-wheel bursts. Trackpads stay smooth while one wheel notch no\n // longer launches the camera through (or far away from) the venue.\n const unit = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? 100 : 1;\n const norm = Math.max(-2, Math.min(2, (e.deltaY * unit) / 100));\n this.dollyBy(Math.exp(norm * 0.22));\n this.fireGesture();\n };\n this.onKeyDown = (e) => {\n const step = 7 * DEG;\n if (e.shiftKey && (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown')) {\n this.panBy(\n e.key === 'ArrowLeft' ? -32 : e.key === 'ArrowRight' ? 32 : 0,\n e.key === 'ArrowUp' ? -32 : e.key === 'ArrowDown' ? 32 : 0,\n );\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n this.azT += e.key === 'ArrowLeft' ? -step : step;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n this.polT = Math.max(POLAR_MIN, Math.min(POLAR_MAX, this.polT + (e.key === 'ArrowUp' ? -step : step)));\n } else if (e.key === '+' || e.key === '=') {\n this.dollyBy(0.82);\n } else if (e.key === '-' || e.key === '_') {\n this.dollyBy(1.22);\n } else {\n return;\n }\n e.preventDefault();\n this.fireGesture();\n this.requestRender();\n };\n\n canvas.addEventListener('pointerdown', this.onPointerDown);\n canvas.addEventListener('pointermove', this.onPointerMove);\n canvas.addEventListener('pointerup', this.onPointerUp);\n canvas.addEventListener('pointercancel', this.onPointerUp);\n canvas.addEventListener('wheel', this.onWheel, { passive: false });\n canvas.addEventListener('keydown', this.onKeyDown);\n canvas.addEventListener('contextmenu', this.onContextMenu);\n }\n\n private pinchCentroid(): { x: number; y: number } {\n const pts = [...this.activePointers.values()];\n if (!pts.length) return { x: 0, y: 0 };\n let x = 0;\n let y = 0;\n for (const p of pts) { x += p.x; y += p.y; }\n return { x: x / pts.length, y: y / pts.length };\n }\n\n /**\n * Slide the orbit pivot across the camera's own screen plane.\n *\n * Scaled by distance and FOV so a pixel of drag moves the same amount of VENUE\n * under the cursor whatever the zoom: at the overview a drag sweeps the whole\n * bowl, and pushed in among the seats it nudges. A fixed world-units-per-pixel\n * would be unusable at one end or the other.\n */\n private panBy(dxPx: number, dyPx: number): void {\n const h = (this.canvas as HTMLElement).clientHeight || 1;\n // World units spanned by one pixel at the pivot's depth.\n const perPx = (2 * this.distance * Math.tan((this.fovY * DEG) / 2)) / h;\n // Camera basis on the ground-plane projection: right is perpendicular to the\n // view azimuth; \"up\" on screen maps to the horizontal forward direction,\n // damped by the tilt so a top-down view pans in the plane you can see.\n const sinA = Math.sin(this.azimuth);\n const cosA = Math.cos(this.azimuth);\n const rightX = cosA;\n const rightZ = -sinA;\n const cp = Math.cos(this.polar);\n const sp = Math.sin(this.polar);\n const fwdX = -sinA * cp;\n const fwdZ = -cosA * cp;\n this.targetT.x += (-dxPx * rightX + dyPx * fwdX) * perPx;\n this.targetT.z += (-dxPx * rightZ + dyPx * fwdZ) * perPx;\n this.targetT.y += dyPx * sp * perPx;\n this.clampPan();\n this.requestRender();\n }\n\n /** Keep the pivot within a bounds-derived box so a stray drag cannot lose the\n * venue entirely — the Overview chip should never be the only way back. */\n private clampPan(): void {\n if (this.panLimit <= 0) return;\n const lim = this.panLimit;\n const cx = this.panAnchor.x;\n const cy = this.panAnchor.y;\n const cz = this.panAnchor.z;\n this.targetT.x = Math.max(cx - lim, Math.min(cx + lim, this.targetT.x));\n this.targetT.z = Math.max(cz - lim, Math.min(cz + lim, this.targetT.z));\n this.targetT.y = Math.max(cy - lim * 0.5, Math.min(cy + lim, this.targetT.y));\n }\n\n /** One-shot: notify the first real user gesture (drives 3d_orbit_engaged). */\n private fireGesture(): void {\n if (this.gestureFired) return;\n this.gestureFired = true;\n this.onGesture?.();\n }\n\n private currentPinchDistance(): number {\n const pts = [...this.activePointers.values()];\n if (pts.length < 2) return 0;\n return Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);\n }\n\n /** Multiply target distance by `factor` (proportional zoom — same feel near\n * and far), clamped so you can swoop right down among the seats. */\n private dollyBy(factor: number): void {\n this.distT = Math.max(this.minDist, Math.min(this.maxDist, this.distT * factor));\n this.requestRender();\n }\n\n /** Programmatic zoom used by the visible camera controls. Keeping it on the\n * same dolly path as wheel, pinch and keyboard preserves all distance limits. */\n zoomBy(factor: number): void {\n this.dollyBy(factor);\n this.fireGesture();\n }\n\n /** Choose what a normal left/one-finger drag does. Shift temporarily performs\n * the other action. This is intentionally public so the view can make the\n * currently active gesture visible rather than hiding it in documentation. */\n setPrimaryDragMode(mode: PrimaryDragMode): void {\n this.primaryDragMode = mode;\n }\n\n /**\n * Fit a flattering 3/4 view to the bounds sphere. With `intro`, the camera\n * STARTS nearly top-down (matching the 2D map's orientation) and further out,\n * then the damped `update()` eases it up into the 3/4 architectural angle and\n * dollies in — the venue \"stands up\" instead of teleporting (~600ms).\n */\n frame(bounds: OrbitBounds, intro = false, stageAzimuth?: number, portraitCrop = false): void {\n this.target.set(bounds.center[0], bounds.center[1], bounds.center[2]);\n this.targetT.copy(this.target);\n this.panAnchor.copy(this.target);\n this.panLimit = Math.max(1, bounds.radius) * 1.5;\n const r = Math.max(1, bounds.radius);\n // Aspect-aware fit: the bounds sphere must clear BOTH the vertical and the\n // (aspect-narrowed) horizontal FOV, so a wide designer canvas frames the\n // chart centred with margin instead of parking it low-left. `setAspect` must\n // run before `frame` for the horizontal term to be correct.\n const halfV = (this.fovY * DEG) / 2;\n // A strict horizontal contain fit makes a wide bowl postage-stamp small in\n // portrait. UX prototypes may opt into a bounded crop: treat very narrow\n // screens as 0.82 aspect for OVERVIEW distance only, allowing the venue's\n // outer edges to sit just beyond the screen while its seats remain useful.\n const aspect = portraitCrop ? Math.max(0.82, this.camera.aspect || 1) : (this.camera.aspect || 1);\n const halfH = Math.atan(Math.tan(halfV) * aspect);\n const fit = Math.max(r / Math.tan(halfV), r / Math.tan(halfH));\n // Enter from the STAGE side when the caller knows where the stage is: the\n // camera stands behind the stage looking into the bowl, so every tier faces\n // you on arrival. A fixed azimuth made the entry view a coin flip — charts\n // oriented the other way opened staring at the back of the shell.\n this.azT = stageAzimuth ?? -30 * DEG;\n this.polT = 55 * DEG;\n this.distT = fit * FRAME_MARGIN;\n // Low min so you can swoop down close enough that seat dots are big, tappable\n // targets (\"into your section\"); generous max to pull right back out.\n this.minDist = Math.max(2, r * 0.12);\n this.maxDist = fit * 4;\n if (intro) {\n this.azimuth = this.azT; // no spin — just tilt up + dolly in\n this.polar = 12 * DEG; // near top-down, like the flat 2D view\n this.distance = this.distT * 1.7;\n } else {\n this.azimuth = this.azT;\n this.polar = this.polT;\n this.distance = this.distT;\n }\n this.applyPosition();\n }\n\n setAspect(aspect: number): void {\n this.camera.perspective({ aspect });\n }\n\n /**\n * Damped return to the framed overview from wherever the camera is now (a\n * seat, behind the shell, anywhere): re-pivot on the venue centre without\n * moving the camera, then glide targets back to the 3/4 architectural pose.\n */\n frameSoft(bounds: OrbitBounds, stageAzimuth?: number, portraitCrop = false): void {\n this.target.set(bounds.center[0], bounds.center[1], bounds.center[2]);\n this.targetT.copy(this.target);\n this.panAnchor.copy(this.target);\n this.panLimit = Math.max(1, bounds.radius) * 1.5;\n this.syncFromCamera(); // re-derive pose around the new pivot — no snap\n this.camera.perspective({ fov: this.fovY, aspect: this.camera.aspect });\n const r = Math.max(1, bounds.radius);\n const halfV = (this.fovY * DEG) / 2;\n const aspect = portraitCrop ? Math.max(0.82, this.camera.aspect || 1) : (this.camera.aspect || 1);\n const halfH = Math.atan(Math.tan(halfV) * aspect);\n const fit = Math.max(r / Math.tan(halfV), r / Math.tan(halfH));\n this.azT = stageAzimuth ?? this.azimuth;\n this.polT = 55 * DEG;\n this.distT = fit * FRAME_MARGIN;\n }\n\n /** Damp toward targets; returns true while still moving. */\n update(): boolean {\n const da = this.azT - this.azimuth;\n const dp = this.polT - this.polar;\n const dd = this.distT - this.distance;\n const tx = this.targetT.x - this.target.x;\n const ty = this.targetT.y - this.target.y;\n const tz = this.targetT.z - this.target.z;\n // Pan is damped on the same curve as the rest of the pose, and its epsilon is\n // scaled by distance: 1e-4 world units is imperceptible at the overview but\n // would keep a pushed-in camera redrawing forever.\n const panEps = Math.max(1e-4, this.distance * 1e-4);\n const moving = Math.abs(da) > 1e-4 || Math.abs(dp) > 1e-4 || Math.abs(dd) > 1e-4\n || Math.abs(tx) > panEps || Math.abs(ty) > panEps || Math.abs(tz) > panEps;\n this.azimuth += da * DAMP;\n this.polar += dp * DAMP;\n this.distance += dd * DAMP;\n this.target.x += tx * DAMP;\n this.target.y += ty * DAMP;\n this.target.z += tz * DAMP;\n if (moving) this.applyPosition();\n return moving;\n }\n\n /** Distance from camera to target (for LOD). */\n get currentDistance(): number {\n return this.distance;\n }\n\n /**\n * Re-derive the orbit's spherical state from the camera's CURRENT pose (after a\n * cinematic flight leaves it somewhere arbitrary), so a subsequent drag damps\n * from where it actually is with no snap. Does not move the camera.\n */\n syncFromCamera(): void {\n const dx = this.camera.position.x - this.target.x;\n const dy = this.camera.position.y - this.target.y;\n const dz = this.camera.position.z - this.target.z;\n const dist = Math.hypot(dx, dy, dz) || 1;\n const polar = Math.max(POLAR_MIN, Math.min(POLAR_MAX, Math.acos(Math.max(-1, Math.min(1, dy / dist)))));\n // Distance is NOT clamped here: a flight can park closer than minDist, and\n // clamping would jump the camera radially on the very first drag. The clamp\n // applies lazily from the next user-driven dolly (see dollyBy).\n this.distance = this.distT = dist;\n this.polar = this.polT = polar;\n this.azimuth = this.azT = Math.atan2(dx, dz);\n }\n\n /** Point the orbit pivot at a new world target without moving the camera. */\n setTarget(target: [number, number, number]): void {\n this.target.set(target[0], target[1], target[2]);\n this.targetT.copy(this.target);\n }\n\n /** Restore the base FOV (a flight ends pushed-in) and re-sync orbit state. A\n * flight ends looking at `target` (the venue focal), so re-pivot there first —\n * otherwise the first drag would `lookAt(bounds.center)` and pop the aim. */\n resumeAfterFlight(target?: [number, number, number]): void {\n this.camera.perspective({ fov: this.fovY, aspect: this.camera.aspect });\n if (target) this.target.set(target[0], target[1], target[2]);\n this.targetT.copy(this.target);\n this.syncFromCamera();\n }\n\n private applyPosition(): void {\n const sp = Math.sin(this.polar);\n const x = this.target.x + this.distance * sp * Math.sin(this.azimuth);\n const y = this.target.y + this.distance * Math.cos(this.polar);\n const z = this.target.z + this.distance * sp * Math.cos(this.azimuth);\n this.camera.position.set(x, y, z);\n this.camera.lookAt(this.target);\n }\n\n dispose(): void {\n this.canvas.removeEventListener('pointerdown', this.onPointerDown);\n this.canvas.removeEventListener('pointermove', this.onPointerMove);\n this.canvas.removeEventListener('pointerup', this.onPointerUp);\n this.canvas.removeEventListener('pointercancel', this.onPointerUp);\n this.canvas.removeEventListener('wheel', this.onWheel);\n this.canvas.removeEventListener('keydown', this.onKeyDown);\n this.canvas.removeEventListener('contextmenu', this.onContextMenu);\n this.activePointers.clear();\n }\n}\n","/**\n * Dirty-flag render loop. A frame is drawn only while the camera is moving /\n * damping or an availability update arrived; when idle, no rAF is scheduled at\n * all (zero CPU/GPU when parked). Tracks an FPS EMA over frames actually\n * rendered — it reads as \"idle\" when nothing is scheduled.\n */\n\nexport interface RenderLoopStats {\n fps: number;\n rendered: number;\n idle: boolean;\n}\n\nexport class RenderLoop {\n private frame: (dt: number) => boolean;\n private rafId = 0;\n private running = false;\n private lastTime = 0;\n private fpsEma = 0;\n private rendered = 0;\n\n /** `frame(dt)` renders one frame and returns true if another is needed. */\n constructor(frame: (dt: number) => boolean) {\n this.frame = frame;\n }\n\n requestRender(): void {\n if (this.running) return;\n this.running = true;\n this.lastTime = 0;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n private tick = (now: number): void => {\n const dt = this.lastTime ? (now - this.lastTime) / 1000 : 1 / 60;\n this.lastTime = now;\n if (dt > 0) {\n const instFps = 1 / dt;\n this.fpsEma = this.fpsEma ? this.fpsEma * 0.9 + instFps * 0.1 : instFps;\n }\n this.rendered++;\n const again = this.frame(dt);\n if (again) {\n this.rafId = requestAnimationFrame(this.tick);\n } else {\n this.running = false;\n this.rafId = 0;\n }\n };\n\n stats(): RenderLoopStats {\n return { fps: this.running ? Math.round(this.fpsEma) : 0, rendered: this.rendered, idle: !this.running };\n }\n\n stop(): void {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.running = false;\n }\n}\n","/**\n * The seat level-of-detail ladder.\n *\n * Three rungs, all driven by how far a seat is from the CAMERA rather than by a\n * global switch, so one venue can be showing all three at once — chairs in the\n * row you are sitting in, dots across the bowl, a dissolving tint on the far\n * side:\n *\n * near (≤ CHAIR_FULL_M) extruded chair silhouettes (scene/seatChair.ts)\n * band (…CHAIR_NONE_M) chair scales in / dot fades out — no pop\n * far the instanced billboard dot, as before\n *\n * Everything below is pure so the thresholds can be reasoned about (and tested)\n * without a GL context. The shaders re-derive the same weight per instance from\n * view depth; these constants are the single source both sides read.\n */\n\nexport interface SeatLod {\n /** Multiplier on the seat world radius (1 = full). */\n scale: number;\n /** Fade toward the tier/fade colour (0 = pure state colour). */\n fade: number;\n /**\n * Minimum screen size a dot is allowed to demand, in pixels.\n *\n * The shader holds this floor by GROWING a dot's world radius with depth, and\n * that growth is what smears neighbouring rows into one mass at range: measured\n * mean seat spacing is 0.53–0.58 m against a 0.44 m dot diameter, so a dot only\n * has to grow ~20 % before it touches its neighbour. `iMaxRadius` caps each dot\n * at its own pitch, but a capped dot then sits at its ceiling across the whole\n * far half of the venue, which is exactly the solid-mass look.\n *\n * So the DEMAND is tapered as well as the growth capped. Past the far knee the\n * requested floor relaxes toward SEAT_MIN_PIXELS_FAR, the dots stop pressing\n * against their ceilings, and the gap between rows survives to the back wall.\n * Legibility past that point is the tier tint's job, not the dot's — which is\n * what `fade` above is for.\n */\n minPixels: number;\n}\n\n/** Pixel floor a dot demands in the near/mid field — unchanged from v1. */\nexport const SEAT_MIN_PIXELS_NEAR = 2.5;\n/**\n * Pixel floor a dot demands at full distance.\n *\n * Not zero: a dot that demands nothing shrinks below a pixel, aliases, and\n * shimmers as the camera moves. 1.15 keeps a sub-pixel dot anchored while\n * leaving it far enough under its pitch ceiling that its neighbour stays\n * separate. The fragment stage already dissolves anything that cannot afford\n * even this (`vBudget`).\n */\nexport const SEAT_MIN_PIXELS_FAR = 1.15;\n\n/**\n * View depth, in metres, at or below which a seat draws as a full chair.\n *\n * 12 m is roughly \"the far end of your own block\". At the arrival pose the\n * camera sits at the seat, so the whole neighbourhood a buyer is actually\n * looking at is inside this.\n */\nexport const CHAIR_FULL_M = 12;\n\n/**\n * View depth at and beyond which a seat is a dot again.\n *\n * The 10 m band between this and CHAIR_FULL_M is the transition. It is wide on\n * purpose: the fly-to-seat cinematic crosses it in well under a second, and a\n * narrow band would read as a wave of furniture sweeping over the venue.\n */\nexport const CHAIR_NONE_M = 22;\n\n/**\n * Radius, in metres, of the CPU gather that decides which seats are handed to\n * the chair mesh at all.\n *\n * Strictly greater than CHAIR_NONE_M + CHAIR_REBUILD_M. That inequality is the\n * whole no-pop argument: a seat always enters the instanced set while it is\n * still beyond the fade band (weight 0, drawn as nothing), so by the time it is\n * close enough to be visible it has already been in the set for a while. A seat\n * can never blink into existence mid-band, whatever the camera does between two\n * gathers. `chairGatherCoversFadeBand()` asserts it.\n */\nexport const CHAIR_GATHER_M = 30;\n\n/**\n * How far the camera may travel before the gathered set is rebuilt.\n *\n * The gather is the only per-frame CPU cost the near field adds, so it is not\n * run per frame. 4 m against an 8 m slack (GATHER − NONE) leaves a 2× margin.\n */\nexport const CHAIR_REBUILD_M = 4;\n\n/**\n * Hard ceiling on instances handed to the chair mesh.\n *\n * The cap is what makes the near field's cost bounded rather than proportional\n * to venue size, and seats are gathered nearest-first, so it only ever drops the\n * OUTER edge of the disc.\n *\n * Its value is not a round number picked for comfort — it is sized against\n * measured venue density, because a cap that bites inside the fade band would\n * silently reintroduce popping on exactly the biggest venues. Worst-case seats\n * within a radius, measured by standing on 60 sampled seats of each shipped\n * chart:\n *\n * 21 m 26 m 30 m\n * megaStadium 4,278 5,905 7,297\n * uberArena 2,670 3,690 4,585\n * amphitheatre 2,326 2,873 3,211\n *\n * megaStadium's figures fell slightly when its ROW_GAP went 28 → 30 to clear the\n * corner-row spacing floor (see `src/core/megaStadium.ts`): rows further apart\n * is strictly less dense, so the headroom below only grew.\n *\n * The binding requirement is that the kept disc still reach CHAIR_NONE_M +\n * CHAIR_REBUILD_M (26 m), which megaStadium meets at 5,905. An earlier 4,096\n * capped megaStadium's disc at roughly 22 m — inside that margin, and the defect\n * would only ever have shown up on the largest chart. 8,192 leaves ~30 % headroom\n * for a denser chart than any shipped today. `chairCapCoversFadeBand()` is the\n * executable form of this.\n */\nexport const CHAIR_MAX_INSTANCES = 8192;\n\n/**\n * The near/far ladder invariant, as a value rather than a comment.\n *\n * Exported so the test suite asserts the actual constants instead of restating\n * them: if someone widens the fade band or slows the rebuild without widening\n * the gather, seats start popping in mid-band and this goes false.\n */\nexport function chairGatherCoversFadeBand(): boolean {\n return CHAIR_GATHER_M > CHAIR_NONE_M + CHAIR_REBUILD_M;\n}\n\n/** The radius the gathered set must still reach after the cap has bitten. */\nexport const CHAIR_REQUIRED_COVER_M = CHAIR_NONE_M + CHAIR_REBUILD_M;\n\n/**\n * Does a venue holding `seatsWithinRequiredCover` seats inside\n * CHAIR_REQUIRED_COVER_M still get every one of them into the capped set?\n *\n * The companion to `chairGatherCoversFadeBand`. That one checks the RADIUS is\n * generous enough; this checks the CAP is, which is the failure mode that only\n * appears on the densest chart and is therefore the one most likely to ship.\n */\nexport function chairCapCoversFadeBand(seatsWithinRequiredCover: number): boolean {\n return seatsWithinRequiredCover <= CHAIR_MAX_INSTANCES;\n}\n\n/** GLSL `smoothstep`, so the JS side and the shaders agree exactly. */\nfunction smoothstep(edge0: number, edge1: number, x: number): number {\n const t = Math.min(1, Math.max(0, (x - edge0) / Math.max(1e-6, edge1 - edge0)));\n return t * t * (3 - 2 * t);\n}\n\n/**\n * How much chair a seat at `depth` metres of view depth shows: 1 = full\n * silhouette, 0 = nothing drawn (the dot carries it).\n *\n * The chair SCALES IN rather than fading in. A cross-fade would need the chairs\n * to be transparent through the whole band, and a transparent chair either\n * writes depth (and punches holes in the chairs behind it) or does not (and\n * shows its own back panel through its own seat pad). Growing an OPAQUE chair\n * out of the seat point has neither problem, and it lands where the dot already\n * is: at weight 0 the chair is a zero-size nub exactly at the seat centre, under\n * a dot at full opacity.\n *\n * Mirrored in CHAIR_VERT / SEAT_VERT — keep the three in step.\n */\nexport function chairWeight(depth: number): number {\n return 1 - smoothstep(CHAIR_FULL_M, CHAIR_NONE_M, depth);\n}\n\n/** The dot's share of a seat: the exact complement of `chairWeight`. */\nexport function dotWeight(depth: number): number {\n return 1 - chairWeight(depth);\n}\n\n/**\n * The chair's linear size at `depth`, as a fraction of full size.\n *\n * NOT the same curve as `chairWeight`, and the difference is what the middle of\n * the band actually looks like. Scaling linearly with the weight put a\n * quarter-size chair inside a full-opacity dot for most of the crossover: the\n * dot is ~0.44 m across and holds its size until it is handed over, so a chair\n * at 25 % read as a speck floating inside a blob rather than as a seat.\n *\n * The square root front-loads the growth — half a weight is 71 % of full size —\n * so by the time the dot has faded to half, the chair underneath it is nearly\n * the same size and the two genuinely overlay each other. It still passes\n * through exactly 0 at CHAIR_NONE_M, so nothing pops into existence.\n */\nexport function chairScale(depth: number): number {\n return Math.sqrt(chairWeight(depth));\n}\n\nexport function computeSeatLod(distance: number, radius: number): SeatLod {\n const near = radius * 1.4;\n const far = radius * 3.2;\n if (distance <= near) return { scale: 1, fade: 0, minPixels: SEAT_MIN_PIXELS_NEAR };\n const t = Math.min(1, (distance - near) / Math.max(1e-3, far - near));\n return {\n scale: 1 - t * 0.4,\n fade: t * 0.55,\n // Tapered on the same ramp as the fade: as the block starts reading by its\n // tier tint, the dots stop fighting each other for pixels.\n minPixels: SEAT_MIN_PIXELS_NEAR + t * (SEAT_MIN_PIXELS_FAR - SEAT_MIN_PIXELS_NEAR),\n };\n}\n","/**\n * \"Which seats are close enough to be worth drawing as chairs?\" — a uniform XZ\n * grid over the seat cloud, queried nearest-cell-first.\n *\n * This is the piece that makes the near field affordable. The chair mesh could\n * simply carry every seat and let the vertex stage collapse the far ones to\n * zero size, but that is 52,000 × 72 vertices of shading per frame to draw a few\n * hundred chairs — the whole 60 fps claim, spent on geometry that is discarded.\n * Instead the CPU hands the mesh a COMPACT set bounded by CHAIR_MAX_INSTANCES,\n * so near-field cost is a function of the camera's neighbourhood and not of\n * venue size.\n *\n * The grid is built lazily on the first gather: a camera that never comes down\n * to the deck never pays for it, and the intro pose is always far out.\n */\n\n/** Target seats per cell. Small enough to prune hard, large enough that the\n * bucket arrays stay short and cache-friendly. */\nconst SEATS_PER_CELL = 4;\n\nexport class NearFieldIndex {\n private readonly iPosition: Float32Array;\n private readonly count: number;\n private minX = 0;\n private minZ = 0;\n private cell = 1;\n private cols = 1;\n private rows = 1;\n /** CSR-style buckets: `cellStart[c]…cellStart[c+1]` indexes into `cellItems`. */\n private cellStart: Int32Array | null = null;\n private cellItems: Int32Array | null = null;\n\n constructor(iPosition: Float32Array, count: number) {\n this.iPosition = iPosition;\n this.count = count;\n }\n\n /** Built on demand; safe to call repeatedly. */\n private ensureGrid(): void {\n if (this.cellStart || this.count === 0) return;\n const p = this.iPosition;\n let minX = Infinity, minZ = Infinity, maxX = -Infinity, maxZ = -Infinity;\n for (let i = 0; i < this.count; i++) {\n const x = p[i * 3], z = p[i * 3 + 2];\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (z < minZ) minZ = z;\n if (z > maxZ) maxZ = z;\n }\n const w = Math.max(maxX - minX, 1e-3);\n const h = Math.max(maxZ - minZ, 1e-3);\n this.cell = Math.max(Math.sqrt((w * h * SEATS_PER_CELL) / this.count), 0.25);\n this.cols = Math.max(1, Math.ceil(w / this.cell) + 1);\n this.rows = Math.max(1, Math.ceil(h / this.cell) + 1);\n this.minX = minX;\n this.minZ = minZ;\n // Counting sort into CSR — no per-cell arrays, one pass to count and one to\n // place. At 52k seats this is two linear scans and two typed allocations.\n const nCells = this.cols * this.rows;\n const start = new Int32Array(nCells + 1);\n const cellOf = (i: number): number => {\n const cx = Math.min(this.cols - 1, Math.max(0, Math.floor((p[i * 3] - minX) / this.cell)));\n const cz = Math.min(this.rows - 1, Math.max(0, Math.floor((p[i * 3 + 2] - minZ) / this.cell)));\n return cz * this.cols + cx;\n };\n for (let i = 0; i < this.count; i++) start[cellOf(i) + 1]++;\n for (let c = 0; c < nCells; c++) start[c + 1] += start[c];\n const items = new Int32Array(this.count);\n const cursor = start.slice(0, nCells);\n for (let i = 0; i < this.count; i++) items[cursor[cellOf(i)]++] = i;\n this.cellStart = start;\n this.cellItems = items;\n }\n\n /**\n * Fill `out` with the indices of seats within `radius` metres of (camX, camZ),\n * nearest cell-ring first, and return how many were written.\n *\n * Ring order is what makes the CHAIR_MAX_INSTANCES cap harmless: when the cap\n * bites it drops the OUTERMOST seats, which are the ones already past the fade\n * band and drawing nothing. A cap that truncated in index order would instead\n * punch holes in the row you are sitting in.\n *\n * Note this is a horizontal (XZ) query and ignores height. A stacked venue's\n * upper tier is therefore gathered along with the stalls beneath it — which is\n * correct, because the fade weight is re-derived from true view depth in the\n * shader anyway. The grid's only job is to bound the candidate set.\n */\n gather(camX: number, camZ: number, radius: number, out: Int32Array): number {\n this.ensureGrid();\n const start = this.cellStart;\n const items = this.cellItems;\n if (!start || !items) return 0;\n const cap = out.length;\n const r2 = radius * radius;\n const cx = Math.floor((camX - this.minX) / this.cell);\n const cz = Math.floor((camZ - this.minZ) / this.cell);\n const maxRing = Math.ceil(radius / this.cell) + 1;\n const p = this.iPosition;\n let n = 0;\n for (let ring = 0; ring <= maxRing && n < cap; ring++) {\n const z0 = cz - ring, z1 = cz + ring;\n const x0 = cx - ring, x1 = cx + ring;\n for (let gz = z0; gz <= z1 && n < cap; gz++) {\n if (gz < 0 || gz >= this.rows) continue;\n // Only the newly-exposed cells of this ring: the full row on the top and\n // bottom edges, just the two end cells in between.\n const edge = (gz === z0 || gz === z1);\n for (let gx = x0; gx <= x1 && n < cap; gx++) {\n if (!edge && gx !== x0 && gx !== x1) { gx = x1 - 1; continue; }\n if (gx < 0 || gx >= this.cols) continue;\n const c = gz * this.cols + gx;\n for (let k = start[c], e = start[c + 1]; k < e && n < cap; k++) {\n const i = items[k];\n const dx = p[i * 3] - camX;\n const dz = p[i * 3 + 2] - camZ;\n if (dx * dx + dz * dz <= r2) out[n++] = i;\n }\n }\n }\n }\n return n;\n }\n}\n","/**\n * 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. */\n/**\n * Growable Float32 buffer.\n *\n * `MeshBuilder` accumulated into plain `number[]` and converted once at the end.\n * On a 99k-seat venue that meant four arrays of ~1.2M boxed numbers each and a\n * final copy of all of them — profiled at 11 % of scene build in `build()`\n * alone, plus a share of the 8 % spent in GC. Writing straight into typed\n * storage that doubles when full removes both the boxing and the final copy.\n */\nclass F32Buffer {\n private buf: Float32Array;\n private len = 0;\n\n constructor(initial = 1 << 14) {\n this.buf = new Float32Array(initial);\n }\n\n push3(a: number, b: number, c: number): void {\n if (this.len + 3 > this.buf.length) this.grow(this.len + 3);\n this.buf[this.len++] = a;\n this.buf[this.len++] = b;\n this.buf[this.len++] = c;\n }\n\n push1(a: number): void {\n if (this.len + 1 > this.buf.length) this.grow(this.len + 1);\n this.buf[this.len++] = a;\n }\n\n private grow(need: number): void {\n let cap = this.buf.length * 2;\n while (cap < need) cap *= 2;\n const next = new Float32Array(cap);\n next.set(this.buf.subarray(0, this.len));\n this.buf = next;\n }\n\n get length(): number { return this.len; }\n\n /** A copy trimmed to the used length. */\n toArray(): Float32Array {\n return this.buf.slice(0, this.len);\n }\n}\n\nexport class MeshBuilder {\n private pos = new F32Buffer();\n private nor = new F32Buffer();\n private col = new F32Buffer();\n private flr = new F32Buffer();\n /** 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.push3(p0[0], p0[1], p0[2]); this.pos.push3(p1[0], p1[1], p1[2]); this.pos.push3(p2[0], p2[1], p2[2]);\n this.nor.push3(n[0], n[1], n[2]); this.nor.push3(n[0], n[1], n[2]); this.nor.push3(n[0], n[1], n[2]);\n this.col.push3(c0[0], c0[1], c0[2]); this.col.push3(c1[0], c1[1], c1[2]); this.col.push3(c2[0], c2[1], c2[2]);\n this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor);\n }\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.push3(p0[0], p0[1], p0[2]); this.pos.push3(p1[0], p1[1], p1[2]); this.pos.push3(p2[0], p2[1], p2[2]);\n this.nor.push3(n0[0], n0[1], n0[2]); this.nor.push3(n1[0], n1[1], n1[2]); this.nor.push3(n2[0], n2[1], n2[2]);\n this.col.push3(c0[0], c0[1], c0[2]); this.col.push3(c1[0], c1[1], c1[2]); this.col.push3(c2[0], c2[1], c2[2]);\n this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor); this.flr.push1(this.currentFloor);\n }\n\n get vertexCount(): number {\n return this.pos.length / 3;\n }\n\n build(): MeshData {\n return {\n position: this.pos.toArray(),\n normal: this.nor.toArray(),\n color: this.col.toArray(),\n floor: this.flr.toArray(),\n 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 * The near-field seat: a three-box extruded silhouette (pedestal, pad, back)\n * and the per-seat facing that aims it at the stage.\n *\n * Why this exists at all: every seat in the venue was a camera-facing disc, and\n * a disc has no front. That is fine across a bowl and wrong at the one moment\n * the 3D view is selling — the buyer lands ON their seat and the row they are\n * sitting in is a line of coloured blobs rather than chairs. The renderer's own\n * programme notes call it the biggest visual gap at close range.\n *\n * What it deliberately is NOT: furniture. There are no armrests, no cushion\n * bevel, no legs. At 3–10 m what the eye needs is a pad at seat height and a\n * back rising behind it, facing the right way — the silhouette, not the detail.\n * Everything here is one instanced mesh over a 72-vertex base, because the third\n * draw call is the budget.\n */\n\n/** Local-space part id, so the fragment stage can treat the three boxes apart. */\nexport const CHAIR_PART = { pedestal: 0, pad: 1, back: 2, body: 3, head: 4 } as const;\n\n/**\n * Parts at or above this index belong to the OCCUPANT, not the chair.\n *\n * They are collapsed to nothing on an empty seat (see the chair vertex shader),\n * which is how one instanced mesh draws both a bare seat and a taken one without\n * a second draw call or a second geometry.\n */\nexport const CHAIR_PART_OCCUPANT_MIN = CHAIR_PART.body;\n\nexport interface ChairMesh {\n /**\n * Local vertex positions. x and z are in units of the seat's own chair\n * half-width (`iChairWidth`); y is in METRES and is not scaled.\n *\n * The split matters: a chart authored at a tight seat pitch needs narrower\n * chairs or they interpenetrate, but it does not need SHORTER ones — a tight\n * row is still full of adult-sized people. A uniform scale would have produced\n * a doll's-house block on exactly the densest charts.\n */\n position: Float32Array;\n normal: Float32Array;\n /** Per-vertex CHAIR_PART. */\n part: Float32Array;\n index: Uint16Array;\n vertexCount: number;\n indexCount: number;\n}\n\n/**\n * A chair's half-width as a fraction of its seat's own spacing.\n *\n * NOT the dot's `SEAT_PITCH_FRACTION` (0.42), and the difference matters. That\n * constant exists to keep round dots visually separate, so it is deliberately\n * conservative and it is capped at SEAT_DOT_RADIUS_M on top. Sizing a chair from\n * it produced tall narrow slabs — a 0.34 m chair with a 0.95 m back, an aspect\n * ratio of nearly 3:1, which reads as a gravestone rather than as seating.\n *\n * Real auditorium seats very nearly touch: at a 0.53 m pitch (the measured mean)\n * a seat is about 0.48 m across. 0.44 reproduces that — the pair occupies 0.88\n * of the pitch and the remaining 12 % is the gap you actually see in a venue.\n */\nexport const CHAIR_PITCH_FRACTION = 0.44;\n\n/**\n * Bounds on the resolved half-width, world metres.\n *\n * The floor is the honest compromise in this file. The amphitheatre measures a\n * 0.21 m minimum seat spacing, and at that spacing NO chair both fits between\n * its neighbours and reads as a chair — 21 cm is not a real seat pitch, it is a\n * chart authored tighter than the units claim. Given the choice between chairs\n * that overlap slightly and chairs that are legible, the overlap wins: it is\n * invisible from any angle a buyer actually looks from, whereas a 9 cm-wide\n * 0.95 m-tall slab is wrong from every angle. The ceiling stops a sparse box\n * seat from inflating into an armchair.\n */\nexport const CHAIR_HALF_WIDTH_MIN_M = 0.15;\nexport const CHAIR_HALF_WIDTH_MAX_M = 0.30;\n/** Used when a seat has no resolvable neighbour at all. */\nexport const CHAIR_HALF_WIDTH_DEFAULT_M = 0.24;\n\n/**\n * The world half-width of one seat's chair, from its own spacing in metres.\n *\n * Everything horizontal about the chair scales from this single number, so a\n * chair's depth tracks its width and the proportions hold at any pitch. Height\n * does not — see `ChairMesh.position`.\n */\nexport function chairHalfWidth(pitchM: number | undefined): number {\n if (pitchM === undefined || !Number.isFinite(pitchM) || pitchM <= 0) {\n return CHAIR_HALF_WIDTH_DEFAULT_M;\n }\n return Math.min(CHAIR_HALF_WIDTH_MAX_M, Math.max(CHAIR_HALF_WIDTH_MIN_M, pitchM * CHAIR_PITCH_FRACTION));\n}\n\n/**\n * Backward lean of the seat back, as a slope (metres of z per metre of rise).\n *\n * The first build had a vertical back and the result read as a crate: a flat top\n * face, a flat wall behind it, and nothing to say which way was up or which way\n * was forward. A rake is the cheapest possible chair cue — it costs a shear —\n * and it puts a lit face at an angle to every other surface in the venue, which\n * is what breaks the block up.\n *\n * Applied in the SHADER, in world metres, and deliberately not baked into the\n * base mesh. The mesh's x/z are in half-width units, so a lean baked there would\n * scale with the seat's width: the same chair would lean 20° on a wide-pitch\n * stadium row and 6° on a tight theatre row. 0.21 is about 12° from vertical,\n * which is roughly a real fixed auditorium seat.\n */\nexport const BACK_RAKE_SLOPE = 0.21;\n\n/**\n * Air between the top of the pad and the bottom of the back, world metres.\n *\n * The other half of \"crate vs chair\". Two boxes sharing a face silhouette as one\n * solid; separate them and the eye reads a seat with a back behind it, even at\n * 10 m where the gap itself is barely a pixel — because it breaks the outline.\n */\nconst PAD_BACK_GAP_M = 0.05;\n\nconst PAD_TOP_M = 0.45;\n/** World height at which the back starts, and therefore where the rake pivots. */\nexport const BACK_BASE_M = PAD_TOP_M + PAD_BACK_GAP_M;\n\n/**\n * Chair dimensions. +Z is the direction the seat FACES, so the back panel lives\n * at negative z. x/z are half-width units; y is metres above the seat's own deck\n * point, chosen against the height contract the rest of the renderer uses — a\n * seated eye sits SEAT_EYE_ABOVE_DECK (1.02 m) up, so a 0.45 m pad and a 0.92 m\n * back top put the occupant's shoulders just over the seat back.\n */\nconst BOXES: Array<{\n part: number;\n min: [number, number, number];\n max: [number, number, number];\n}> = [\n // Pedestal — a plain column under the pad. Without it the pad floats 0.36 m\n // over the deck and the row reads as hovering trays.\n { part: CHAIR_PART.pedestal, min: [-0.30, 0.00, -0.30], max: [0.30, 0.36, 0.30] },\n // Seat pad — a full seat width across and about as deep, which is what a real\n // one is. Its depth is bounded by the same pitch as its width, because the\n // pitch measure is the tighter of the in-row and row-to-row spacings, so a\n // tightly-raked tier cannot drive a pad into the back of the row in front.\n { part: CHAIR_PART.pad, min: [-1.00, 0.36, -0.95], max: [1.00, PAD_TOP_M, 1.00] },\n // Back panel — thin, raked, and the tallest thing in the row, so it is what\n // carries the state colour when you look along a row from behind.\n {\n part: CHAIR_PART.back,\n min: [-1.00, BACK_BASE_M, -1.00],\n max: [1.00, 0.92, -0.72],\n },\n // --- the occupant ---------------------------------------------------------\n //\n // A hall with every seat empty reads as an architectural model, not a venue.\n // The 2048-px panorama this replaced drew a crowd; losing it was the price of\n // sharpness, and this is how it is bought back — in geometry, where there is\n // no resolution ceiling.\n //\n // Deliberately two blocks and no limbs. At the range these are visible a\n // person is a torso and a head, and every extra part multiplies by the number\n // of occupied seats in view. The silhouette is what carries it, exactly as it\n // does in the generated panorama's head-and-shoulder figures.\n //\n // Sized as a seated adult against the 0.92 m chair back: hips at the pad top,\n // shoulders just above the back panel, head clear of it. Torso is narrower\n // than the chair so neighbours never interpenetrate at any pitch, and it sits\n // forward of the back panel rather than inside it.\n {\n part: CHAIR_PART.body,\n min: [-0.66, PAD_TOP_M, -0.58],\n max: [0.66, 1.00, 0.26],\n },\n // The head is deliberately SMALL. Sized by eye against the chair it came out\n // near-cubic and read as Lego; a real head is about 0.16 m across and 0.22 m\n // tall, which against a 0.24 m chair half-width is roughly a third of the\n // chair's width and clearly taller than it is wide. Getting this ratio wrong\n // is what makes a crowd look like toys rather than people.\n {\n part: CHAIR_PART.head,\n min: [-0.34, 1.03, -0.40],\n max: [0.34, 1.27, 0.02],\n },\n];\n\n/** The six axis-aligned faces of a box, as (normal, corner order). */\nconst FACES: Array<{ n: [number, number, number]; c: Array<[number, number, number]> }> = [\n // +X\n { n: [1, 0, 0], c: [[1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 0, 1]] },\n // -X\n { n: [-1, 0, 0], c: [[0, 0, 1], [0, 1, 1], [0, 1, 0], [0, 0, 0]] },\n // +Y\n { n: [0, 1, 0], c: [[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]] },\n // -Y\n { n: [0, -1, 0], c: [[0, 0, 1], [0, 0, 0], [1, 0, 0], [1, 0, 1]] },\n // +Z\n { n: [0, 0, 1], c: [[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]] },\n // -Z\n { n: [0, 0, -1], c: [[1, 0, 0], [0, 0, 0], [0, 1, 0], [1, 1, 0]] },\n];\n\n/**\n * Build the shared chair base mesh. Called once per GPU scene; the result is\n * 72 vertices and 108 indices, i.e. ~1.7 KB of GPU memory total however many\n * seats the venue has.\n */\nexport function buildChairMesh(): ChairMesh {\n const vertexCount = BOXES.length * FACES.length * 4;\n const indexCount = BOXES.length * FACES.length * 6;\n const position = new Float32Array(vertexCount * 3);\n const normal = new Float32Array(vertexCount * 3);\n const part = new Float32Array(vertexCount);\n const index = new Uint16Array(indexCount);\n let v = 0;\n let t = 0;\n for (const box of BOXES) {\n for (const face of FACES) {\n const base = v;\n const corner3 = new Float32Array(12);\n for (let ci = 0; ci < 4; ci++) {\n const corner = face.c[ci];\n for (let a = 0; a < 3; a++) {\n corner3[ci * 3 + a] = corner[a] ? box.max[a] : box.min[a];\n }\n }\n // Face normal derived from the quad rather than asserted. The boxes are\n // axis-aligned today so this reproduces face.n exactly; deriving it means\n // a future non-axis-aligned part cannot ship silently mis-lit.\n const ax = corner3[3] - corner3[0], ay = corner3[4] - corner3[1], az = corner3[5] - corner3[2];\n const bx = corner3[6] - corner3[0], by = corner3[7] - corner3[1], bz = corner3[8] - corner3[2];\n let nx = ay * bz - az * by;\n let ny = az * bx - ax * bz;\n let nz = ax * by - ay * bx;\n const nl = Math.hypot(nx, ny, nz);\n if (nl > 1e-9) { nx /= nl; ny /= nl; nz /= nl; }\n else { nx = face.n[0]; ny = face.n[1]; nz = face.n[2]; }\n // Keep the authored outward sense — the cross product's sign depends on\n // corner order, and a flipped normal would light the face from inside.\n if (nx * face.n[0] + ny * face.n[1] + nz * face.n[2] < 0) { nx = -nx; ny = -ny; nz = -nz; }\n for (let ci = 0; ci < 4; ci++) {\n position[v * 3] = corner3[ci * 3];\n position[v * 3 + 1] = corner3[ci * 3 + 1];\n position[v * 3 + 2] = corner3[ci * 3 + 2];\n normal[v * 3] = nx;\n normal[v * 3 + 1] = ny;\n normal[v * 3 + 2] = nz;\n part[v] = box.part;\n v++;\n }\n index[t++] = base; index[t++] = base + 1; index[t++] = base + 2;\n index[t++] = base; index[t++] = base + 2; index[t++] = base + 3;\n }\n }\n return { position, normal, part, index, vertexCount, indexCount };\n}\n\n/**\n * Per-seat facing yaw, radians, such that the chair's local +Z points at what\n * the seat looks at.\n *\n * Derived from the ROW, not from the seat alone. Aiming every seat individually\n * at the focal point is the obvious rule and it is wrong for the commonest case:\n * a straight row in a theatre fans out into a shallow arc of chairs pointing\n * slightly inward, which no real auditorium does. Taking the row's local tangent\n * and turning perpendicular to it gives BOTH cases for free — a straight row\n * comes out uniformly forward, and a curved arena row keeps the genuine radial\n * fan it is built with.\n *\n * The focal only picks the SIGN (which of the two perpendiculars faces the\n * show), so a mis-authored focal can never rotate a row sideways, only flip it.\n * A row with a single seat has no tangent and falls back to facing the focal\n * directly.\n *\n * Row membership is read as RUNS of equal `rowId` rather than through a map:\n * expanded seats come out row-contiguous, the scan is O(n) with no allocation,\n * and a chart that somehow interleaved rows degrades to shorter runs rather than\n * to wrong answers.\n */\nexport function computeSeatYaw(\n iPosition: Float32Array,\n count: number,\n rowIdAt: (index: number) => string | undefined,\n focal: (index: number) => readonly [number, number],\n): Float32Array {\n const yaw = new Float32Array(count);\n const px = (i: number): number => iPosition[i * 3];\n const pz = (i: number): number => iPosition[i * 3 + 2];\n\n let runStart = 0;\n const flushRun = (start: number, end: number): void => {\n // [start, end) share a rowId.\n const n = end - start;\n for (let i = start; i < end; i++) {\n const [fx, fz] = focal(i);\n let dx = fx - px(i);\n let dz = fz - pz(i);\n if (n >= 2) {\n // Central difference where possible; one-sided at the row ends.\n const a = Math.max(start, i - 1);\n const b = Math.min(end - 1, i + 1);\n const tx = px(b) - px(a);\n const tz = pz(b) - pz(a);\n const tl = Math.hypot(tx, tz);\n if (tl > 1e-6) {\n // Perpendicular to the row, signed toward the focal.\n let nx = -tz / tl;\n let nz = tx / tl;\n if (nx * dx + nz * dz < 0) { nx = -nx; nz = -nz; }\n dx = nx;\n dz = nz;\n }\n }\n // atan2(x, z): the yaw that rotates local +Z onto (dx, dz).\n yaw[i] = (dx === 0 && dz === 0) ? 0 : Math.atan2(dx, dz);\n }\n };\n\n for (let i = 1; i <= count; i++) {\n if (i === count || rowIdAt(i) !== rowIdAt(runStart)) {\n flushRun(runStart, i);\n runStart = i;\n }\n }\n return yaw;\n}\n","/**\n * Pure builder for the instanced seat cloud. Produces the per-instance arrays a\n * single OGL InstancedMesh consumes (one draw call for every seat), plus the\n * seatId → instanceIndex map that `setAvailability` uses to patch only the seats\n * that actually changed via a sub-range `bufferSubData` upload.\n */\n\nimport { accessibilityRingColor, type ExpandedSeat } from '../../core/types';\nimport { hexToRgb } from '../palette';\nimport { SEATED_EYE_HEIGHT_M } from '../../core/units';\nimport { M } from './geometry';\nimport { chairHalfWidth } from './seatChair';\nimport { seatStateIndex, SEAT_STATES, type SeatState3D } from '../palette';\nimport type { VenueSurfaces } from './surface';\n\n/**\n * World radius of a seat dot in metres — the single source for the shader\n * uniform AND for how far a section deck is padded so its seats sit on it.\n * (The shader may grow this with distance to hold `uMinPixels`; this is the\n * near-field base.)\n */\nexport const SEAT_DOT_RADIUS_M = 0.22;\n\n/**\n * Whether a seat has somebody in it, and who.\n *\n * Occupancy is not invented — it is the inventory. A seat that is SOLD has been\n * bought by a person, and a HELD one is being bought right now, so those are the\n * seats that get an occupant. Available and dimmed seats stay empty, and a\n * SELECTED seat stays empty too: that is the buyer's own seat, and drawing a\n * stranger in it while they choose it would be worse than an empty hall.\n *\n * This means the crowd tells the truth about the event. A nearly-sold show looks\n * full from the seat you are considering, and a quiet one looks quiet — which is\n * information a buyer actually wants, arrived at without inventing anything.\n *\n * Returns a negative value for an empty seat; otherwise a stable per-seat hash\n * in [0,1) that varies the occupant's build and tone. Stable so a person does\n * not change shape as the near-field set is re-gathered around a moving camera.\n */\nexport function seatOccupantSeed(stateIndex: number, seatIndex: number): number {\n const state = SEAT_STATES[stateIndex];\n if (state !== 'sold' && state !== 'held') return -1;\n // Cheap integer hash — deterministic, no Math.random, and well distributed\n // across neighbouring indices so adjacent seats do not come out matching.\n let h = (seatIndex + 1) * 2654435761;\n h ^= h >>> 15;\n h = Math.imul(h, 2246822519);\n h ^= h >>> 13;\n return ((h >>> 0) % 100000) / 100000;\n}\n\n/**\n * Fraction of a seat's nearest-neighbour spacing that its dot may occupy.\n *\n * Below 0.5 two adjacent dots cannot touch even when both sit at their ceiling,\n * so a gap always survives between rows. 0.42 leaves that gap visible rather\n * than hairline.\n */\nexport const SEAT_PITCH_FRACTION = 0.42;\n\nexport interface SeatInstanceData {\n count: number;\n /** vec3 per instance: world (x, y, z) in metres. */\n iPosition: Float32Array;\n /** float per instance: index into the seat-state colour LUT. */\n iState: Float32Array;\n /**\n * vec3 per instance: the seat's CATEGORY colour, used while it is available.\n *\n * 2D fills a free seat with its category colour and only overrides it once the\n * seat is held/sold/selected. 3D used one flat green for every available seat,\n * which threw away the price-tier read on switching views and made a full\n * house look like a status map instead of a venue.\n */\n iCategory: Float32Array;\n /**\n * float per instance: the largest world radius this dot may take, metres.\n *\n * The shader enforces `uMinPixels` by GROWING a dot's world radius with depth,\n * which is what merges rows into a solid mass at range — measured mean seat\n * spacing is 0.53–0.58 m against a 0.44 m dot diameter, so there is very little\n * slack to spend before neighbours touch. A global cap cannot fix it: spacing\n * is a property of the chart, and varies between sections of the same venue\n * (measured min 0.21 m on the amphitheatre against a 0.58 m mean).\n *\n * So the ceiling travels per seat, derived from that seat's own nearest\n * neighbour. A dot grows to hold its minimum pixel size and then STOPS,\n * whatever the distance. Past that point holding legibility is the LOD ladder's\n * job (fade toward the tier tint), not the dot's.\n */\n iMaxRadius: Float32Array;\n /**\n * vec3 per instance: accommodation ring colour, or (0,0,0) for none.\n *\n * 2D draws a coloured ring around every seat with an accessibility type, and\n * 3D drew nothing at all — so a wheelchair space, a companion seat or a\n * lift-armrest seat was indistinguishable from any other the moment a buyer\n * switched to the 3D view. A ring mirrors the 2D treatment exactly, needs no\n * texture, and costs one instanced attribute rather than a draw call.\n */\n iRing: Float32Array;\n /** float per instance: owning floor index, for per-floor isolation. */\n iFloor: Float32Array;\n /**\n * float per instance: facing yaw in radians, for the near-field chair mesh.\n *\n * A billboard dot has no front, so nothing here ever needed a direction. A\n * chair does, and getting it wrong is worse than drawing nothing — a row of\n * seats with their backs to the stage reads as a mistake immediately.\n *\n * Left at zero by this builder and filled by `sceneModel`, which is the layer\n * that knows what each seat FACES (its floor's focal point). Direct callers\n * and tests that build instances without a scene get zeros, which is a\n * well-defined \"facing +Z\" rather than an undefined attribute.\n */\n iYaw: Float32Array;\n /**\n * float per instance: half-width of this seat's near-field CHAIR, world metres.\n *\n * Deliberately not `iMaxRadius`. That value is the dot's ceiling — conservative\n * by design so round dots stay visually apart, and clamped to\n * SEAT_DOT_RADIUS_M on top of that. Sizing a chair from it gave 0.34 m chairs\n * under 0.95 m backs: an aspect ratio near 3:1, which reads as a headstone.\n * Real seats very nearly touch. See `chairHalfWidth`.\n */\n iChairWidth: Float32Array;\n /** 1 = physical chair; 0 = sellable empty wheelchair bay. */\n iPhysicalSeat: Float32Array;\n /** seatId → instance index (drives targeted availability updates). */\n idToIndex: Map<string, number>;\n}\n\n/**\n * Nearest-neighbour distance in chart units for every seat, via a uniform grid.\n *\n * Linear in seat count for realistic charts (the grid cell is sized to the mean\n * spacing, so each lookup touches a bounded neighbourhood) — the naive pairwise\n * version is 14k² on the Uber Arena and would show up in build time.\n */\nfunction nearestNeighbourSpacing(seats: ExpandedSeat[]): Float64Array {\n const n = seats.length;\n const out = new Float64Array(n);\n if (n < 2) { out.fill(Infinity); return out; }\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const s of seats) {\n if (s.x < minX) minX = s.x;\n if (s.y < minY) minY = s.y;\n if (s.x > maxX) maxX = s.x;\n if (s.y > maxY) maxY = s.y;\n }\n const w = Math.max(maxX - minX, 1e-6), h = Math.max(maxY - minY, 1e-6);\n // Aim at ~1 seat per cell: cell = sqrt(area / count).\n const cell = Math.max(Math.sqrt((w * h) / n), 1e-6);\n const cols = Math.max(1, Math.ceil(w / cell) + 1);\n const rows = Math.max(1, Math.ceil(h / cell) + 1);\n const buckets = new Map<number, number[]>();\n const cellOf = (x: number, y: number): number => {\n const cx = Math.min(cols - 1, Math.max(0, Math.floor((x - minX) / cell)));\n const cy = Math.min(rows - 1, Math.max(0, Math.floor((y - minY) / cell)));\n return cy * cols + cx;\n };\n for (let i = 0; i < n; i++) {\n const k = cellOf(seats[i].x, seats[i].y);\n const b = buckets.get(k);\n if (b) b.push(i); else buckets.set(k, [i]);\n }\n for (let i = 0; i < n; i++) {\n const s = seats[i];\n const cx = Math.min(cols - 1, Math.max(0, Math.floor((s.x - minX) / cell)));\n const cy = Math.min(rows - 1, Math.max(0, Math.floor((s.y - minY) / cell)));\n let best = Infinity;\n // Widen the ring until a hit is found — a sparse region (a lone box seat)\n // would otherwise report Infinity and lose its ceiling entirely.\n for (let r = 1; r <= 4; r++) {\n for (let gy = cy - r; gy <= cy + r; gy++) {\n if (gy < 0 || gy >= rows) continue;\n for (let gx = cx - r; gx <= cx + r; gx++) {\n if (gx < 0 || gx >= cols) continue;\n // Only the newly-added ring after the first pass.\n if (r > 1 && Math.abs(gy - cy) < r && Math.abs(gx - cx) < r) continue;\n const b = buckets.get(gy * cols + gx);\n if (!b) continue;\n for (const j of b) {\n if (j === i) continue;\n const d = Math.hypot(seats[j].x - s.x, seats[j].y - s.y);\n if (d < best) best = d;\n }\n }\n }\n if (Number.isFinite(best) && best <= r * cell) break;\n }\n out[i] = best;\n }\n return out;\n}\n\n/**\n * Resolve a seat's surface height in world metres — the fallback path used only\n * when no venue surface was supplied (direct callers, tests). Prefer the\n * section-resolved eye height minus the seated-eye offset.\n *\n * There is deliberately NO lift constant here any more. A dot is placed exactly\n * ON the deck, and the seat shader offsets the billboard by its own radius along\n * screen-up so the dot RESTS on that point at every distance. A fixed lift could\n * not do that: the shader enforces `uMinPixels`, so a dot's world radius grows\n * with depth and any constant clearance is eventually smaller than the dot.\n */\nfunction seatSurfaceY(seat: ExpandedSeat): number {\n const eye = seat.eyeHeightM;\n if (Number.isFinite(eye)) return Math.max(0, (eye as number) - SEATED_EYE_HEIGHT_M);\n return 0;\n}\n\nexport function buildSeatInstances(\n seats: ExpandedSeat[],\n initial?: (seat: ExpandedSeat) => SeatState3D,\n surfaces?: VenueSurfaces,\n seatFloor?: Float32Array,\n /** categoryKey -> display colour, for the AVAILABLE tint. Omitted by direct\n * callers and tests, which then get the flat state green as before. */\n categoryColor?: Map<string, string>,\n): SeatInstanceData {\n const count = seats.length;\n const iPosition = new Float32Array(count * 3);\n const iState = new Float32Array(count);\n const iCategory = new Float32Array(count * 3);\n const iMaxRadius = new Float32Array(count);\n const iChairWidth = new Float32Array(count);\n const iPhysicalSeat = new Float32Array(count);\n const iRing = new Float32Array(count * 3);\n const idToIndex = new Map<string, number>();\n const spacing = nearestNeighbourSpacing(seats);\n for (let i = 0; i < count; i++) {\n const seat = seats[i];\n // The seat's OWN row pitch when its section resolved into rows; the raw\n // nearest-neighbour distance only as a fallback for unstructured seats.\n // See `VenueSurfaces.seatPitchU` for why the raw measure is wrong.\n const resolved = surfaces?.seatPitchU(i);\n const pitchM = (resolved ?? spacing[i]) * M;\n // The ceiling is allowed BELOW the base radius. A chart authored tighter\n // than the 0.22 m default dot (measured 0.21 m minimum spacing on the\n // amphitheatre) overlaps at every distance today, including close up, and\n // shrinking those dots is the only thing that separates them. A small floor\n // keeps a pathologically dense chart's seats visible rather than vanishing.\n // A seat with no resolvable neighbour keeps the base radius, not an\n // unbounded one.\n iMaxRadius[i] = Number.isFinite(pitchM)\n ? Math.max(0.06, Math.min(SEAT_DOT_RADIUS_M, pitchM * SEAT_PITCH_FRACTION))\n : SEAT_DOT_RADIUS_M;\n // The chair reads the SAME pitch but through its own rule — see iChairWidth.\n iChairWidth[i] = chairHalfWidth(Number.isFinite(pitchM) ? pitchM : undefined);\n iPhysicalSeat[i] = seat.wheelchairSpaceType === 'no-seat' ? 0 : 1;\n iPosition[i * 3] = seat.x * M;\n // The venue surface wins when present: it is the same function the tier cap\n // is built from, which is what guarantees a seat can never sit inside it.\n iPosition[i * 3 + 1] = surfaces ? surfaces.seatDeckY(i) : seatSurfaceY(seat);\n iPosition[i * 3 + 2] = seat.y * M;\n iState[i] = seatStateIndex(initial ? initial(seat) : 'available');\n // The seat's CATEGORY colour, which is what 2D paints a free seat with\n // (`SeatmapRenderer.paintSeat` -> `seatBaseColor(categoryKey)`). 3D painted\n // every available seat one flat green instead, so a venue lost its price\n // tiers the moment a buyer switched view — and at seat level a hall of\n // identical green read as a diagram rather than a room. Falls back to the\n // same #6e7bff 2D uses when a category has no colour.\n const cat = hexToRgb(categoryColor?.get(seat.categoryKey) ?? '#6e7bff') ?? [0.43, 0.48, 1.0];\n iCategory[i * 3] = cat[0];\n iCategory[i * 3 + 1] = cat[1];\n iCategory[i * 3 + 2] = cat[2];\n // Same source of truth as 2D: the ring colour is derived from the seat's\n // accessibility types, so the two views cannot disagree about which seat is\n // an accessible one.\n if (seat.accessibility?.length) {\n const rgb = hexToRgb(accessibilityRingColor(seat.accessibility));\n if (rgb) { iRing[i * 3] = rgb[0]; iRing[i * 3 + 1] = rgb[1]; iRing[i * 3 + 2] = rgb[2]; }\n }\n idToIndex.set(seat.id, i);\n }\n return {\n count, iPosition, iState, iCategory, iMaxRadius, iRing, idToIndex, iChairWidth, iPhysicalSeat,\n iFloor: seatFloor ?? new Float32Array(count),\n iYaw: new Float32Array(count),\n };\n}\n\n/** Contiguous run of instance indices to upload in a single bufferSubData call. */\nexport interface DirtyRun {\n start: number;\n /** number of instances (floats, since iState is 1 float/instance). */\n length: number;\n}\n\n/**\n * Instances worth re-uploading just to avoid another GL call.\n *\n * Runs are merged across gaps up to this size. A `bufferSubData` has fixed\n * driver overhead regardless of how few bytes it carries, so uploading a handful\n * of unchanged instances inside one call is far cheaper than issuing a second.\n *\n * The case this exists for is a live socket delta on a big venue, where the\n * changed seats are SCATTERED rather than contiguous. Measured on a 50,400-seat\n * chart before merging: 1,000 scattered seats produced 983 separate uploads and\n * 5,000 produced 4,438 — thousands of GL calls in a single frame to move a few\n * kilobytes. A block going on sale already coalesced well; random churn did not.\n */\nconst RUN_MERGE_GAP = 64;\n\n/**\n * Apply state updates to the CPU `iState` array and return the coalesced\n * contiguous runs that changed — the caller uploads exactly those ranges and\n * never the whole buffer.\n */\nexport function applySeatStates(\n data: SeatInstanceData,\n updates: Array<{ seatId: string; state: SeatState3D }>,\n): DirtyRun[] {\n const changed: number[] = [];\n for (const u of updates) {\n const idx = data.idToIndex.get(u.seatId);\n if (idx === undefined) continue;\n const v = seatStateIndex(u.state);\n if (data.iState[idx] !== v) {\n data.iState[idx] = v;\n changed.push(idx);\n }\n }\n if (!changed.length) return [];\n changed.sort((a, b) => a - b);\n const runs: DirtyRun[] = [];\n let start = changed[0];\n let prev = changed[0];\n for (let i = 1; i < changed.length; i++) {\n const idx = changed[i];\n if (idx === prev) continue;\n // Bridge small gaps: the unchanged instances in between are rewritten from\n // `iState`, which already holds their current value, so this is a no-op for\n // them and saves a GL call. See RUN_MERGE_GAP.\n if (idx <= prev + RUN_MERGE_GAP) { prev = idx; continue; }\n runs.push({ start, length: prev - start + 1 });\n start = idx;\n prev = idx;\n }\n runs.push({ start, length: prev - start + 1 });\n return runs;\n}\n","/**\n * Resolve a chart's authored theme into the 3D scene's colours.\n *\n * Every chart already carries a `ChartTheme` — background, brand accent, seat\n * scale — and the 2D renderer and the picker chrome honour it. The 3D view did\n * not: `palette.ts` was a fixed set of constants, so a white-labelled event\n * rendered in SeatLayer's own dark grey whatever the organizer had branded. That\n * is a visible gap in a paid feature, and it is the kind of thing a customer\n * notices immediately when they switch from the 2D map to the 3D view.\n *\n * The palette in `palette.ts` stays the DEFAULT and the reference. This module\n * only rebases it, so an unthemed chart is byte-for-byte what it was.\n *\n * ## How structure is rebased\n *\n * Structure colours are not replaced by the brand colour — a venue rendered in\n * flat brand paint reads as a diagram, not a building, and the look brief is\n * deliberately \"desaturated greys for structure, saturated colour only on\n * seats\". Instead each structure colour is blended a little way toward the\n * authored background, so the whole venue picks up the brand's cast and sits in\n * its own light, while keeping the tonal relationships (tier above wall, stage\n * warmer than tier) that make the geometry readable.\n */\n\nimport type { ChartTheme } from '../core/types';\nimport { BACKGROUND, SEAT_STATE_COLORS, STRUCTURE, hexToRgb, mix, scaleRgb, type RGB, type SeatState3D } from './palette';\n\n/** How far a structure colour is pulled toward the authored background. */\nconst BACKGROUND_INFLUENCE = 0.15;\n\n/**\n * The background gradient's two stops as multiples of the authored colour.\n *\n * Fitted to the existing hand-tuned gradient on the default `#0e1117`, whose\n * per-channel ratios are 0.91/0.90/0.89 for the top stop and 1.82/1.80/1.66 for\n * the bottom. A single scalar cannot reproduce a hand-picked triple exactly, so\n * these are the best fit: an authored background lands within ~0.01 of the old\n * look, and an UNTHEMED chart bypasses this path entirely and stays identical.\n * What matters is that a themed chart gets the same vertical falloff around its\n * own colour rather than a flat wash.\n */\nconst BG_TOP_SCALE = 0.9;\nconst BG_BOTTOM_SCALE = 1.76;\n\n/** Bounds on the authored seat-size multiplier, matching `ChartTheme.seatScale`. */\nconst SEAT_SCALE_MIN = 0.7;\nconst SEAT_SCALE_MAX = 1.6;\n\nexport interface Theme3D {\n background: { top: RGB; bottom: RGB };\n structure: typeof STRUCTURE;\n seatStates: Record<SeatState3D, RGB>;\n /** Multiplier on the seat dot's world radius. */\n seatScale: number;\n}\n\n/** The unthemed default — the palette exactly as authored in `palette.ts`. */\nexport function defaultTheme3D(): Theme3D {\n return {\n background: { top: [...BACKGROUND.top] as RGB, bottom: [...BACKGROUND.bottom] as RGB },\n structure: STRUCTURE,\n seatStates: { ...SEAT_STATE_COLORS },\n seatScale: 1,\n };\n}\n\nexport function resolveTheme3D(theme: ChartTheme | undefined): Theme3D {\n const base = defaultTheme3D();\n if (!theme) return base;\n\n const bg = hexToRgb(theme.background);\n if (bg) {\n base.background = {\n top: scaleRgb(bg, BG_TOP_SCALE),\n bottom: scaleRgb(bg, BG_BOTTOM_SCALE),\n };\n // Rebase every structure colour onto the authored background. Done as one\n // pass over the palette rather than field by field, so a colour added to\n // STRUCTURE later is themed automatically instead of silently staying fixed.\n const rebased: Record<string, RGB> = {};\n for (const [key, value] of Object.entries(STRUCTURE)) {\n rebased[key] = mix(value as RGB, bg, BACKGROUND_INFLUENCE);\n }\n base.structure = rebased as unknown as typeof STRUCTURE;\n }\n\n // Selection is the one seat colour a brand owns: it is the buyer's own\n // choice reflected back, and the picker chrome already paints it in `accent`.\n // Availability, held and sold stay fixed — they carry MEANING, and letting a\n // brand recolour \"sold\" would let a chart mislead about what is for sale.\n const selection = hexToRgb(theme.selectionColor) ?? hexToRgb(theme.accent);\n if (selection) base.seatStates = { ...base.seatStates, selected: selection };\n\n const scale = theme.seatScale;\n if (typeof scale === 'number' && Number.isFinite(scale)) {\n base.seatScale = Math.min(SEAT_SCALE_MAX, Math.max(SEAT_SCALE_MIN, scale));\n }\n return base;\n}\n\n/** Flat LUT (5 × vec3) for the seat fragment shader, in `SEAT_STATES` order. */\nexport function themeSeatColorLUT(theme: Theme3D, order: readonly SeatState3D[]): number[] {\n const out: number[] = [];\n for (const s of order) out.push(...theme.seatStates[s]);\n return out;\n}\n","/**\n * Venue labels — anchors, level-of-detail, and world→screen projection.\n *\n * ## Why labels are DOM, not geometry\n *\n * The renderer is deliberately texture-free at three draw calls. Drawing text on\n * the GPU means a signed-distance font atlas: a texture, another shader, a build\n * asset, and a resolution ceiling — a lot of machinery for the few dozen labels a\n * venue actually needs. Projecting anchors and positioning DOM elements costs\n * nothing when a chart has no labels, and buys properties the GPU path cannot:\n *\n * - **Real text.** A screen reader can read the venue's structure. That is the\n * accessibility gap in 3D, not just a rendering convenience.\n * - Crisp at any device pixel ratio and any zoom, with no atlas to outgrow.\n * - `ChartTheme.fontFamily`, i18n and RTL come from the browser.\n *\n * This module is the pure half — what to label, where its anchor sits, and when\n * it should show. The overlay that positions elements lives in `index.ts`.\n *\n * ## Why the rungs mirror 2D\n *\n * 2D melts through zones → sections → seats. Labels follow the same idea for the\n * same reason: at a distance a buyer needs to know which part of the venue they\n * are looking at, and up close they need to know which block and which door. A\n * label set that does not thin out with distance turns a 51-section arena into\n * unreadable confetti.\n */\n\nimport type { Point } from '../core/types';\n\nexport type LabelKind = 'zone' | 'section' | 'ga' | 'annotation' | 'booth' | 'row' | 'seat';\n\nexport interface SceneLabel {\n id: string;\n kind: LabelKind;\n text: string;\n /** World-metre anchor the label is pinned to. */\n anchor: [number, number, number];\n /** Authored colour (`#rrggbb`), when the object carries one. */\n color?: string;\n /** Authored rotation in degrees, for annotations that specify one. */\n rotation?: number;\n}\n\n/**\n * Distance thresholds as multiples of the venue radius, matching the seat LOD's\n * scale so labels and seats thin out together rather than fighting.\n *\n * Zone labels are the far rung and switch OFF close in, where they would sit on\n * top of the section labels that have become more useful. Section labels are the\n * middle rung. Annotations and booth labels are wayfinding — only legible, and\n * only wanted, once the buyer is actually in that part of the venue.\n */\nconst ZONE_MIN_DISTANCE = 1.15;\nconst SECTION_MAX_DISTANCE = 2.2;\nconst NEAR_MAX_DISTANCE = 0.85;\n/**\n * Row and seat identity are the last two rungs, mirroring 2D's melt: a buyer\n * this close has stopped choosing a part of the venue and started choosing a\n * place to sit. Seats come in last of all — a seat number is unreadable and\n * useless until you are close enough that one row fills much of the screen.\n */\nconst ROW_MAX_DISTANCE = 0.5;\nconst SEAT_MAX_DISTANCE = 0.26;\n\n/** Which label kinds should show at this camera distance. */\nexport function visibleLabelKinds(distance: number, venueRadius: number): Set<LabelKind> {\n const r = Math.max(1e-6, venueRadius);\n const d = distance / r;\n const out = new Set<LabelKind>();\n if (d >= ZONE_MIN_DISTANCE) out.add('zone');\n // A GA area is named at the same rung as a section because that is what it is\n // — a sellable part of the venue the buyer picks before picking a place in it.\n if (d <= SECTION_MAX_DISTANCE) { out.add('section'); out.add('ga'); }\n if (d <= NEAR_MAX_DISTANCE) { out.add('annotation'); out.add('booth'); }\n if (d <= ROW_MAX_DISTANCE) out.add('row');\n if (d <= SEAT_MAX_DISTANCE) out.add('seat');\n return out;\n}\n\nexport interface Projected {\n /** CSS pixels from the container's left/top. */\n x: number;\n y: number;\n /** Normalised depth; smaller is nearer. */\n depth: number;\n /** False when the anchor is behind the camera or outside the frustum. */\n visible: boolean;\n}\n\n/**\n * Project a world point through a column-major 4x4 view-projection matrix.\n *\n * Behind-camera points are reported invisible rather than mirrored to the far\n * side of the screen, which is what a naive divide by a negative w produces —\n * a label for the section behind you appearing over the stage in front of you.\n */\nexport function projectToScreen(\n viewProjection: ArrayLike<number>,\n p: readonly [number, number, number],\n width: number,\n height: number,\n): Projected {\n const m = viewProjection;\n const x = p[0], y = p[1], z = p[2];\n const cx = m[0] * x + m[4] * y + m[8] * z + m[12];\n const cy = m[1] * x + m[5] * y + m[9] * z + m[13];\n const cz = m[2] * x + m[6] * y + m[10] * z + m[14];\n const cw = m[3] * x + m[7] * y + m[11] * z + m[15];\n if (!(cw > 1e-6)) return { x: 0, y: 0, depth: Infinity, visible: false };\n const ndcX = cx / cw, ndcY = cy / cw, ndcZ = cz / cw;\n const inside = ndcX >= -1.05 && ndcX <= 1.05 && ndcY >= -1.05 && ndcY <= 1.05 && ndcZ <= 1;\n return {\n x: (ndcX * 0.5 + 0.5) * width,\n y: (1 - (ndcY * 0.5 + 0.5)) * height,\n depth: ndcZ,\n visible: inside,\n };\n}\n\n/**\n * Drop labels that would overlap, nearest kept.\n *\n * Without this a dense venue paints its section names on top of each other and\n * every one of them becomes unreadable — worse than showing fewer. Nearest-wins\n * because the label a buyer is closest to is the one they are asking about.\n *\n * The test is RECTANGULAR, not radial. A label is a line of text: wide and\n * short. A single radius big enough to stop two names colliding side by side is\n * far bigger than the vertical gap they actually need, so a radial test threw\n * away labels that were stacked but perfectly readable — on the amphitheatre it\n * dropped \"Terrace\" purely for sitting between the other two concentric zones.\n */\nexport function cullOverlapping<T extends { screen: Projected }>(\n items: T[],\n separationX: number,\n separationY: number = separationX,\n): T[] {\n const kept: T[] = [];\n const ordered = [...items].sort((a, b) => a.screen.depth - b.screen.depth);\n for (const item of ordered) {\n let clash = false;\n for (const k of kept) {\n const dx = Math.abs(item.screen.x - k.screen.x);\n const dy = Math.abs(item.screen.y - k.screen.y);\n // Only a genuine box overlap counts: near on BOTH axes.\n if (dx < separationX && dy < separationY) { clash = true; break; }\n }\n if (!clash) kept.push(item);\n }\n return kept;\n}\n\n/**\n * How many of the dense per-place labels may be painted at once.\n *\n * The overlap cull above is necessary and NOT sufficient, and the gap between\n * those two is a real defect: it only removes labels that collide, so the closer\n * the camera gets the FEWER collisions there are and the MORE labels survive.\n * Flown to a seat in a 3,605-seat amphitheatre, every row and seat name in the\n * frustum passed the test and the frame became a wall of tiled text with the\n * seating unreadable behind it — the labels stopped describing the venue and\n * became the thing obscuring it.\n *\n * A budget is the missing rung. A buyer arriving at their seat wants the handful\n * of names around them, not an index of the building; past a dozen or so the set\n * has no informational value left, only cost.\n */\nexport const DENSE_LABEL_BUDGET: Record<'row' | 'seat', number> = { row: 8, seat: 14 };\n\n/**\n * Order dense labels by how much the buyer is likely to care: near the camera\n * and near the middle of the frame first.\n *\n * Both terms are needed. Distance alone keeps labels for seats behind the\n * camera's shoulder that happen to be metres away; screen-centre alone keeps a\n * label for a seat on the far side of the venue purely for being dead ahead. The\n * product ranks \"what I am looking at, close to me\" top, which is the seat a\n * buyer just flew to.\n */\nexport function focusScore(\n screen: Projected,\n worldDistance: number,\n width: number,\n height: number,\n): number {\n const half = Math.max(1, Math.min(width, height) * 0.5);\n const off = Math.min(2, Math.hypot(screen.x - width / 2, screen.y - height / 2) / half);\n return worldDistance * (1 + 1.5 * off);\n}\n\n/**\n * Pick the dense labels to paint: rank by focus, drop overlaps in that order,\n * then take at most `budget`.\n *\n * Order matters. Culling overlaps FIRST and then truncating would let an\n * arbitrary far-corner label win a slot over the seat the buyer is sitting in,\n * because the overlap pass keeps whatever it happens to reach first.\n */\nexport function pickDenseLabels<T extends { screen: Projected; focus: number }>(\n items: T[],\n separationX: number,\n separationY: number,\n budget: number,\n): T[] {\n const ordered = [...items].sort((a, b) => a.focus - b.focus);\n const kept: T[] = [];\n for (const item of ordered) {\n if (kept.length >= budget) break;\n let clash = false;\n for (const k of kept) {\n if (Math.abs(item.screen.x - k.screen.x) < separationX\n && Math.abs(item.screen.y - k.screen.y) < separationY) { clash = true; break; }\n }\n if (!clash) kept.push(item);\n }\n return kept;\n}\n\n/** Mean of a point set, or null when empty. */\nexport function centroidOf(points: readonly Point[]): Point | null {\n if (!points.length) return null;\n let x = 0, y = 0;\n for (const p of points) { x += p.x; y += p.y; }\n return { x: x / points.length, y: y / points.length };\n}\n","/**\n * The JS-side source of truth for the 3D scene — a pure, GPU-free description\n * built once from the chart's existing height contract. Everything the renderer\n * uploads (merged solid geometry, the instanced seat cloud, camera-framing\n * bounds, the seat-state colour LUT) is derived here, so it survives a WebGL\n * context loss: on `webglcontextrestored` the renderer simply re-uploads from\n * this model without recomputing anything.\n *\n * Feeds 100% from `sectionGeometry` / `Floor.baseHeightM` / `ExpandedSeat`\n * (docs/3d-program-workorder §Architecture) — no new chart data is invented.\n */\n\nimport type { ChartDoc, ChartObject, ExpandedSeat, Point, SectionObject, ShapeObject } from '../../core/types';\nimport { CHART_UNITS_PER_METRE } from '../../core/units';\nimport { SEAT_STATES, STRUCTURE, hexToRgb, mix, desaturate, scaleRgb, type RGB } from '../palette';\nimport { resolveTheme3D, themeSeatColorLUT, type Theme3D } from '../theme';\nimport { centroidOf, type SceneLabel } from '../labels';\nimport {\n MeshBuilder, extrudePrism, mergeMeshData, ellipsePolygon, rectPolygon, outsetRing, M, type MeshData,\n} from './geometry';\nimport polygonClipping from 'polygon-clipping';\nimport { buildSeatInstances, SEAT_DOT_RADIUS_M, type SeatInstanceData } from './seatInstances';\nimport { computeSeatYaw } from './seatChair';\nimport { buildVenueSurfaces, CAP_MAX_ERROR_M, type SectionSurface } from './surface';\n\n/** Axis-aligned stage footprint in world metres. */\nexport interface StageBounds {\n cx: number;\n cz: number;\n y: number;\n halfX: number;\n halfZ: number;\n}\nimport { emitDeckBands, deckFootprints, rowNeighbourhoods } from './deckBands';\n\n/** One resolved plane of geometry — a single-floor chart is one of these. */\ninterface FloorUnit {\n objects: ChartObject[];\n focal: Point;\n baseHeightM: number;\n}\n\n/**\n * A navigable zone — the venue's own top-level grouping (Orchestra, Lower Bowl,\n * Hall A), resolved for camera framing and for the section/zone LOD rung.\n *\n * Every shipped chart authors zones and gives every section one, and the 2D\n * renderer uses them as its farthest LOD rung. 3D ignored them entirely, so the\n * one structure a buyer navigates by (\"take me to the Grand Circle\") had no\n * representation at all in the 3D view.\n */\nexport interface SceneZone {\n id: string;\n label: string;\n /** Authored zone colour, or null when the chart leaves it to the category mix. */\n color: RGB | null;\n /** Section object ids belonging to this zone. */\n sectionIds: string[];\n /** Seats resolved into this zone. */\n seatCount: number;\n /** World-metre centre of the zone's seats (camera target). */\n center: [number, number, number];\n /** Half-diagonal of its footprint, world metres (camera fit). */\n radius: number;\n /** What this zone faces — its authored focal, else the venue's. */\n focalWorld: [number, number, number];\n}\n\n/**\n * One seated section, resolved for navigation — the middle rung of the venue\n * ladder, between a zone and a seat. A chart with no zones authored still has\n * sections, so this is the level at which every venue can be navigated.\n */\nexport interface SceneSection {\n id: string;\n /** Buyer-facing name, matching the section label. */\n label: string;\n seatCount: number;\n /** World-metre centre of the section's seats (camera target). */\n center: [number, number, number];\n /** Half-diagonal of its seat footprint, world metres (camera fit). */\n radius: number;\n /** What this section faces — the camera approaches from this side. */\n focalWorld: [number, number, number];\n}\n\n/** One authored row resolved for guided large-venue navigation. */\nexport interface SceneRow {\n id: string;\n label: string;\n sectionId?: string;\n seatCount: number;\n center: [number, number, number];\n radius: number;\n}\n\n/** A navigable floor — a logical level of the venue. */\nexport interface SceneFloor {\n index: number;\n id: string;\n label: string;\n seatCount: number;\n center: [number, number, number];\n radius: number;\n}\n\nexport interface SceneModel {\n /** Every non-seat surface merged into one triangle soup (1 draw call). */\n solids: MeshData;\n seats: SeatInstanceData;\n bounds: {\n /** World-metre venue centre (camera target). */\n center: [number, number, number];\n /** Half-diagonal of the horizontal footprint, metres (camera fit). */\n radius: number;\n groundY: number;\n };\n /** 5 × vec3 flat LUT for the seat fragment shader. */\n stateColorLUT: number[];\n /** Resolved colours for this chart's authored theme (background, seat scale). */\n theme: Theme3D;\n seatCount: number;\n /** Venue focal point in world metres (cinematic look-at target). */\n focalWorld: [number, number, number];\n /** The venue's zones, in authored order. Empty when the chart has none. */\n zones: SceneZone[];\n /**\n * Stage footprints in world metres.\n *\n * Collected for stage lighting, which is not shipped: additive cones were\n * built, placed correctly and still could not be read from a seat (see\n * `git log` for `stageLight.ts`). The bounds stay because they are cheap,\n * correct, and exactly what a second attempt needs — the geometry was never\n * the hard part, the technique was.\n */\n stages: StageBounds[];\n /** Seated sections, in authored order — the navigable middle rung. */\n sections: SceneSection[];\n /** Rows used by the Section → Row → Seat locator. */\n rows: SceneRow[];\n /**\n * The venue's floors, in authored order. A single-floor chart reports one.\n *\n * Every shipped multi-floor template puts all its floors at baseHeightM 0 and\n * takes height from the sections instead, so floors are a logical grouping,\n * not a physical stack. What they need is ISOLATION: draw all three of an\n * opera house at once and the balcony sits over the parterre.\n */\n floors: SceneFloor[];\n /**\n * Everything worth naming, with a world anchor: zones, sections, authored\n * wayfinding text, and booths. Rendered as a DOM overlay — see `labels.ts` for\n * why text is not geometry here.\n */\n labels: SceneLabel[];\n}\n\nexport function shapeVisibleInConfiguration(\n shape: Pick<ShapeObject, 'eventConfigurationIds'>,\n activeConfigurationId?: string,\n): boolean {\n const ids = shape.eventConfigurationIds;\n return !ids?.length || (!!activeConfigurationId && ids.includes(activeConfigurationId));\n}\n\nfunction visibleObjects(objects: ChartObject[], activeConfigurationId?: string): ChartObject[] {\n return objects.filter((object) => (\n object.type !== 'shape' || shapeVisibleInConfiguration(object, activeConfigurationId)\n ));\n}\n\nfunction floorUnits(doc: ChartDoc, activeConfigurationId?: string): FloorUnit[] {\n if (doc.floors?.length) {\n return doc.floors.map((f) => ({\n objects: visibleObjects(f.objects, activeConfigurationId),\n focal: f.focalPoint ?? doc.focalPoint,\n baseHeightM: f.baseHeightM ?? 0,\n }));\n }\n return [{ objects: visibleObjects(doc.objects, activeConfigurationId), focal: doc.focalPoint, baseHeightM: 0 }];\n}\n\nconst AO = { top: 1.0, wallBottom: 0.5, bottomCap: 0.4 };\n\n// The cap's tessellation tolerance lives beside the surface it approximates\n// (surface.ts), together with the seat clearance derived from it.\n\n/**\n * Fold a surface's 2D fill colour into the dark structure palette: desaturate\n * ~40 %, darken, then ground it in the neutral structure grey so a tier top\n * reads architectural — a recognisable hue (purple/green/orange) but muted, not\n * candy-coloured paint. Risers/walls stay neutral concrete; baked AO still\n * multiplies these per vertex downstream. `null` fill ⇒ the neutral grey.\n */\nfunction tintTop(fill: RGB | null, neutral: RGB): RGB {\n if (!fill) return neutral;\n const muted = scaleRgb(desaturate(fill, 0.4), 0.62);\n return mix(neutral, muted, 0.72);\n}\n\n/**\n * Resolve each section's 2D paint colour, keyed by logical section id\n * (`logicalSectionId ?? id`, matching how expanded seats attribute `sectionId`):\n * the count-weighted mix of its member seats' category colours — the same source\n * the 2D renderer blends into a section's block fill. An explicit `section.color`\n * override is applied later (it wins in `sectionFill`).\n */\nfunction resolveSectionFills(doc: ChartDoc, seats: ExpandedSeat[]): Map<string, RGB> {\n const catColor = new Map<string, string>();\n for (const c of doc.categories ?? []) catColor.set(c.key, c.color);\n const counts = new Map<string, Map<string, number>>();\n for (const s of seats) {\n if (!s.sectionId) continue;\n let m = counts.get(s.sectionId);\n if (!m) { m = new Map(); counts.set(s.sectionId, m); }\n m.set(s.categoryKey, (m.get(s.categoryKey) ?? 0) + 1);\n }\n const out = new Map<string, RGB>();\n for (const [sid, byCat] of counts) {\n let r = 0, g = 0, b = 0, w = 0;\n for (const [key, n] of byCat) {\n const rgb = hexToRgb(catColor.get(key));\n if (!rgb) continue;\n r += rgb[0] * n; g += rgb[1] * n; b += rgb[2] * n; w += n;\n }\n if (w > 0) out.set(sid, [r / w, g / w, b / w]);\n }\n return out;\n}\n\n/** A section's fill: explicit `color` override wins, else the member-category mix. */\nfunction sectionFill(section: SectionObject, byLogical: Map<string, RGB>): RGB | null {\n return hexToRgb(section.color) ?? byLogical.get(section.logicalSectionId ?? section.id) ?? null;\n}\n\n/**\n * Extrude one section into the shared builder, capped by ITS OWN seating surface.\n *\n * The height function is not recomputed here — it comes from `surface.ts`, the\n * same object the seat dots stand on. That is the whole point: there is one\n * definition of the seating surface, and the cap, the walls and the seats all\n * read it, so no cross-file offset constant has to be kept in agreement.\n */\ninterface RingBox { minX: number; minY: number; maxX: number; maxY: number }\n\nfunction bboxOfRing(ring: readonly Point[]): RingBox {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const p of ring) {\n if (p.x < minX) minX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.x > maxX) maxX = p.x;\n if (p.y > maxY) maxY = p.y;\n }\n return { minX, minY, maxX, maxY };\n}\n\nfunction boxesOverlap(a: RingBox, b: RingBox): boolean {\n return a.minX <= b.maxX && b.minX <= a.maxX && a.minY <= b.maxY && b.minY <= a.maxY;\n}\n\n/**\n * A section's padded outline with every OTHER section's authored outline removed.\n *\n * Deck padding grows a section by 0.33 m so a seat authored hard against its\n * boundary still rests on deck. Where two sections abut, that growth crosses into\n * the neighbour — and if the neighbour is lower, the overhang covers its seats.\n * Measured on the concert hall: 27 stalls-terrace seats buried by 0.20 m beneath\n * the terrace beside them, which sits 0.60 m higher.\n *\n * Clipping to the section's own padded ring is not enough, because the padding IS\n * the intrusion. The rule that works is: grow to hold your own seats, but never\n * over another section's floor.\n */\nfunction paddedFootprint(section: SectionObject, siblings: readonly SectionObject[]): Point[][] {\n const padded = outsetRing(section.outline, SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE);\n // Only siblings whose bounding box actually overlaps can remove any area, and\n // a polygon boolean is expensive. Differencing against EVERY sibling made\n // scene build quadratic in section count: measured 2.7 s at 50k seats / 40\n // sections and 11.7 s at 99k / 60, where doubling the venue cost 4.3x the\n // time. A venue's sections are laid out around it, so almost no pair overlaps\n // and this prunes nearly all of the work.\n const box = bboxOfRing(padded);\n const others = siblings.filter((o) => o !== section\n && o.outline && o.outline.length >= 3\n && boxesOverlap(box, bboxOfRing(o.outline)));\n if (!others.length) return [padded];\n const toRing = (pts: readonly Point[]): [number, number][] => {\n const r = pts.map((p) => [p.x, p.y] as [number, number]);\n r.push(r[0]);\n return r;\n };\n try {\n const diff = polygonClipping.difference([toRing(padded)], ...others.map((o) => [toRing(o.outline)]));\n const out: Point[][] = [];\n for (const poly of diff) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push(pts);\n }\n return out.length ? out : [padded];\n } catch {\n return [padded];\n }\n}\n\n/** Intersect each block footprint with the section's own allowed area. */\nfunction clipFootprints(\n blocks: ReturnType<typeof deckFootprints>,\n allowed: Point[][],\n): ReturnType<typeof deckFootprints> {\n if (!allowed.length) return blocks;\n const toRing = (pts: readonly Point[]): [number, number][] => {\n const r = pts.map((p) => [p.x, p.y] as [number, number]);\n r.push(r[0]);\n return r;\n };\n const allowedPolys = allowed.map((a) => [toRing(a)]);\n const out: ReturnType<typeof deckFootprints> = [];\n for (const b of blocks) {\n // NOTE: a \"skip the clip when the block looks contained\" fast path was tried\n // here and reverted. Testing that the block's VERTICES lie inside the allowed\n // ring is not sound for a CONCAVE allowed region — and a section footprint is\n // routinely concave, since an arena wedge curves inward and siblings are\n // subtracted from it. An edge can bulge outside while every vertex is inside,\n // which buried seats on the arena. A sound test needs edge-intersection\n // checks, which is most of the cost of the boolean it would be avoiding.\n let pieces;\n try {\n pieces = polygonClipping.intersection([toRing(b.outline)], ...[allowedPolys.flat()]);\n } catch {\n out.push(b);\n continue;\n }\n for (const poly of pieces) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push({ outline: pts, holes: [], topY: b.topY });\n }\n }\n return out.length ? out : blocks;\n}\n\nfunction buildTier(\n builder: MeshBuilder,\n section: SectionObject,\n unit: FloorUnit,\n fill: RGB | null,\n surface: SectionSurface | undefined,\n claimed: ClaimedArea,\n siblings: readonly SectionObject[],\n S: typeof STRUCTURE,\n): void {\n if (!section.outline || section.outline.length < 3) return;\n const bottomY = unit.baseHeightM;\n const colTop = tintTop(fill, S.tierTop);\n if (!surface) return;\n const topY = (p: Point): number => surface.deckAt(p.x, p.y);\n // Pad the deck so a seat authored hard against the section boundary rests ON\n // its own section instead of overhanging into the aisle. Seats are chart data\n // and never move; the deck grows to meet them. 1.5x the dot footprint: enough\n // that a dot sitting exactly on the boundary still lands clear of the edge,\n // and small against real aisle widths.\n const footprint = paddedFootprint(section, siblings);\n const outline = footprint[0] ?? section.outline;\n\n // A section whose rows resolved into levels is drawn as GEOMETRY, not as a\n // tessellated field: a flat landing prism carrying one level ribbon per row,\n // with a riser between consecutive ribbons. See `deckBands.ts` for why a\n // refined cap cannot work once every row has to be level.\n if (surface.rowLevels.length >= 2) {\n // One base prism per BLOCK, hugging the ribbons above it, instead of one flat\n // plate over the section's whole authored outline. The outline reaches well\n // past the seating, so a single plate stuck out around the stands as a slab\n // and cut every block's end square through it. Each block also stands on its\n // OWN lowest row rather than the section's.\n // Clipped to the section's own footprint like the treads are: a block base is\n // built from a ribbon union carrying its own outward margin, so it overhangs\n // for the same reason and buries a lower neighbour's seats the same way.\n // One neighbourhood solve, shared by the footprint and the emission below.\n const nbrs = rowNeighbourhoods(surface.rowLevels);\n const blocks = clipFootprints(deckFootprints(surface.rowLevels, unit.focal, nbrs), footprint);\n // Explicit UP normals rather than face normals. A block footprint is a\n // boolean union of ~100 overlapping ribbons, and such a union always leaves\n // some very thin triangles along its seams; deriving a normal from one by\n // cross product is numerically meaningless and shades as a dark hairline.\n // The top is FLAT, so the correct normal is known outright and the thin\n // triangles become harmless.\n const capUp = (): [number, number, number] => [0, 1, 0];\n for (const b of blocks) {\n 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 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(), nbrs);\n return;\n }\n\n // A flat section's cap is exact at any tessellation, so it is left\n // unsubdivided (Infinity) and stays identical to the pre-surface mesh —\n // legacy height-less charts must not shift by a single vertex.\n const maxErr = surface.flat ? Infinity : CAP_MAX_ERROR_M;\n const topN = (p: Point): [number, number, number] => surface.normalAt(p.x, p.y);\n\n // Take only the ground this section has not already lost to an earlier one.\n //\n // Every flat section's top sits at exactly the same height, and the deck\n // padding above grows each one by 0.33 m, so neighbouring sections OVERLAP.\n // Two coplanar surfaces at identical depth is a z-fight, and it renders as a\n // serrated comb of interpenetrating teeth along every shared boundary — the\n // artifact visible on the flat chart. Depth-biasing them apart would hide it\n // rather than fix it, and would break down as soon as a chart has many\n // sections. Removing the overlap means there is nothing to fight over.\n // A flat section's deck is one constant height, so it can be tested for\n // coplanarity; a raked fallback surface has no single height to offer.\n const level = surface.flat ? surface.landingY : undefined;\n for (const ring of claimed.subtract(outline, level)) {\n extrudePrism(builder, ring, section.holes, topY, bottomY, colTop, S.tierWall, AO, maxErr, topN);\n }\n}\n\n/**\n * The ground already taken by sections drawn so far, so a later section can be\n * clipped to what is left. Only meaningful between COPLANAR surfaces, which is\n * why raked sections (each at its own height) never reach this.\n */\n/** Two claims fight for the same ground only if both are level and level TOGETHER.\n * A claim with no single height (an unresolved raked surface) is treated as\n * comparable with everything, which is what it did before heights existed. */\nfunction coplanarLevels(a: number | undefined, b: number | undefined): boolean {\n if (a === undefined || b === undefined) return true;\n return Math.abs(a - b) < 1e-3;\n}\n\nclass ClaimedArea {\n private rings: Array<[number, number][][]> = [];\n private boxes: RingBox[] = [];\n /** Constant deck height of each claim, or undefined when it is not level. */\n private levels: Array<number | undefined> = [];\n\n /**\n * `ring` minus everything claimed so far AT THE SAME HEIGHT; then claim it.\n *\n * `level` is the claim's constant deck height when it has one. Two decks only\n * z-fight when they are coplanar, so only a coplanar claim may take ground\n * away — an elevated box hanging over a ground-level section overlaps it in\n * plan and must still draw its whole floor, or the box loses the part of its\n * deck that shares a footprint with whatever is underneath it. Passing\n * undefined (a surface with no single height) keeps the original\n * clip-against-everything behaviour.\n */\n subtract(ring: Point[], level?: number): Point[][] {\n const closed: [number, number][] = ring.map((p) => [p.x, p.y]);\n if (closed.length < 3) return [];\n closed.push(closed[0]);\n const box = bboxOfRing(ring);\n // Same prune as `paddedFootprint`: a claim that cannot overlap cannot remove\n // anything, and without this the boolean work is quadratic in section count.\n const overlapping = this.rings.filter((_, i) => boxesOverlap(box, this.boxes[i])\n && coplanarLevels(level, this.levels[i]));\n let pieces: Array<[number, number][][]> = [[closed]];\n if (overlapping.length) {\n try {\n const diff = polygonClipping.difference([closed], ...overlapping);\n pieces = diff.map((poly) => poly.map((r) => r.map(([x, y]) => [x, y] as [number, number])));\n } catch {\n pieces = [[closed]]; // a degenerate outline must not drop the section\n }\n }\n this.rings.push([closed]);\n this.boxes.push(box);\n this.levels.push(level);\n const out: Point[][] = [];\n for (const poly of pieces) {\n if (!poly.length) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n if (pts.length > 1) {\n const f = pts[0], l = pts[pts.length - 1];\n if (Math.abs(f.x - l.x) < 1e-9 && Math.abs(f.y - l.y) < 1e-9) pts.pop();\n }\n if (pts.length >= 3) out.push(pts);\n }\n return out;\n }\n}\n\n/**\n * Height of a booth stand, world metres — a partition wall, not a table.\n *\n * Trade-show and exhibition charts are entirely booths (267 across the two\n * shipped templates), and before this they drew nothing at all: the whole venue\n * rendered as empty floor. A booth is a sellable unit the buyer picks, so it has\n * to be a solid the camera can approach and the pointer can hit.\n */\nconst BOOTH_HEIGHT_M = 2.4;\n\n/** How far a label floats above the thing it names, world metres. */\nconst ZONE_LABEL_LIFT_M = 6;\nconst SECTION_LABEL_LIFT_M = 2.2;\nconst ANNOTATION_LIFT_M = 0.1;\nconst BOOTH_LABEL_LIFT_M = 0.3;\n/** A GA area is flat, so its name sits at head height over the standing floor. */\nconst GA_LABEL_LIFT_M = 1.8;\n/** Row and seat labels ride just above their own deck, like painted markings. */\nconst ROW_LABEL_LIFT_M = 0.9;\nconst SEAT_LABEL_LIFT_M = 0.55;\n/**\n * Seat-label ceiling. Above this the whole set is not emitted — see the note at\n * the emit site. 6,000 covers theatres, halls, cinemas and mid-size arenas; the\n * charts above it are exactly the ones where a per-seat label is least readable.\n */\nconst SEAT_LABEL_MAX = 6_000;\n\n/** Height of a banquet table top, world metres (standard dining height). */\nconst TABLE_HEIGHT_M = 0.75;\n\n/**\n * Height of a décor-image floor plate — a marking on the ground, not an object.\n *\n * Matched to the GA slab (0.15 m) rather than made as thin as it \"should\" be: a\n * 4 cm plate under a 76 m rink is a depth ratio of 5e-4 and Uber Arena's ice\n * z-fought the ground slab into horizontal stripes across the whole surface.\n */\nconst DECOR_PLATE_M = 0.15;\n\n/** Resolve a booth to its closed chart-unit polygon, honouring a custom outline. */\nfunction boothPolygon(booth: Extract<ChartObject, { type: 'booth' }>): Point[] | null {\n // A custom outline wins and ignores rotation, exactly as the 2D renderer does\n // (see BoothObject.points) — L-shaped and island units on an expo floor.\n if (booth.points && booth.points.length >= 3) return booth.points;\n const { center, width, height, rotation } = booth;\n if (!width || !height) return null;\n const a = ((rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: center.x + lx * cos - ly * sin,\n y: center.y + lx * sin + ly * cos,\n }));\n}\n\nfunction buildBooth(\n builder: MeshBuilder,\n booth: Extract<ChartObject, { type: 'booth' }>,\n base: number,\n fill: RGB | null,\n S: typeof STRUCTURE,\n): void {\n const poly = boothPolygon(booth);\n if (!poly) return;\n extrudePrism(builder, poly, undefined, () => base + BOOTH_HEIGHT_M, base,\n tintTop(fill, S.boothTop), S.boothWall, AO);\n}\n\n/** Resolve a table to its closed chart-unit polygon. */\nfunction tablePolygon(table: Extract<ChartObject, { type: 'table' }>): Point[] | null {\n if (table.shape === 'round') {\n const r = table.radius;\n if (!r) return null;\n return ellipsePolygon(table.center.x, table.center.y, r, r, 24);\n }\n const { width, height, rotation, center } = table;\n if (!width || !height) return null;\n const a = ((rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: center.x + lx * cos - ly * sin,\n y: center.y + lx * sin + ly * cos,\n }));\n}\n\nfunction buildTable(\n builder: MeshBuilder,\n table: Extract<ChartObject, { type: 'table' }>,\n base: number,\n fill: RGB | null,\n S: typeof STRUCTURE,\n): void {\n const poly = tablePolygon(table);\n if (!poly) return;\n // Banquet and club charts (77 tables across the shipped templates) drew their\n // chairs floating around nothing. The table is what makes the arrangement read\n // as a table rather than a ring of stray seats.\n extrudePrism(builder, poly, undefined, () => base + TABLE_HEIGHT_M, base,\n tintTop(fill, S.tableTop), S.tableWall, AO);\n}\n\nfunction rotateShapePolygon(poly: Point[], degrees: number | undefined): Point[] {\n if (!degrees) return poly;\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (const point of poly) {\n minX = Math.min(minX, point.x); maxX = Math.max(maxX, point.x);\n minY = Math.min(minY, point.y); maxY = Math.max(maxY, point.y);\n }\n const cx = (minX + maxX) / 2, cy = (minY + maxY) / 2;\n const radians = (degrees * Math.PI) / 180;\n const cos = Math.cos(radians), sin = Math.sin(radians);\n return poly.map((point) => {\n const dx = point.x - cx, dy = point.y - cy;\n return { x: cx + dx * cos - dy * sin, y: cy + dx * sin + dy * cos };\n });\n}\n\n/** Resolve a shape object to its rotated closed chart-unit polygon. */\nfunction shapePolygon(shape: Extract<ChartObject, { type: 'shape' }>): Point[] | null {\n let poly: Point[] | null = null;\n if (shape.kind === 'polygon' && shape.points && shape.points.length >= 3) poly = shape.points;\n if (shape.kind === 'rect' && shape.width && shape.height) {\n poly = rectPolygon(shape.x ?? 0, shape.y ?? 0, shape.width, shape.height);\n }\n if (shape.kind === 'ellipse' && shape.width && shape.height) {\n const cx = (shape.x ?? 0) + shape.width / 2;\n const cy = (shape.y ?? 0) + shape.height / 2;\n poly = ellipsePolygon(cx, cy, shape.width / 2, shape.height / 2);\n }\n return poly ? rotateShapePolygon(poly, shape.rotation) : null; // line / polyline are stroke-only\n}\n\nexport type ShapeArchitectureKind = 'plate' | 'stage' | 'solid' | 'portal' | 'suite';\nexport interface ShapeArchitecture {\n kind: ShapeArchitectureKind;\n heightM: number;\n}\n\n/** Physical interpretation of the chart's existing décor vocabulary. */\nexport function architectureForShape(shape: Pick<ShapeObject, 'role' | 'heightM'>): ShapeArchitecture {\n const role = shape.role ?? '';\n const defaults: Record<string, ShapeArchitecture> = {\n stage: { kind: 'stage', heightM: 1 },\n entrance: { kind: 'portal', heightM: 2.7 },\n exit: { kind: 'portal', heightM: 2.7 },\n screen: { kind: 'solid', heightM: 6 },\n wall: { kind: 'solid', heightM: 2.8 },\n rail: { kind: 'solid', heightM: 1.1 },\n suite: { kind: 'suite', heightM: 3 },\n obstruction: { kind: 'solid', heightM: 4.5 },\n sound: { kind: 'solid', heightM: 1.45 },\n restroom: { kind: 'solid', heightM: 2.4 },\n bar: { kind: 'solid', heightM: 1.1 },\n concession: { kind: 'solid', heightM: 1.1 },\n coat: { kind: 'solid', heightM: 1.1 },\n };\n const resolved = defaults[role] ?? { kind: 'plate' as const, heightM: 0.25 };\n return { ...resolved, heightM: shape.heightM ?? resolved.heightM };\n}\n\ninterface ShapeLayerFootprint {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n topOffsetM: number;\n}\n\n/**\n * Flat authored shapes share one solid draw call, so polygonOffset cannot\n * separate a marking from the surface below it. Preserve the 2D object order by\n * lifting only shapes whose footprints overlap something already emitted.\n * Eight centimetres remains visually flush at venue scale while clearing the\n * depth precision that made Cricket Oval's wicket strip flicker into slivers.\n */\nconst SHAPE_LAYER_LIFT_M = 0.08;\n\nfunction claimShapeTopOffset(\n shape: Extract<ChartObject, { type: 'shape' }>,\n claimed: ShapeLayerFootprint[],\n): number {\n const poly = shapePolygon(shape);\n const architecture = architectureForShape(shape);\n const defaultTop = architecture.heightM;\n if (!poly) return defaultTop;\n // Vertical architecture occupies real space; it does not participate in the\n // tiny z-fight lifts used to order overlapping painted floor plates.\n if (architecture.kind !== 'plate' && architecture.kind !== 'stage') return defaultTop;\n\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const p of poly) {\n minX = Math.min(minX, p.x); minY = Math.min(minY, p.y);\n maxX = Math.max(maxX, p.x); maxY = Math.max(maxY, p.y);\n }\n let topOffsetM = defaultTop;\n for (const prior of claimed) {\n const overlaps = maxX > prior.minX && minX < prior.maxX\n && maxY > prior.minY && minY < prior.maxY;\n if (overlaps && topOffsetM <= prior.topOffsetM + 1e-6) {\n topOffsetM = prior.topOffsetM + SHAPE_LAYER_LIFT_M;\n }\n }\n claimed.push({ minX, minY, maxX, maxY, topOffsetM });\n return topOffsetM;\n}\n\nfunction lerpPoint(a: Point, b: Point, t: number): Point {\n return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };\n}\n\nfunction orientQuadLong(poly: Point[]): Point[] {\n if (poly.length !== 4) return poly;\n const edge0 = Math.hypot(poly[1].x - poly[0].x, poly[1].y - poly[0].y);\n const edge1 = Math.hypot(poly[2].x - poly[1].x, poly[2].y - poly[1].y);\n return edge0 >= edge1 ? poly : [poly[1], poly[2], poly[3], poly[0]];\n}\n\nfunction quadWidthSlice(poly: Point[], t0: number, t1: number): Point[] {\n return [\n lerpPoint(poly[0], poly[1], t0), lerpPoint(poly[0], poly[1], t1),\n lerpPoint(poly[3], poly[2], t1), lerpPoint(poly[3], poly[2], t0),\n ];\n}\n\nfunction quadDepthSlice(poly: Point[], t0: number, t1: number): Point[] {\n return [\n lerpPoint(poly[0], poly[3], t0), lerpPoint(poly[1], poly[2], t0),\n lerpPoint(poly[1], poly[2], t1), lerpPoint(poly[0], poly[3], t1),\n ];\n}\n\nfunction emitArchitecturalPrism(\n builder: MeshBuilder,\n poly: Point[],\n top: number,\n bottom: number,\n colTop: RGB,\n colWall: RGB,\n): void {\n extrudePrism(builder, poly, undefined, () => top, bottom, colTop, colWall, AO);\n}\n\nfunction buildShape(\n builder: MeshBuilder,\n shape: Extract<ChartObject, { type: 'shape' }>,\n base: number,\n topOffsetM: number,\n S: typeof STRUCTURE,\n focal: Point,\n /** Collects the footprint of each STAGE, for the lighting rig above it. */\n stages?: StageBounds[],\n): void {\n const poly = shapePolygon(shape);\n if (!poly) return;\n const isStage = shape.role === 'stage';\n const architecture = architectureForShape(shape);\n const height = base + topOffsetM;\n if (isStage && stages) {\n let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity;\n for (const pt of poly) {\n if (pt.x < minX) minX = pt.x;\n if (pt.x > maxX) maxX = pt.x;\n if (pt.y < minZ) minZ = pt.y;\n if (pt.y > maxZ) maxZ = pt.y;\n }\n // Chart units -> world metres, matching every other position in the scene.\n stages.push({\n cx: ((minX + maxX) / 2) * M,\n cz: ((minZ + maxZ) / 2) * M,\n y: height,\n halfX: Math.max(((maxX - minX) / 2) * M, 0.5),\n halfZ: Math.max(((maxZ - minZ) / 2) * M, 0.5),\n });\n }\n // An authored `fill` is what the 2D renderer paints this shape (see\n // `renderShape`), and 3D used to discard it for a fixed grey — so an organizer\n // who coloured their stage, dance floor or pitch saw it in the map and lost it\n // the moment they switched to 3D. Muted through `tintTop` like every other\n // authored colour, so it keeps its hue without out-shouting the seating.\n const colTop = tintTop(hexToRgb(shape.fill), isStage ? S.stageTop : S.decorTop);\n // A screen's useful surface is vertical, so it needs to read from its WALL\n // faces rather than its almost invisible top cap. Treat it like a powered LED\n // panel; every other structure remains inside the muted architectural palette.\n const colWall: RGB = isStage\n ? S.stageWall\n : shape.role === 'screen'\n ? [0.24, 0.46, 0.82]\n : tintTop(hexToRgb(shape.fill), S.decorWall);\n if (architecture.kind === 'portal' && poly.length === 4) {\n const quad = orientQuadLong(poly);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0, 0.16), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0.84, 1), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quad, height, Math.max(base, height - 0.35), colTop, colWall);\n return;\n }\n if (architecture.kind === 'suite' && poly.length === 4) {\n const edgeDistances = poly.map((point, index) => {\n const next = poly[(index + 1) % 4];\n const mx = (point.x + next.x) / 2, my = (point.y + next.y) / 2;\n return Math.hypot(mx - focal.x, my - focal.y);\n });\n const front = edgeDistances.indexOf(Math.min(...edgeDistances));\n const quad = [0, 1, 2, 3].map((offset) => poly[(front + offset) % 4]);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0, 0.08), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quadWidthSlice(quad, 0.92, 1), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quadDepthSlice(quad, 0.88, 1), height, base, colTop, colWall);\n emitArchitecturalPrism(builder, quad, height, Math.max(base, height - 0.18), colTop, colWall);\n return;\n }\n emitArchitecturalPrism(builder, poly, height, base, colTop, colWall);\n}\n\n/**\n * Resolve a décor image to its closed chart-unit polygon.\n *\n * `DecorImageObject` is authored as a top-left anchor plus size, rotated about\n * its own CENTRE — the same convention the 2D renderer uses when it offsets the\n * Konva node by half its extent.\n */\nfunction decorPolygon(decor: Extract<ChartObject, { type: 'decorImage' }>): Point[] | null {\n const { x, y, width, height } = decor;\n if (!width || !height) return null;\n const cx = x + width / 2, cy = y + height / 2;\n const a = ((decor.rotation ?? 0) * Math.PI) / 180;\n const cos = Math.cos(a), sin = Math.sin(a);\n const hw = width / 2, hh = height / 2;\n return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({\n x: cx + lx * cos - ly * sin,\n y: cy + lx * sin + ly * cos,\n }));\n}\n\n/**\n * The colour a décor plate is painted, sniffed from an SVG data URL.\n *\n * The renderer is deliberately texture-free (three draw calls, no atlas), so a\n * décor image cannot be drawn as an image. What it CAN do is paint the plate in\n * the graphic's own dominant colour, which is the difference between an ice rink\n * reading as ice and reading as a grey slab.\n *\n * The heuristic is narrow on purpose: take the paint of the FIRST covering\n * element (`<rect>` or `<path>`), because an SVG floor graphic is built up\n * background-first — the rink surface, then the lines on top of it. A gradient\n * reference resolves to the mean of that gradient's stops. Anything else (a\n * raster data URL, an external href, an SVG that does not match) returns null\n * and the caller falls back to the neutral décor tone, which is what the object\n * rendered as before this existed.\n */\nfunction decorPlatePaint(href: string | undefined): RGB | null {\n if (!href || !href.startsWith('data:image/svg+xml')) return null;\n let svg = href.slice(href.indexOf(',') + 1);\n if (/;base64/i.test(href.slice(0, href.indexOf(',')))) {\n try { svg = atob(svg); } catch { return null; }\n } else {\n try { svg = decodeURIComponent(svg); } catch { /* already literal */ }\n }\n\n const covering = svg.match(/<(?:rect|path)\\b[^>]*\\bfill=\"([^\"]+)\"/i);\n if (!covering) return null;\n const paint = covering[1].trim();\n if (paint === 'none' || paint === 'transparent') return null;\n\n const gradient = paint.match(/^url\\(#([^)]+)\\)$/);\n if (!gradient) return hexToRgb(paint);\n\n // Average the referenced gradient's stops — a two-stop ice gradient is one\n // colour as far as a flat-shaded plate is concerned.\n const def = svg.match(new RegExp(`<(?:linear|radial)Gradient\\\\b[^>]*\\\\bid=\"${gradient[1]}\"[^>]*>([\\\\s\\\\S]*?)</(?:linear|radial)Gradient>`, 'i'));\n if (!def) return null;\n const stops: RGB[] = [];\n for (const m of def[1].matchAll(/stop-color=\"([^\"]+)\"/gi)) {\n const c = hexToRgb(m[1].trim());\n if (c) stops.push(c);\n }\n if (!stops.length) return null;\n return stops.reduce<RGB>((acc, c, i) => mix(acc, c, 1 / (i + 1)), stops[0]);\n}\n\nfunction buildDecorImage(\n builder: MeshBuilder,\n decor: Extract<ChartObject, { type: 'decorImage' }>,\n base: number,\n S: typeof STRUCTURE,\n): void {\n // A `foreground` décor image draws ABOVE the seats in 2D — a canopy or an\n // overlay graphic. In 3D an opaque plate over the seating would hide the very\n // thing the buyer came to pick from, so it is left undrawn rather than drawn\n // wrongly. No shipped template authors one; the designer can.\n if (decor.layer === 'foreground') return;\n const poly = decorPolygon(decor);\n if (!poly) return;\n // The plate is a floor marking, not an object: it sits a few centimetres proud\n // of the ground slab so it cannot z-fight, and well under the 0.25 m décor\n // shapes that may be standing on it.\n const top = base + DECOR_PLATE_M;\n // Muted into the structure palette exactly like a category fill on a tier cap.\n // Taken literally, the rink's #f4f9ff ice is the BRIGHTEST thing in the venue\n // and the floor out-shouts the seating — against the look brief, which puts\n // saturated colour only on seats. Through `tintTop` it keeps its blue cast and\n // sits in the same tonal range as every other surface.\n let paint = tintTop(decorPlatePaint(decor.href), S.decorTop);\n // No alpha in the solid pass, so authored opacity is honoured by mixing the\n // plate toward the ground it would otherwise be showing through to.\n const opacity = Math.max(0, Math.min(1, decor.opacity ?? 1));\n if (opacity < 1) paint = mix(S.ground, paint, opacity);\n extrudePrism(builder, poly, undefined, () => top, base, paint, mix(paint, S.decorWall, 0.5), AO);\n}\n\nfunction buildGa(builder: MeshBuilder, ga: Extract<ChartObject, { type: 'gaArea' }>, base: number, fill: RGB | null, S: typeof STRUCTURE): void {\n if (!ga.points || ga.points.length < 3) return;\n const colTop = tintTop(fill, S.gaTop);\n extrudePrism(builder, ga.points, ga.holes, () => base + 0.15, base, colTop, S.gaWall, AO);\n}\n\n/** Compute the horizontal chart-unit footprint over everything drawable. */\nfunction chartFootprint(units: FloorUnit[], seats: ExpandedSeat[]): { minX: number; minY: number; maxX: number; maxY: number } {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n const acc = (x: number, y: number): void => {\n if (x < minX) minX = x; if (y < minY) minY = y;\n if (x > maxX) maxX = x; if (y > maxY) maxY = y;\n };\n for (const s of seats) acc(s.x, s.y);\n for (const u of units) {\n for (const o of u.objects) {\n if (o.type === 'section') for (const p of o.outline) acc(p.x, p.y);\n else if (o.type === 'shape' && o.points) for (const p of o.points) acc(p.x, p.y);\n else if (o.type === 'gaArea') for (const p of o.points) acc(p.x, p.y);\n else if (o.type === 'booth') { const b = boothPolygon(o); if (b) for (const p of b) acc(p.x, p.y); }\n else if (o.type === 'table') { const t = tablePolygon(o); if (t) for (const p of t) acc(p.x, p.y); }\n else if (o.type === 'decorImage') { const d = decorPolygon(o); if (d) for (const p of d) acc(p.x, p.y); }\n }\n }\n if (!Number.isFinite(minX)) { minX = -100; minY = -100; maxX = 100; maxY = 100; }\n return { minX, minY, maxX, maxY };\n}\n\nexport interface SceneModelInput {\n doc: ChartDoc;\n seats: ExpandedSeat[];\n /** Overrides the chart's active layout without mutating its authored data. */\n eventConfigurationId?: string;\n /** Optional initial per-seat state (default all available). */\n initialState?: (seat: ExpandedSeat) => import('../palette').SeatState3D;\n}\n\nexport function buildSceneModel(input: SceneModelInput): SceneModel {\n const { doc, seats } = input;\n // Resolved once and threaded down, so no builder reaches for the module-level\n // palette and quietly ignores an organizer's branding.\n const theme = resolveTheme3D(doc.theme);\n const S = theme.structure;\n const units = floorUnits(doc, input.eventConfigurationId ?? doc.eventConfigurationId);\n const builder = new MeshBuilder();\n\n // Ground slab sized to the footprint (+ margin), sitting at datum 0.\n const fp = chartFootprint(units, seats);\n const padU = Math.max(60, (fp.maxX - fp.minX + fp.maxY - fp.minY) * 0.06);\n const groundPoly = rectPolygon(fp.minX - padU, fp.minY - padU, (fp.maxX - fp.minX) + padU * 2, (fp.maxY - fp.minY) + padU * 2);\n extrudePrism(builder, groundPoly, undefined, () => 0, -0.4, S.ground, S.ground, AO);\n\n // Per-section 2D fill colours (member-category mix), carried onto tier tops.\n const sectionFills = resolveSectionFills(doc, seats);\n const catColor = new Map<string, string>();\n for (const c of doc.categories ?? []) catColor.set(c.key, c.color);\n const stageBounds: StageBounds[] = [];\n\n // ONE seating surface per section, resolved from the same model layout.ts uses\n // for eye heights. Consumed below by the tier caps and by the seat instances.\n const surfaces = buildVenueSurfaces(units, seats);\n /** Owning floor per seat, for per-instance floor isolation. */\n const seatFloor = new Float32Array(seats.length);\n\n // Coplanar sections must not overlap (see ClaimedArea). One claim per floor:\n // sections on different floors sit at different heights and cannot z-fight.\n for (let unitIndex = 0; unitIndex < units.length; unitIndex++) {\n const unit = units[unitIndex];\n builder.setFloor(unitIndex);\n const claimed = new ClaimedArea();\n const claimedShapeLayers: ShapeLayerFootprint[] = [];\n const siblings = unit.objects.filter((o): o is SectionObject => o.type === 'section' && !!o.outline && o.outline.length >= 3);\n for (const o of unit.objects) {\n if (o.type === 'section') buildTier(builder, o, unit, sectionFill(o, sectionFills), surfaces.bySection.get(o.id), claimed, siblings, S);\n else if (o.type === 'shape') buildShape(\n builder,\n o,\n unit.baseHeightM,\n claimShapeTopOffset(o, claimedShapeLayers),\n S,\n unit.focal,\n stageBounds,\n );\n else if (o.type === 'gaArea') buildGa(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'booth') buildBooth(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'table') buildTable(builder, o, unit.baseHeightM, hexToRgb(catColor.get(o.categoryKey)), S);\n else if (o.type === 'decorImage') buildDecorImage(builder, o, unit.baseHeightM, S);\n }\n }\n\n const focal = doc.focalPoint ?? { x: (fp.minX + fp.maxX) / 2, y: (fp.minY + fp.maxY) / 2 };\n\n // NOTE — per-row stepped treads (./rowSteps.ts) are DISABLED pending the\n // unified-surface rework. See docs/handover-2026-07-25-3d-surface-rework.md.\n //\n // They layered a second surface on top of the section prism, and the two could\n // not be kept in agreement across chart shapes: treads became large flat plates\n // that swallowed the stepping, sliver triangles appeared where seat ordering\n // failed, and seat dots were sliced by their own tread. The fix is one surface\n // function per section consumed by cap + steps + dots, not a second layer.\n // The module is kept (with its measurements in the doc comments) as input to\n // that rework; re-enabling it as-is is not the intended path.\n\n const solids = mergeMeshData([builder.build()]);\n const seatData: SeatInstanceData = buildSeatInstances(seats, input.initialState, surfaces, seatFloor, catColor);\n\n // --- Zones -----------------------------------------------------------------\n // Resolved from the SEATS, not the section outlines: a zone's meaning to a\n // buyer is the seats in it, and framing on the outlines would include the\n // aisles and margins a section is drawn with.\n const zoneDefs = doc.zones ?? [];\n const zones: SceneZone[] = [];\n if (zoneDefs.length) {\n const sectionZone = new Map<string, string>();\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'section' && o.zone) sectionZone.set(o.id, o.zone);\n }\n }\n interface Acc { n: number; minX: number; minY: number; maxX: number; maxY: number; sumY: number }\n const acc = new Map<string, Acc>();\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n // Prefer the seat's own resolved zone; fall back to its owning section's.\n const owner = surfaces.seatOwner[i];\n const zid = s.zoneId ?? (owner ? sectionZone.get(owner) : undefined);\n if (!zid) continue;\n let a = acc.get(zid);\n if (!a) { a = { n: 0, minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity, sumY: 0 }; acc.set(zid, a); }\n a.n++;\n if (s.x < a.minX) a.minX = s.x;\n if (s.y < a.minY) a.minY = s.y;\n if (s.x > a.maxX) a.maxX = s.x;\n if (s.y > a.maxY) a.maxY = s.y;\n a.sumY += surfaces.seatDeckY(i);\n }\n for (const z of zoneDefs) {\n const a = acc.get(z.id);\n const sectionIds: string[] = [];\n for (const [secId, zid] of sectionZone) if (zid === z.id) sectionIds.push(secId);\n if (!a || a.n === 0) {\n // A zone with no seats still exists (an empty hall, a zone of GA areas);\n // report it rather than dropping it, so navigation can show it as empty.\n zones.push({\n id: z.id, label: z.label, color: hexToRgb(z.color), sectionIds, seatCount: 0,\n center: [0, 0, 0], radius: 0,\n focalWorld: [(z.focalPoint ?? focal).x * M, 1.5, (z.focalPoint ?? focal).y * M],\n });\n continue;\n }\n zones.push({\n id: z.id,\n label: z.label,\n color: hexToRgb(z.color),\n sectionIds,\n seatCount: a.n,\n center: [((a.minX + a.maxX) / 2) * M, a.sumY / a.n, ((a.minY + a.maxY) / 2) * M],\n radius: 0.5 * Math.hypot((a.maxX - a.minX) * M, (a.maxY - a.minY) * M) || 1,\n // A zone may face its own point (`ZoneDef.focalPoint`); the documented\n // fallback is the floor/chart focal.\n focalWorld: [(z.focalPoint ?? focal).x * M, 1.5, (z.focalPoint ?? focal).y * M],\n });\n }\n }\n\n // --- Labels ----------------------------------------------------------------\n const labels: SceneLabel[] = [];\n const sections: SceneSection[] = [];\n const rows: SceneRow[] = [];\n for (const z of zones) {\n if (z.seatCount === 0) continue;\n // Zone labels float above the seating they name, so they read as belonging\n // to the whole block rather than to whichever seat is under them.\n labels.push({\n id: `zone:${z.id}`,\n kind: 'zone',\n text: z.label,\n anchor: [z.center[0], z.center[1] + ZONE_LABEL_LIFT_M, z.center[2]],\n color: doc.zones?.find((d) => d.id === z.id)?.color,\n });\n }\n {\n // A section is named over its own SEATS, at their mean deck height. Its\n // outline centroid would drift into the aisles a concave section wraps, and\n // on a raked tier the label would sit at the wrong height entirely.\n const acc = new Map<string, { n: number; x: number; y: number; deck: number }>();\n // Seat extent per section, for the camera fit `focusSection` needs.\n const ext = new Map<string, { minX: number; maxX: number; minY: number; maxY: number }>();\n for (let i = 0; i < seats.length; i++) {\n const owner = surfaces.seatOwner[i];\n if (!owner) continue;\n let a = acc.get(owner);\n if (!a) { a = { n: 0, x: 0, y: 0, deck: 0 }; acc.set(owner, a); }\n a.n++; a.x += seats[i].x; a.y += seats[i].y; a.deck += surfaces.seatDeckY(i);\n let e = ext.get(owner);\n if (!e) { e = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity }; ext.set(owner, e); }\n if (seats[i].x < e.minX) e.minX = seats[i].x;\n if (seats[i].x > e.maxX) e.maxX = seats[i].x;\n if (seats[i].y < e.minY) e.minY = seats[i].y;\n if (seats[i].y > e.maxY) e.maxY = seats[i].y;\n }\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type !== 'section') continue;\n const a = acc.get(o.id);\n if (!a || a.n === 0) continue;\n const e = ext.get(o.id)!;\n const centre: [number, number, number] = [(a.x / a.n) * M, a.deck / a.n, (a.y / a.n) * M];\n sections.push({\n id: o.id,\n label: o.displayLabel || o.label || o.id,\n seatCount: a.n,\n center: centre,\n // Half-diagonal of the seat extent — the same fit the zones use.\n radius: Math.max(1, Math.hypot(e.maxX - e.minX, e.maxY - e.minY) * 0.5 * M),\n // A section faces its floor's focal, which is what the camera should\n // look along so the seats present their fronts.\n focalWorld: [unit.focal.x * M, unit.baseHeightM + 1.5, unit.focal.y * M],\n });\n labels.push({\n id: `section:${o.id}`,\n kind: 'section',\n // The buyer-facing name wins over the technical one, as it does in 2D.\n text: o.displayLabel || o.label || o.id,\n anchor: [centre[0], centre[1] + SECTION_LABEL_LIFT_M, centre[2]],\n });\n }\n }\n }\n // --- GA areas, rows and seats ----------------------------------------------\n {\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type !== 'gaArea') continue;\n const c = centroidOf(o.points);\n if (!c) continue;\n // A GA area's capacity IS its buyer-facing fact — \"Standing Floor\" alone\n // does not say whether 40 or 4,000 people fit in it, and 2D shows the\n // number. The label carries both, as one line.\n const name = o.displayLabel || o.label || o.id;\n labels.push({\n id: `ga:${o.id}`,\n kind: 'ga',\n text: o.capacity > 0 ? `${name} · ${o.capacity.toLocaleString()}` : name,\n anchor: [c.x * M, unit.baseHeightM + GA_LABEL_LIFT_M, c.y * M],\n });\n }\n }\n\n // Rows are labelled at the END of the row, where a real venue paints them —\n // on the aisle, not over the middle of the seating. Anchored to the outermost\n // seat so the label tracks the row's own rake instead of a flat guess.\n const rowEnds = new Map<string, { seat: ExpandedSeat; index: number; far: number }>();\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n const far = Math.hypot(s.x - focal.x, s.y - focal.y);\n const cur = rowEnds.get(s.rowId);\n // \"End\" = furthest along the row from its own midpoint; approximated by\n // distance from the venue focal, which for a row is one of its two ends.\n if (!cur || far > cur.far) rowEnds.set(s.rowId, { seat: s, index: i, far });\n }\n for (const [rowId, end] of rowEnds) {\n // A seat's public label is `${rowLabel}-${seatNumber}`, so the row's name\n // is everything before the LAST separator — a row legitimately called\n // \"A-1\" in a segmented plan must not be truncated to \"A\".\n const cut = end.seat.label ? end.seat.label.lastIndexOf('-') : -1;\n const text = cut > 0 ? end.seat.label.slice(0, cut) : rowId;\n if (!text) continue;\n labels.push({\n id: `row:${rowId}`,\n kind: 'row',\n text,\n anchor: [end.seat.x * M, surfaces.seatDeckY(end.index) + ROW_LABEL_LIFT_M, end.seat.y * M],\n });\n }\n\n const rowAcc = new Map<string, {\n n: number; x: number; y: number; z: number;\n minX: number; maxX: number; minZ: number; maxZ: number;\n label: string; sectionId?: string;\n }>();\n for (let i = 0; i < seats.length; i++) {\n const seat = seats[i];\n const cut = seat.label ? seat.label.lastIndexOf('-') : -1;\n let acc = rowAcc.get(seat.rowId);\n if (!acc) {\n acc = {\n n: 0, x: 0, y: 0, z: 0,\n minX: Infinity, maxX: -Infinity, minZ: Infinity, maxZ: -Infinity,\n label: cut > 0 ? seat.label.slice(0, cut) : seat.rowId,\n sectionId: seat.sectionId,\n };\n rowAcc.set(seat.rowId, acc);\n }\n const offset = i * 3;\n const x = seatData.iPosition[offset];\n const y = seatData.iPosition[offset + 1];\n const z = seatData.iPosition[offset + 2];\n acc.n++; acc.x += x; acc.y += y; acc.z += z;\n acc.minX = Math.min(acc.minX, x); acc.maxX = Math.max(acc.maxX, x);\n acc.minZ = Math.min(acc.minZ, z); acc.maxZ = Math.max(acc.maxZ, z);\n }\n for (const [id, acc] of rowAcc) {\n rows.push({\n id,\n label: acc.label,\n sectionId: acc.sectionId,\n seatCount: acc.n,\n center: [acc.x / acc.n, acc.y / acc.n, acc.z / acc.n],\n radius: Math.max(0.8, Math.hypot(acc.maxX - acc.minX, acc.maxZ - acc.minZ) * 0.5),\n });\n }\n\n // Seat labels are the densest rung by far, and unlike every other kind their\n // count is the seat count. On a 52,000-seat ground that is 52,000 DOM-backed\n // labels to hold and project, for text that is only legible when a handful\n // are on screen. So they are emitted for charts where the whole set stays\n // cheap, and above that a seat identifies itself the way it already does on\n // every surface: by being tapped, which raises the seat's own card.\n if (seats.length <= SEAT_LABEL_MAX) {\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n const text = s.displayLabel || s.label;\n if (!text) continue;\n labels.push({\n id: `seat:${s.id}`,\n kind: 'seat',\n text,\n anchor: [s.x * M, surfaces.seatDeckY(i) + SEAT_LABEL_LIFT_M, s.y * M],\n });\n }\n }\n }\n\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'shape' && o.role && ['entrance', 'exit', 'screen', 'suite', 'obstruction'].includes(o.role)) {\n const poly = shapePolygon(o);\n const centre = poly ? centroidOf(poly) : null;\n if (!centre || !o.label) continue;\n labels.push({\n id: `architecture:${o.id}`,\n kind: 'annotation',\n text: o.label,\n anchor: [centre.x * M, unit.baseHeightM + architectureForShape(o).heightM + ANNOTATION_LIFT_M, centre.y * M],\n color: o.fill,\n });\n } else if (o.type === 'text') {\n // Authored wayfinding sits just above the floor it annotates — a door or\n // aisle name belongs to the ground, not to the air above it.\n if (!o.text) continue;\n labels.push({\n id: `text:${o.id}`,\n kind: 'annotation',\n text: o.text,\n anchor: [o.position.x * M, unit.baseHeightM + ANNOTATION_LIFT_M, o.position.y * M],\n color: o.color,\n rotation: o.rotation,\n });\n } else if (o.type === 'booth') {\n // A booth is a sellable unit the buyer picks by NAME, so it is labelled\n // on top of its own stand rather than left as an anonymous box.\n const poly = boothPolygon(o);\n if (!poly) continue;\n const c = centroidOf(poly);\n if (!c) continue;\n labels.push({\n id: `booth:${o.id}`,\n kind: 'booth',\n text: o.displayLabel || o.label || o.id,\n anchor: [c.x * M, unit.baseHeightM + BOOTH_HEIGHT_M + BOOTH_LABEL_LIFT_M, c.y * M],\n });\n }\n }\n }\n\n // --- Floors ----------------------------------------------------------------\n const floors: SceneFloor[] = [];\n {\n const sectionFloor = new Map<string, number>();\n for (let ui = 0; ui < units.length; ui++) {\n for (const o of units[ui].objects) if (o.type === 'section') sectionFloor.set(o.id, ui);\n }\n const acc = units.map(() => ({ n: 0, minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity, sumY: 0 }));\n for (let i = 0; i < seats.length; i++) {\n const owner = surfaces.seatOwner[i];\n const ui = owner !== null ? sectionFloor.get(owner) : undefined;\n if (ui === undefined) continue;\n const a = acc[ui];\n const s = seats[i];\n a.n++;\n if (s.x < a.minX) a.minX = s.x;\n if (s.y < a.minY) a.minY = s.y;\n if (s.x > a.maxX) a.maxX = s.x;\n if (s.y > a.maxY) a.maxY = s.y;\n a.sumY += surfaces.seatDeckY(i);\n seatFloor[i] = ui;\n }\n for (let ui = 0; ui < units.length; ui++) {\n const a = acc[ui];\n const f = doc.floors?.[ui];\n floors.push({\n index: ui,\n id: f?.id ?? `floor-${ui}`,\n label: f?.name ?? (units.length > 1 ? `Floor ${ui + 1}` : 'Venue'),\n seatCount: a.n,\n center: a.n ? [((a.minX + a.maxX) / 2) * M, a.sumY / a.n, ((a.minY + a.maxY) / 2) * M] : [0, 0, 0],\n radius: a.n ? (0.5 * Math.hypot((a.maxX - a.minX) * M, (a.maxY - a.minY) * M) || 1) : 0,\n });\n }\n }\n\n // --- Seat facing -----------------------------------------------------------\n // Resolved here rather than in `buildSeatInstances` because it needs the thing\n // only this layer knows: which floor a seat belongs to, and therefore what it\n // looks at. Runs after the floors block above, which is what fills `seatFloor`.\n seatData.iYaw = computeSeatYaw(\n seatData.iPosition,\n seats.length,\n (i) => seats[i].rowId,\n (i) => {\n const f = units[seatFloor[i]]?.focal ?? focal;\n return [f.x * M, f.y * M];\n },\n );\n\n const cx = ((fp.minX + fp.maxX) / 2) * M;\n const cz = ((fp.minY + fp.maxY) / 2) * M;\n const radius = 0.5 * Math.hypot((fp.maxX - fp.minX) * M, (fp.maxY - fp.minY) * M) || 10;\n\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 stages: stageBounds,\n sections,\n rows,\n labels,\n floors,\n };\n}\n","/**\n * A section's RAKE FIELD — the scalar \"depth\" that its seating rises along.\n *\n * ## The defect this exists to fix\n *\n * Both the eye-height model (`layout.ts`) and the 3D seating surface\n * (`view3d/scene/surface.ts`) used to raise a seat by its radial distance from\n * ONE venue focal point. That is only correct when a section's rows are\n * concentric arcs about that focal.\n *\n * Measured on the amphitheatre gallery, whose rows are straight blocks: a single\n * row's seats span a radial-distance range of 105 chart units on the\n * centre-facing blocks and 240 on the side blocks. Since height is a function of\n * that distance, one row's seats landed at DIFFERENT heights — a within-row deck\n * spread of 2.30 m on the side blocks against 1.05 m on the centre-facing ones.\n * On screen the rows visibly tilt, and the tilt differs block to block, which is\n * exactly the left/right asymmetry the owner reported.\n *\n * ## Why a per-section AXIS is not enough\n *\n * The first attempt fitted one straight rake axis per section by PCA over its row\n * centroids, and scored it against the radial model. It did not fix the\n * amphitheatre, because `sec-gall` is a SINGLE section containing six wedge\n * blocks at six different orientations. No single axis — straight or radial —\n * fits them all, so the fit correctly fell back to radial and the tilt survived.\n * Sections containing several blocks are normal, not exotic.\n *\n * ## The model that does work\n *\n * Depth is defined by the section's OWN ROWS. Each row gets one depth value (the\n * mean distance of its seats from the focal), and the field at an arbitrary point\n * is an inverse-distance blend of the nearest rows' depths.\n *\n * The properties that matter:\n *\n * - **Exact on a row.** A point on a row has zero distance to it, so it takes\n * that row's depth exactly and every seat in the row lands at ONE height.\n * The tilt cannot come back, whatever the row's orientation.\n * - **Orientation-free.** Straight blocks, arcs, fans, in-the-round and blocks\n * at six different angles in one section all work, because nothing is fitted\n * to a direction.\n * - **Continuous.** It is a field over the plane, not a per-seat lookup. The 3D\n * cap is tessellated at points BETWEEN seats and needs a value and a gradient\n * there; a per-row lookup would leave the deck undefined between rows and put\n * the seats back off the surface they stand on.\n *\n * That last point is why quantising per row inside `assignEyeHeights` was tried\n * and removed: the renderer cannot follow a step function it has no definition\n * for, and the two models diverging is the trap that made every offset constant\n * chart-specific.\n */\n\nimport type { Point } from './types';\n\n/** A resolved row: its seats as an ordered polyline, plus its depth ordinate. */\nexport interface RakeRowFit {\n /** The row's seats, ordered along the row. */\n readonly pts: readonly Point[];\n /** Mean distance of the row's seats from the focal — what rise is a function of. */\n readonly depth: number;\n}\n\nexport interface SectionRake {\n readonly kind: 'radial' | 'rows';\n /** Depth ordinate at a chart-unit point; rise is a function of this. */\n depthAt(x: number, y: number): number;\n /** Unit gradient direction of `depthAt` — turns a rake angle into a normal. */\n gradientAt(x: number, y: number): readonly [number, number];\n /** Number of rows the field was built from (0 for the radial fallback). */\n readonly rowCount: number;\n /**\n * The section's rows, ORDERED BY DEPTH (front first).\n *\n * The deck is built directly from these — one level ribbon per row — rather\n * than by tessellating `depthAt`. See `deckBands.ts` for why: a section holding\n * several blocks at different heights has a genuine cliff between them, and no\n * continuous field can be both level on every row and trackable by refinement.\n */\n readonly rows: readonly RakeRowFit[];\n /** Index into `rows` of the row nearest a point, or -1 when there are none. */\n nearestRow(x: number, y: number): number;\n}\n\n/** One row's member points, in any order. */\nexport interface RakeRow {\n points: Point[];\n}\n\n/** How many nearest rows contribute to a blended sample. */\nconst BLEND_ROWS = 3;\n\n/**\n * A row further than this multiple of the nearest row's distance contributes\n * nothing. Just above 2 so the two rows bracketing a point midway between them\n * both count (their distances differ by at most the row pitch), while a row on\n * the far side of an aisle or beyond the last row does not.\n */\nconst BLEND_DISTANCE_RATIO = 2.2;\n\n/** Step used for the finite-difference gradient, chart units. */\nconst GRAD_STEP = 0.5;\n\nfunction radialRake(focal: Point): SectionRake {\n return {\n kind: 'radial',\n rowCount: 0,\n rows: [],\n nearestRow: () => -1,\n depthAt: (x, y) => Math.hypot(x - focal.x, y - focal.y),\n gradientAt: (x, y) => {\n const dx = x - focal.x, dy = y - focal.y;\n const d = Math.hypot(dx, dy);\n // At the focal the gradient is undefined; the surface is flat there anyway.\n return d < 1e-9 ? [0, 0] : [dx / d, dy / d];\n },\n };\n}\n\ninterface RowFit {\n /**\n * The row as an ordered POLYLINE through its own seats — not a straight chord.\n *\n * A chord was tried first and broke the arena: its upper-bowl rows are strong\n * arcs, so a seat at the end of a row sits far from the chord between the row's\n * extremes. The blend below then treated that seat as \"between rows\" and gave\n * it a neighbouring row's depth, which put the within-row spread UP from 0.07 m\n * to 2.83 m. Distance must be measured to the row's real shape, so that every\n * one of its seats reads as being exactly on it.\n */\n pts: Point[];\n /** Row centroid, and the radius of a circle about it containing the row. */\n cx: number;\n cy: number;\n radius: number;\n /** The row's depth ordinate — mean distance of its seats from the focal. */\n depth: number;\n}\n\n/**\n * Reduce a row to a segment plus a depth.\n *\n * The direction comes from the row's own extent (the two furthest-apart seats),\n * not from a fitted line: it is exact for a straight row, good enough for a\n * gently curved one, and cannot be thrown off by an outlier the way a\n * least-squares fit through few points can.\n */\nfunction fitRow(points: Point[], focal: Point): RowFit | null {\n if (!points.length) return null;\n let cx = 0, cy = 0, depth = 0;\n for (const p of points) {\n cx += p.x; cy += p.y;\n depth += Math.hypot(p.x - focal.x, p.y - focal.y);\n }\n const n = points.length;\n cx /= n; cy /= n; depth /= n;\n\n // Order the seats along the row so consecutive pairs are real segments. The\n // chord direction is only used for this ORDERING, never for distance, so a\n // curved row orders correctly even though its chord is a poor fit to it.\n let ax = points[0], far = -1;\n for (const p of points) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > far) { far = d; ax = p; }\n }\n let dx = ax.x - cx, dy = ax.y - cy;\n const len = Math.hypot(dx, dy);\n if (len > 1e-9) { dx /= len; dy /= len; } else { dx = 1; dy = 0; }\n const pts = [...points].sort((p, q) =>\n ((p.x - cx) * dx + (p.y - cy) * dy) - ((q.x - cx) * dx + (q.y - cy) * dy));\n\n let radius = 0;\n for (const p of pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > radius) radius = d;\n }\n return { pts, cx, cy, radius, depth };\n}\n\n/** Distance from a point to a row's polyline. */\nfunction distToRow(r: RowFit, x: number, y: number): number {\n const pts = r.pts;\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** How many rows survive the cheap bounding-circle prefilter. */\nconst CANDIDATE_ROWS = 8;\n\n/**\n * Inverse-distance blend of the nearest rows' depths.\n *\n * Only the nearest few rows contribute, so a distant block on the far side of the\n * venue cannot drag a section's near rows. Squared inverse distance makes the\n * nearest row dominate quickly, which keeps the field flat ALONG a row and\n * varying across it — the shape a rake actually has.\n */\nfunction sampleRows(rows: RowFit[], x: number, y: number): number {\n // Prefilter on each row's bounding circle. `centroidDist - radius` is a true\n // LOWER bound on the polyline distance, so this cannot discard a row that would\n // have won — it just keeps the exact polyline test off ~90 % of the rows, which\n // is what makes the field affordable per cap vertex.\n const candD = new Array<number>(CANDIDATE_ROWS).fill(Infinity);\n const candI = new Array<number>(CANDIDATE_ROWS).fill(-1);\n for (let i = 0; i < rows.length; i++) {\n const r = rows[i];\n const lower = Math.hypot(x - r.cx, y - r.cy) - r.radius;\n for (let k = 0; k < CANDIDATE_ROWS; k++) {\n if (lower < candD[k]) {\n for (let j = CANDIDATE_ROWS - 1; j > k; j--) { candD[j] = candD[j - 1]; candI[j] = candI[j - 1]; }\n candD[k] = lower; candI[k] = i;\n break;\n }\n }\n }\n\n // Nearest BLEND_ROWS by insertion (the list is short and this avoids a sort).\n const bestD = new Array<number>(BLEND_ROWS).fill(Infinity);\n const bestI = new Array<number>(BLEND_ROWS).fill(-1);\n for (const i of candI) {\n if (i < 0) continue;\n const d = distToRow(rows[i], x, y);\n for (let k = 0; k < BLEND_ROWS; k++) {\n if (d < bestD[k]) {\n for (let j = BLEND_ROWS - 1; j > k; j--) { bestD[j] = bestD[j - 1]; bestI[j] = bestI[j - 1]; }\n bestD[k] = d; bestI[k] = i;\n break;\n }\n }\n }\n if (bestI[0] < 0) return 0;\n // Exactly on a row: take its depth, so a seat is never blended off its own row.\n if (bestD[0] < 1e-6) return rows[bestI[0]].depth;\n\n // Blend only across rows at COMPARABLE distance. Absolute inverse-distance\n // weighting was tried and produced the cap's worst error: in the empty parts of\n // a section outline — beyond the last row, or the gap between two blocks — the\n // two nearest rows can both be hundreds of units away, so their weights stay\n // comparable and the field goes on interpolating between them across the void.\n // Measured, 79 of the gallery's 97 out-of-tolerance sample points sat more than\n // 100 units from ANY seat, and a cap triangle spanning that region missed the\n // surface by up to 10.84 m.\n //\n // A relative cutoff makes the far field settle to the nearest row's depth, i.e.\n // locally CONSTANT, which a flat triangle interpolates exactly. Inside the\n // seating the nearest rows are all within the cutoff, so blending is unchanged\n // and rows stay level.\n const cutoff = bestD[0] * BLEND_DISTANCE_RATIO;\n let num = 0, den = 0;\n for (let k = 0; k < BLEND_ROWS; k++) {\n const i = bestI[k];\n if (i < 0 || bestD[k] > cutoff) continue;\n const w = 1 / (bestD[k] * bestD[k]);\n num += w * rows[i].depth;\n den += w;\n }\n return den > 0 ? num / den : rows[bestI[0]].depth;\n}\n\nfunction rowsRake(rows: RowFit[]): SectionRake {\n const depthAt = (x: number, y: number): number => sampleRows(rows, x, y);\n const nearestRow = (x: number, y: number): number => {\n let best = Infinity, bestI = -1;\n for (let i = 0; i < rows.length; i++) {\n // Bounding-circle lower bound first; only test the polyline if it can win.\n const r = rows[i];\n if (Math.hypot(x - r.cx, y - r.cy) - r.radius >= best) continue;\n const d = distToRow(r, x, y);\n if (d < best) { best = d; bestI = i; }\n }\n return bestI;\n };\n return {\n kind: 'rows',\n rowCount: rows.length,\n rows,\n nearestRow,\n depthAt,\n gradientAt: (x, y) => {\n // Central differences: the blend has no closed form, and shading only needs\n // the direction. A half-unit step is far below row pitch, so this tracks\n // the field rather than smoothing across rows.\n const gx = (depthAt(x + GRAD_STEP, y) - depthAt(x - GRAD_STEP, y)) / (2 * GRAD_STEP);\n const gy = (depthAt(x, y + GRAD_STEP) - depthAt(x, y - GRAD_STEP)) / (2 * GRAD_STEP);\n const len = Math.hypot(gx, gy);\n return len < 1e-9 ? [0, 0] : [gx / len, gy / len];\n },\n };\n}\n\n/**\n * Build a section's rake field from its rows.\n *\n * `rows` should be the section's member seats grouped by row. With fewer than two\n * usable rows there is nothing to build a field from, so the radial model is kept\n * — which is also how every chart behaved before this, leaving a row-less or\n * single-row section unchanged.\n */\nexport function buildSectionRake(rows: RakeRow[], focal: Point): SectionRake {\n const fits: RowFit[] = [];\n for (const r of rows) {\n const f = fitRow(r.points, focal);\n if (f) fits.push(f);\n }\n if (fits.length < 2) return radialRake(focal);\n // Front-first ordering is the contract `rows` promises, and the deck builder\n // relies on it to pair each ribbon with the riser below it.\n fits.sort((a, b) => a.depth - b.depth);\n return rowsRake(fits);\n}\n","/**\n * The raked deck, built from a section's ROWS instead of from its outline.\n *\n * ## Why the deck is not a tessellated surface any more\n *\n * The previous deck was one cap over the section outline, tessellated against a\n * height field and refined until it tracked it. That works only while the field\n * is smooth and convex. Once height is defined so that every row is LEVEL — the\n * fix for rows visibly tilting, see `core/rake.ts` — the field stops being\n * either:\n *\n * - A section commonly holds several blocks at different heights (the\n * amphitheatre gallery is one section holding six wedges). Any field that is\n * level on each row must JUMP between blocks. Measured, that jump drove the\n * cap's worst error to 11 m against a 0.15 m bound.\n * - Smoothing the jump away puts the tilt straight back. The two requirements\n * are contradictory, so no amount of refinement resolves it.\n *\n * The jump is not an artefact — it is a step between two blocks, which is a\n * WALL. Walls are geometry. So the deck is emitted as geometry that already has\n * the right shape rather than as a surface something has to approximate:\n *\n * - one **ribbon** per row, horizontal, at that row's level;\n * - a **riser** dropping from each ribbon's front edge to the level below;\n * - a flat **landing** under everything, for the parts of the outline that hold\n * no rows (aisles, margins, the gaps between blocks).\n *\n * Every deck vertex therefore lies on a row, where the height is exactly defined.\n * A seat's clearance above its own ribbon is exact by construction instead of\n * measured against a tessellation error, and because every ribbon is horizontal\n * the whole deck shades with a single up normal.\n */\n\nimport type { Point } from '../../core/types';\nimport type { RGB } from '../palette';\nimport polygonClipping from 'polygon-clipping';\nimport { CHART_UNITS_PER_METRE } from '../../core/units';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport earcut from 'earcut';\nimport { MeshBuilder, M } from './geometry';\n\n/** A row ready to draw: its seats as a polyline, and the level it sits at. */\nexport interface BandRow {\n readonly pts: readonly Point[];\n /** World-metre height of this row's ribbon. */\n readonly y: number;\n /**\n * The row's depth ordinate, chart units — its MEAN distance from the focal.\n *\n * Carried through rather than recomputed here. Deriving it from the row's first\n * seat was tried and buried 20 seats: in a section holding several blocks, one\n * row's first seat and the next row's first seat can be in different blocks\n * entirely, so the implied row gap was wrong by a large factor and a ribbon\n * reached forward far enough to cover the row in front of it.\n */\n readonly depth: number;\n /**\n * Which block (stand) this row belongs to.\n *\n * Neighbour and riser resolution must stay inside a block. Across blocks the\n * nearest row is often the one across an aisle, at a different level, so a\n * ribbon would size itself against a stand it is not part of and a riser would\n * step down onto the wrong deck.\n */\n readonly blockId: number;\n /**\n * A ready-made deck FOOTPRINT for this entry, chart units, convex and closed —\n * used instead of the row-ribbon path when the entry is not a row at all.\n *\n * A table is the case this exists for. Its seats ring a centre rather than\n * running across the view, so \"offset the polyline sideways\" describes nothing:\n * whichever way the ribbon is offset it covers an arc of the ring and leaves\n * the rest of it hanging. Measured on the nightclub VIP terrace (14 six-seat\n * round tables), authoring any rake at all left 5 seats off-deck at 1°, 18 at\n * 5° and 24 at 10°. The count barely moves with the rake because the fault is\n * in the deck's PLAN SHAPE, not in its height — a taller step just exposes the\n * ground that was already missing.\n *\n * The patch is the convex hull of the entry's seats grown outward by the same\n * clearance a ribbon carries ({@link MIN_REACH_U}), so a table's deck reaches\n * past its outermost seat CENTRE by a full seat-dot allowance exactly as a\n * ribbon does past its end seat. It is supplied by the surface builder, which\n * is where the authored objects are known — deriving \"is this a table\" by\n * guessing at seat geometry here would re-answer a question the chart already\n * answers.\n */\n readonly patch?: readonly Point[];\n}\n\n/**\n * How far a ribbon reaches FORWARD, as a fraction of the gap to the row in front.\n *\n * Deliberately past the halfway line. A ribbon is built by offsetting its own\n * row's polyline, and the ribbon in front is built by offsetting a different\n * polyline with a different vertex count, so their edges do not land on exactly\n * the same curve. Meeting them at 0.5 each would leave hairline gaps showing the\n * landing through the deck. Overlapping instead is free: the ribbon behind is\n * HIGHER, so it simply covers the seam, and the riser closes the vertical face.\n */\nconst FRONT_REACH = 0.58;\n\n/**\n * Smallest step worth drawing a riser for, world metres.\n *\n * A 1e-4 threshold was tried and emitted 142 needle triangles with aspect ratios\n * to 17,776: risers 5 m long and 0.3 mm tall, where two rows resolved to almost\n * the same level. Such a step is invisible, and the ribbons already overlap\n * enough to close the seam without it. 1 cm is below anything a viewer can see\n * and far above the degenerate range.\n */\nconst MIN_RISER_M = 0.01;\n\n/**\n * Smallest half-width a ribbon may have, chart units.\n *\n * A seat's dot has a real world radius, so a ribbon narrower than that leaves the\n * dot's rim hanging over the aisle beside it — the same defect the deck padding\n * (`outsetRing`) exists to prevent on the cap path, and it is the same 1.5x\n * margin here. Two orchestra seats were still failing on ribbon width alone\n * before this floor; row pitch happened to be barely over a dot diameter there.\n */\nexport const MIN_REACH_U = SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE;\n\n/** How far a ribbon reaches BACK, as a fraction of the gap to the row behind. */\nconst BACK_REACH = 0.5;\n\n/**\n * How far a ribbon extends past its end seats, as a fraction of the SEAT spacing\n * along the row — not of the row gap.\n *\n * The row gap was tried and buried 23 seats. A section can hold several blocks\n * side by side (the amphitheatre gallery holds six), and the aisle between two\n * blocks is often narrower than a row gap, so a ribbon extended by a row gap\n * reached over its neighbour's seats — which sit at a DIFFERENT level, so the\n * overhanging ribbon painted over them. Seat spacing is the right measure: it is\n * exactly enough to carry the end seat's own dot and cannot cross an aisle.\n */\nconst END_REACH = 0.6;\n\n/**\n * Outward normals along a polyline, pointing in the direction of increasing\n * depth (away from `focal`).\n *\n * Taken from the row's own local direction rather than from the height field's\n * gradient: the field is a step function now, so its gradient is zero almost\n * everywhere and undefined on the steps.\n */\nfunction rowNormals(pts: readonly Point[], focal: Point): Array<readonly [number, number]> {\n const n = pts.length;\n const raw: Array<readonly [number, number]> = [];\n for (let i = 0; i < n; i++) {\n // Central difference gives a smooth normal along a curved row; the ends fall\n // back to their single adjacent segment.\n const a = pts[Math.max(0, i - 1)];\n const b = pts[Math.min(n - 1, i + 1)];\n let dx = b.x - a.x, dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-9) { raw.push([0, 0]); continue; }\n dx /= len; dy /= len;\n raw.push([-dy, dx]);\n }\n\n // Orient the WHOLE ROW at once, from the sum of the per-vertex tests, rather\n // than flipping each vertex on its own.\n //\n // Per-vertex orientation was tried and left a hole in the deck. The test is\n // \"does this normal point away from the focal\", and its value passes through\n // zero when a row runs radially — straight at the stage rather than across it.\n // The amphitheatre orchestra has such rows, and there the sign flipped between\n // two adjacent vertices, so the quad between them was built with its two ends\n // offset in OPPOSITE directions: a bow-tie that covers neither side properly.\n // One seat's dot rim ended up over the void.\n //\n // A row is a single object with one front and one back, so the decision belongs\n // to the row. Summing is also the stable form of the same test: it is dominated\n // by the vertices where the answer is unambiguous.\n let vote = 0;\n for (let i = 0; i < n; i++) {\n vote += (pts[i].x - focal.x) * raw[i][0] + (pts[i].y - focal.y) * raw[i][1];\n }\n const flip = vote < 0;\n return flip ? raw.map(([x, y]) => [-x, -y] as const) : raw;\n}\n\n/** Extend a polyline past both ends along its end tangents by `by` chart units. */\nfunction extendEnds(pts: readonly Point[], by: number): Point[] {\n if (pts.length < 2 || by <= 0) return [...pts];\n const out = [...pts];\n const dir = (p: Point, q: Point): Point => {\n const dx = q.x - p.x, dy = q.y - p.y;\n const len = Math.hypot(dx, dy) || 1;\n return { x: dx / len, y: dy / len };\n };\n const head = dir(out[1], out[0]);\n const tail = dir(out[out.length - 2], out[out.length - 1]);\n out.unshift({ x: out[0].x + head.x * by, y: out[0].y + head.y * by });\n out.push({\n x: out[out.length - 1].x + tail.x * by,\n y: out[out.length - 1].y + tail.y * by,\n });\n return out;\n}\n\n/**\n * Angular step used when rounding a patch's corners, radians (~15°).\n *\n * The offset of a convex hull by a fixed distance is that hull swept by a DISC,\n * so every corner is a circular arc. Sampling it is the only approximation in\n * the patch, and it is taken on the OUTSIDE (see `CORNER_SEGMENTS` below) so the\n * sampled polygon still contains the true offset rather than cutting inside it.\n */\nconst CORNER_STEP_RAD = Math.PI / 12;\n\n/**\n * Convex hull of a point set (Andrew's monotone chain), counter-clockwise.\n *\n * Returns fewer than 3 points when the input is a point or a segment; callers\n * fall back to a circle there, which is the correct offset of both.\n */\nfunction convexHull(pts: readonly Point[]): Point[] {\n if (pts.length < 3) return [...pts];\n const s = [...pts].sort((a, b) => (a.x - b.x) || (a.y - b.y));\n const cross = (o: Point, a: Point, b: Point): number =>\n (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);\n const half = (src: readonly Point[]): Point[] => {\n const out: Point[] = [];\n for (const p of src) {\n while (out.length >= 2 && cross(out[out.length - 2], out[out.length - 1], p) <= 0) out.pop();\n out.push(p);\n }\n out.pop();\n return out;\n };\n const hull = [...half(s), ...half([...s].reverse())];\n return hull.length >= 3 ? hull : [...pts];\n}\n\n/**\n * The deck footprint a cluster of seats needs: their convex hull grown outward\n * by `pad` chart units, as a closed counter-clockwise ring.\n *\n * Exact rather than approximate, and that matters: the property the caller is\n * buying is \"every seat centre lies at least `pad` inside this ring\", which is\n * what makes a table's deck carry its own seat dots. A hull offset is the\n * Minkowski sum of the hull with a disc of radius `pad`, and that sum contains\n * every seat's own disc by construction. Sampling the corner arcs is the one\n * place accuracy could leak, so the samples are placed on the CIRCUMSCRIBED\n * radius — the sampled polygon then contains the true arc instead of chording\n * across it, and the guarantee survives the discretisation.\n */\nexport function seatClusterPatch(pts: readonly Point[], pad: number): Point[] {\n if (!pts.length || !(pad > 0)) return [];\n const hull = convexHull(pts);\n // Round a corner from one outward normal to the next, on the circumscribed\n // radius so the chord between samples still clears `pad`.\n const arc = (v: Point, from: number, to: number, out: Point[]): void => {\n let sweep = to - from;\n while (sweep < 0) sweep += Math.PI * 2;\n while (sweep > Math.PI * 2) sweep -= Math.PI * 2;\n const steps = Math.max(1, Math.ceil(sweep / CORNER_STEP_RAD));\n const r = pad / Math.cos(sweep / steps / 2);\n for (let k = 0; k <= steps; k++) {\n const a = from + (sweep * k) / steps;\n out.push({ x: v.x + Math.cos(a) * r, y: v.y + Math.sin(a) * r });\n }\n };\n\n if (hull.length < 3) {\n // A single seat, or two seats (a collinear \"hull\"). The bounding circle grown\n // by `pad` covers the true offset in both cases — it over-covers a segment\n // slightly, which is harmless at the scale of one or two chairs and avoids a\n // second geometric path for a case a real table never hits.\n let cx = 0, cy = 0;\n for (const p of hull) { cx += p.x; cy += p.y; }\n cx /= hull.length || 1; cy /= hull.length || 1;\n let far = 0;\n for (const p of hull) far = Math.max(far, Math.hypot(p.x - cx, p.y - cy));\n const out: Point[] = [];\n const steps = Math.max(3, Math.ceil((Math.PI * 2) / CORNER_STEP_RAD));\n const r = (far + pad) / Math.cos(Math.PI / steps);\n for (let k = 0; k < steps; k++) {\n const a = (k / steps) * Math.PI * 2;\n out.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r });\n }\n return out;\n }\n\n const n = hull.length;\n // Outward normal of the edge leaving vertex i, for a CCW hull.\n const edgeAngle: number[] = [];\n for (let i = 0; i < n; i++) {\n const a = hull[i], b = hull[(i + 1) % n];\n edgeAngle.push(Math.atan2(-(b.x - a.x), b.y - a.y));\n }\n const out: Point[] = [];\n for (let i = 0; i < n; i++) {\n // At each vertex sweep from the previous edge's normal to this edge's, then\n // walk this edge offset outward. Consecutive vertices' runs join along the\n // shared edge, so the concatenation closes into one ring.\n arc(hull[i], edgeAngle[(i + n - 1) % n], edgeAngle[i], out);\n }\n return dedupeAdjacent(out);\n}\n\n/** Perpendicular distance from a point to a row's polyline, chart units. */\nfunction distToPolyline(pts: readonly Point[], x: number, y: number): number {\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** A row's SPATIAL neighbours: how wide its ribbon is, and what it steps down to. */\ninterface Neighbourhood {\n /** Perpendicular distance to the nearest other row, chart units. */\n pitch: number;\n /** World-metre level of the nearest row in FRONT, or null if this row is first. */\n belowY: number | null;\n}\n\n/**\n * Resolve each row's neighbours by POSITION, not by order in the array.\n *\n * `rows` arrives sorted by depth across the whole section, and a section\n * routinely holds several blocks. Two consecutive entries in that ordering are\n * usually the same row index in two DIFFERENT blocks, at almost the same depth —\n * so treating them as neighbours gave a row pitch of nearly zero. Measured, that\n * produced ribbons a couple of centimetres wide (so seat rims hung off the deck\n * in 394 of 400 samples) and risers 5 m long by 0.3 mm tall.\n *\n * The spatially nearest row is the real neighbour whatever block it is in, and\n * the nearest row that is closer to the focal is the one this ribbon steps down\n * onto.\n */\nexport function rowNeighbourhoods(rows: readonly BandRow[]): Neighbourhood[] {\n // Bounding circle per row, so the exact polyline test is skipped for rows that\n // cannot possibly be nearest. This is O(rows^2) at heart and a big venue has\n // thousands of rows in a section.\n const bounds = rows.map((r) => {\n let cx = 0, cy = 0;\n for (const p of r.pts) { cx += p.x; cy += p.y; }\n const n = r.pts.length || 1;\n cx /= n; cy /= n;\n let rad = 0;\n for (const p of r.pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > rad) rad = d;\n }\n return { cx, cy, rad };\n });\n // Probe from points ON the row, at a quarter, a half and three quarters along\n // it. The centroid was tried and broke the arena: its rows are strong arcs, and\n // an arc's centroid lies well inside the curve rather than on it, so distances\n // measured from there are not the row pitch at all (arena rims went 0 -> 272\n // off deck). A point on the polyline measures the true perpendicular gap, and\n // three of them keep one odd row end from setting the whole ribbon's width.\n const probesOf = (pts: readonly Point[]): Point[] => {\n const n = pts.length;\n if (n <= 2) return [...pts];\n return [pts[Math.floor(n * 0.25)], pts[Math.floor(n * 0.5)], pts[Math.floor(n * 0.75)]];\n };\n\n const out: Neighbourhood[] = [];\n for (let i = 0; i < rows.length; i++) {\n const probes = probesOf(rows[i].pts);\n const pitches: number[] = [];\n let bestFrontD = Infinity, belowY: number | null = null;\n for (const c of probes) {\n let nearest = Infinity;\n for (let j = 0; j < rows.length; j++) {\n // Neighbours are rows at a DIFFERENT LEVEL, not rows in the same block.\n //\n // \"Same block\" was tried and broke the concert hall: block detection\n // legitimately fragments one stand into several (sec-terr-0's 12 rows\n // resolved into 4), and a fragment's front neighbour then sits in another\n // block and was skipped — so the pitch came from a distant row, the\n // ribbon reached far forward, and it buried the seats of the row in front\n // by 0.20 m.\n //\n // Level is the property that actually distinguishes the two cases: a\n // lateral neighbour across an aisle shares this row's level (it is the\n // same row, split), while the row in front or behind does not.\n if (j === i || Math.abs(rows[j].y - rows[i].y) < 1e-3) continue;\n // Admissible lower bound: the true distance can never be smaller than\n // this, so a row that cannot beat EITHER accumulator can be skipped\n // outright. Guarding on only one of them left the prune almost never\n // firing, which is the whole cost of this function.\n const b = bounds[j];\n const lower = Math.hypot(c.x - b.cx, c.y - b.cy) - b.rad;\n if (lower >= nearest && lower >= bestFrontD) continue;\n const d = distToPolyline(rows[j].pts, c.x, c.y);\n if (d < nearest) nearest = d;\n // \"In front\" = nearer the focal, i.e. a smaller depth ordinate.\n if (rows[j].depth < rows[i].depth && d < bestFrontD) {\n bestFrontD = d;\n belowY = rows[j].y;\n }\n }\n if (Number.isFinite(nearest) && nearest > 0) pitches.push(nearest);\n }\n pitches.sort((a, b) => a - b);\n // Median: robust to one probe landing beside an aisle or a short row.\n const pitch = pitches.length ? pitches[Math.floor(pitches.length / 2)] : 1;\n out.push({ pitch, belowY });\n }\n return out;\n}\n\nexport interface BandColors {\n /** Ribbon (tread) top colour, AO already applied. */\n tread: RGB;\n /** Riser face colour, AO already applied. */\n riser: RGB;\n}\n\n\n/** One row's ribbon: the polyline it is built on, its normals, and its reaches. */\ninterface Ribbon {\n pts: Point[];\n nrm: Array<readonly [number, number]>;\n front: number;\n back: number;\n}\n\n/**\n * Resolve one row's ribbon geometry.\n *\n * Shared by the mesh emitter and the footprint builder so the drawn ribbons and\n * the block outline extruded beneath them are derived from ONE computation. They\n * disagreeing is the same class of bug as the cap and the seats disagreeing.\n */\nfunction ribbonOf(rows: readonly BandRow[], i: number, nbrs: Neighbourhood[], focal: Point): Ribbon | null {\n const row = rows[i];\n // A \"row\" of one seat is a free-standing seat, not a row — it still needs a\n // deck under it, so give it a short segment across the view direction and let\n // the normal ribbon path carry it. Skipping these left their dots hanging\n // over the landing.\n const rowPts = row.pts.length >= 2\n ? [...row.pts]\n : row.pts.length === 1\n ? ((): Point[] => {\n const p = row.pts[0];\n let dx = p.x - focal.x, dy = p.y - focal.y;\n const len = Math.hypot(dx, dy) || 1;\n dx /= len; dy /= len;\n // Across the line of sight, half a pitch each way.\n const h = Math.max(nbrs[i].pitch, 1e-3) * 0.5;\n return [{ x: p.x + dy * h, y: p.y - dx * h }, { x: p.x - dy * h, y: p.y + dx * h }];\n })()\n : [];\n if (rowPts.length < 2) return null;\n\n let seatSpan = 0, spanN = 0;\n for (let k = 1; k < rowPts.length; k++) {\n const d = Math.hypot(rowPts[k].x - rowPts[k - 1].x, rowPts[k].y - rowPts[k - 1].y);\n if (d > 1e-6) { seatSpan += d; spanN++; }\n }\n const spacing = spanN > 0 ? seatSpan / spanN : 0;\n const pts = extendEnds(rowPts, Math.max(spacing * END_REACH, MIN_REACH_U));\n return {\n pts,\n nrm: rowNormals(pts, focal),\n front: Math.max(nbrs[i].pitch * FRONT_REACH, MIN_REACH_U),\n back: Math.max(nbrs[i].pitch * BACK_REACH, MIN_REACH_U),\n };\n}\n\n/** A block: the footprint its ribbons cover, and the level its base sits at. */\nexport type { Neighbourhood };\n\nexport interface DeckFootprint {\n /** Outer ring, chart units. */\n outline: Point[];\n /** Any interior rings (an enclosed gap between rows). */\n holes: Point[][];\n /** World-metre height of this block's lowest ribbon — the top of its base. */\n topY: number;\n}\n\n/**\n * The footprint of each BLOCK in a section, as the union of its ribbons.\n *\n * This replaces extruding the section's whole authored outline under the deck.\n * That outline is drawn generously — it reaches past the last row and across the\n * gaps between blocks — so a single flat plate at the front row's level stuck out\n * around the seating as a large slab, and every block's end read as a square cut\n * through that slab rather than as the edge of a stand.\n *\n * Unioning the ribbons instead yields one polygon per block automatically (the\n * amphitheatre's three sections resolve to 4, 5 and 6 blocks — exactly their\n * wedge counts), each hugging its own seating, and each carrying its own base\n * level rather than sharing the section's. Costs 7–13 ms per section at build\n * time, which is paid once.\n */\n/**\n * Extra width given to a ribbon in the block footprint, chart units (~3 cm).\n *\n * The footprint is the base the ribbons stand on, so a small margin keeps its\n * edge from coinciding exactly with a tread edge and z-fighting it.\n */\nconst FOOTPRINT_MARGIN_U = 1.5;\n\n/**\n * Douglas-Peucker tolerance for a block outline, chart units (~7 mm).\n *\n * The base carries no detail at this scale — the SEATS are carried by the\n * ribbons above it — and long runs of near-collinear vertices are what earcut\n * fans into needle triangles.\n */\nconst FOOTPRINT_TOLERANCE_U = 0.3;\n\n/** Douglas-Peucker on an open point run. */\nfunction simplifyRun(pts: Point[], tol: number): Point[] {\n if (pts.length < 3) return pts;\n const a = pts[0], b = pts[pts.length - 1];\n const dx = b.x - a.x, dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n let worst = -1, worstI = -1;\n for (let i = 1; i < pts.length - 1; i++) {\n const p = pts[i];\n const d = len > 1e-12\n ? Math.abs((p.x - a.x) * dy - (p.y - a.y) * dx) / len\n : Math.hypot(p.x - a.x, p.y - a.y);\n if (d > worst) { worst = d; worstI = i; }\n }\n if (worst <= tol || worstI < 0) return [a, b];\n const left = simplifyRun(pts.slice(0, worstI + 1), tol);\n const right = simplifyRun(pts.slice(worstI), tol);\n return [...left.slice(0, -1), ...right];\n}\n\n/** Simplify a closed ring, keeping it closed. */\nfunction simplifyRing(ring: Point[], tol: number): Point[] {\n if (ring.length < 4) return ring;\n const out = simplifyRun([...ring, ring[0]], tol);\n out.pop();\n return out.length >= 3 ? out : ring;\n}\n\nexport function deckFootprints(rows: readonly BandRow[], focal: Point, shared?: Neighbourhood[]): DeckFootprint[] {\n if (rows.length < 2) return [];\n const nbrs = shared ?? rowNeighbourhoods(rows);\n\n // Group by the block the structure resolver already identified. The first\n // implementation instead recovered blocks by UNIONING every ribbon and reading\n // off the resulting polygons — correct, but it made a boolean solve the\n // question a resolved field already answers, and ribbons overlap each other by\n // design (FRONT_REACH is past the halfway line), which is the worst case for a\n // sweep-line union. Profiled at 48 % of scene build on a 50k venue and 58 % on\n // 99k, growing superlinearly: 1,024 ms -> 5,285 ms for twice the seats.\n const byBlock = new Map<number, number[]>();\n for (let i = 0; i < rows.length; i++) {\n const a = byBlock.get(rows[i].blockId);\n if (a) a.push(i); else byBlock.set(rows[i].blockId, [i]);\n }\n\n const out: DeckFootprint[] = [];\n for (const [, allIndices] of byBlock) {\n // A patch entry (a table) stands on its own base. It is not part of the\n // ribbon stack — including it in the front-to-back trace would drag the\n // block's outline through a ring of seats that has no front or back — and a\n // pedestal per table is what a raked table terrace physically is.\n const indices: number[] = [];\n for (const i of allIndices) {\n const patch = rows[i].patch;\n if (patch && patch.length >= 3) out.push({ outline: [...patch], holes: [], topY: rows[i].y });\n else indices.push(i);\n }\n if (!indices.length) continue;\n // Front to back, so the ring below traces the block's real outline.\n indices.sort((a, b) => rows[a].depth - rows[b].depth);\n const ribbons = indices.map((i) => ribbonOf(rows, i, nbrs, focal));\n const usable = ribbons.filter((r): r is Ribbon => r !== null);\n if (!usable.length) continue;\n\n const frontOf = (r: Ribbon, k: number): Point => ({\n x: r.pts[k].x - r.nrm[k][0] * (r.front + FOOTPRINT_MARGIN_U),\n y: r.pts[k].y - r.nrm[k][1] * (r.front + FOOTPRINT_MARGIN_U),\n });\n const backOf = (r: Ribbon, k: number): Point => ({\n x: r.pts[k].x + r.nrm[k][0] * (r.back + FOOTPRINT_MARGIN_U),\n y: r.pts[k].y + r.nrm[k][1] * (r.back + FOOTPRINT_MARGIN_U),\n });\n\n // Trace the boundary directly: the front row's front edge, down the right\n // ends, the back row's back edge reversed, and up the left ends. O(rows)\n // instead of a boolean over every ribbon.\n const first = usable[0], last = usable[usable.length - 1];\n const ring: Point[] = [];\n for (let k = 0; k < first.pts.length; k++) ring.push(frontOf(first, k));\n for (const r of usable) ring.push(backOf(r, r.pts.length - 1));\n for (let k = last.pts.length - 1; k >= 0; k--) ring.push(backOf(last, k));\n for (let i = usable.length - 1; i >= 0; i--) ring.push(frontOf(usable[i], 0));\n\n const deduped = dedupeAdjacent(ring);\n let topY = Infinity;\n for (const i of indices) if (rows[i].y < topY) topY = rows[i].y;\n if (!Number.isFinite(topY)) continue;\n\n // VERIFY the traced ring before trusting it. Tracing assumes a block's rows\n // stack cleanly front to back; a block that folds or wraps can trace a\n // self-intersecting ring, which still has positive area but covers the wrong\n // ground — on the West End theatre that left seats standing over a base that\n // had moved out from under them. The property that matters is that the base\n // covers its own ribbons, so test exactly that and fall back to the boolean\n // union for the rare block that fails.\n const covers = deduped.length >= 3\n && Math.abs(ringArea(deduped)) > 1e-6\n && usable.every((r) => {\n const mid = r.pts[Math.floor(r.pts.length / 2)];\n return pointInRing(deduped, mid.x, mid.y);\n });\n\n if (covers) {\n out.push({ outline: simplifyRing(deduped, FOOTPRINT_TOLERANCE_U), holes: [], topY });\n continue;\n }\n for (const poly of unionRibbons(usable)) out.push({ outline: poly, holes: [], topY });\n }\n return out;\n}\n\n/**\n * The original boolean union, kept as the fallback for blocks the direct trace\n * cannot describe. Correct but expensive, so it now runs for a handful of blocks\n * rather than for every one.\n */\nfunction unionRibbons(ribbons: readonly Ribbon[]): Point[][] {\n const rings: Array<[number, number][][]> = [];\n for (const r of ribbons) {\n const f: [number, number][] = [];\n const b: [number, number][] = [];\n const rf = r.front + FOOTPRINT_MARGIN_U;\n const rb = r.back + FOOTPRINT_MARGIN_U;\n for (let k = 0; k < r.pts.length; k++) {\n const p = r.pts[k], n = r.nrm[k];\n f.push([p.x - n[0] * rf, p.y - n[1] * rf]);\n b.push([p.x + n[0] * rb, p.y + n[1] * rb]);\n }\n const ring = [...f, ...b.reverse()];\n if (ring.length < 3) continue;\n ring.push(ring[0]);\n rings.push([ring]);\n }\n if (!rings.length) return [];\n try {\n const merged = polygonClipping.union(rings[0], ...rings.slice(1));\n const out: Point[][] = [];\n for (const poly of merged) {\n if (!poly.length || poly[0].length < 4) continue;\n const pts = poly[0].map(([x, y]) => ({ x, y }));\n const first = pts[0], last = pts[pts.length - 1];\n if (pts.length > 1 && Math.abs(first.x - last.x) < 1e-9 && Math.abs(first.y - last.y) < 1e-9) pts.pop();\n if (pts.length >= 3) out.push(simplifyRing(pts, FOOTPRINT_TOLERANCE_U));\n }\n return out;\n } catch {\n return [];\n }\n}\n\n/** Shoelace area of a ring. */\nfunction ringArea(ring: readonly Point[]): number {\n let a = 0;\n for (let i = 0, n = ring.length; i < n; i++) {\n const p = ring[i], q = ring[(i + 1) % n];\n a += p.x * q.y - q.x * p.y;\n }\n return a / 2;\n}\n\n/** Drop consecutive duplicates, which the traced ring produces at its corners. */\nfunction dedupeAdjacent(ring: readonly Point[]): Point[] {\n const out: Point[] = [];\n for (const p of ring) {\n const last = out[out.length - 1];\n if (last && Math.hypot(p.x - last.x, p.y - last.y) < 1e-9) continue;\n out.push(p);\n }\n while (out.length > 2 && Math.hypot(out[0].x - out[out.length - 1].x, out[0].y - out[out.length - 1].y) < 1e-9) out.pop();\n return out;\n}\n\n/** Even-odd point-in-ring test. */\nfunction pointInRing(ring: readonly Point[], x: number, y: number): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const a = ring[i], b = ring[j];\n if ((a.y > y) !== (b.y > y) && x < ((b.x - a.x) * (y - a.y)) / (b.y - a.y) + a.x) inside = !inside;\n }\n return inside;\n}\n\n/**\n * Cheap containment test for the clip region, so the boolean is only run for the\n * quads that actually straddle a boundary.\n */\nclass ClipTest {\n private rings: Point[][];\n private minX = Infinity; private minY = Infinity;\n private maxX = -Infinity; private maxY = -Infinity;\n\n constructor(rings: Point[][]) {\n this.rings = rings;\n for (const r of rings) {\n for (const p of r) {\n if (p.x < this.minX) this.minX = p.x;\n if (p.y < this.minY) this.minY = p.y;\n if (p.x > this.maxX) this.maxX = p.x;\n if (p.y > this.maxY) this.maxY = p.y;\n }\n }\n }\n\n /** True when every point lies strictly inside ONE ring of the clip region. */\n containsAll(pts: readonly Point[]): boolean {\n for (const p of pts) {\n if (p.x < this.minX || p.x > this.maxX || p.y < this.minY || p.y > this.maxY) return false;\n }\n // A quad spanning two disjoint rings must go through the boolean, so the\n // test demands a SINGLE ring contain all of it.\n for (const ring of this.rings) {\n let all = true;\n for (const p of pts) {\n if (!pointInRing(ring, p.x, p.y)) { all = false; break; }\n }\n if (all) return true;\n }\n return false;\n }\n}\n\n/**\n * Emit a horizontal CONVEX polygon clipped to `clipRing`, triangulated.\n *\n * Treads are horizontal, so every piece shades with the same up normal whatever\n * shape the clip leaves behind — which is what makes clipping cheap here.\n *\n * Convex is all the callers produce: a ribbon segment is a quad and a table\n * patch is an offset convex hull, so the unclipped fast path can fan from one\n * vertex instead of running a triangulator.\n */\nfunction emitClippedPoly(\n builder: MeshBuilder,\n clipRing: [number, number][][],\n clipTest: ClipTest,\n poly: Point[],\n y: number,\n color: RGB,\n): void {\n if (poly.length < 3) return;\n // FAST PATH: a polygon wholly inside the clip contributes nothing to clip, and\n // the overwhelming majority are — only ribbons at a section's edge straddle it.\n // Running a polygon boolean per ribbon SEGMENT regardless cost ~100,000\n // intersections on a 99k-seat venue and dominated scene build (8.5 s of 11 s).\n if (clipTest.containsAll(poly)) {\n const UPF = [0, 1, 0] as const;\n const a = poly[0];\n for (let i = 1; i + 1 < poly.length; i++) {\n const b = poly[i], c = poly[i + 1];\n builder.tri([a.x * M, y, a.y * M], [b.x * M, y, b.y * M], [c.x * M, y, c.y * M], UPF, color);\n }\n return;\n }\n const ring: [number, number][] = poly.map((p) => [p.x, p.y]);\n ring.push(ring[0]);\n let pieces: ReturnType<typeof polygonClipping.intersection>;\n try {\n pieces = polygonClipping.intersection([ring], clipRing);\n } catch {\n return; // a degenerate quad simply contributes nothing\n }\n const UP = [0, 1, 0] as const;\n for (const poly of pieces) {\n if (!poly.length || poly[0].length < 4) continue;\n const outer = poly[0];\n const flat: number[] = [];\n const pts: Array<[number, number]> = [];\n for (let i = 0; i < outer.length - 1; i++) { // drop the repeated closing point\n flat.push(outer[i][0], outer[i][1]);\n pts.push([outer[i][0], outer[i][1]]);\n }\n if (pts.length < 3) continue;\n const tris = earcut(flat, undefined, 2);\n for (let i = 0; i < tris.length; i += 3) {\n const a = pts[tris[i]], b = pts[tris[i + 1]], c = pts[tris[i + 2]];\n builder.tri(\n [a[0] * M, y, a[1] * M],\n [b[0] * M, y, b[1] * M],\n [c[0] * M, y, c[1] * M],\n UP, color,\n );\n }\n }\n}\n\n/**\n * Emit one section's ribbons and risers.\n *\n * `rows` must be ordered front first (nearest the focal), which is the contract\n * `SectionRake.rows` provides.\n */\nexport function emitDeckBands(\n builder: MeshBuilder,\n rows: readonly BandRow[],\n focal: Point,\n landingY: number,\n colors: BandColors,\n clip?: readonly Point[],\n shared?: Neighbourhood[],\n): void {\n if (rows.length < 2) return;\n // Reused from the footprint pass when the caller has one: this is the O(rows^2)\n // step and computing it twice per section doubled it for nothing.\n const nbrs = shared ?? rowNeighbourhoods(rows);\n const UP = [0, 1, 0] as const;\n const clipRing: [number, number][][] | null = clip && clip.length >= 3\n ? [[...clip.map((p) => [p.x, p.y] as [number, number]), [clip[0].x, clip[0].y]]]\n : null;\n const clipTest = clip && clip.length >= 3 ? new ClipTest([[...clip]]) : null;\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n\n // A table: draw its own footprint, not a ribbon. See `BandRow.patch`.\n if (row.patch && row.patch.length >= 3) {\n const patch = [...row.patch];\n const belowY = nbrs[i].belowY ?? landingY;\n if (clipRing && clipTest) emitClippedPoly(builder, clipRing, clipTest, patch, row.y, colors.tread);\n else {\n const a = patch[0];\n for (let k = 1; k + 1 < patch.length; k++) {\n const b = patch[k], c = patch[k + 1];\n builder.tri([a.x * M, row.y, a.y * M], [b.x * M, row.y, b.y * M], [c.x * M, row.y, c.y * M], UP, colors.tread);\n }\n }\n // A skirt all the way round, not just across the front. A patch is a\n // free-standing pedestal rather than a step in a stack, so every side of\n // it is exposed; leaving the sides open showed the void under the deck\n // from any angle but dead ahead.\n if (row.y > belowY + MIN_RISER_M) {\n let cx = 0, cy = 0;\n for (const p of patch) { cx += p.x; cy += p.y; }\n cx /= patch.length; cy /= patch.length;\n for (let k = 0; k < patch.length; k++) {\n const p = patch[k], q = patch[(k + 1) % patch.length];\n const dx = q.x - p.x, dy = q.y - p.y;\n const len = Math.hypot(dx, dy);\n if (len < 1e-6) continue;\n // Outward = the side of the edge the patch centre is NOT on.\n let nx = dy / len, ny = -dx / len;\n if ((p.x - cx) * nx + (p.y - cy) * ny < 0) { nx = -nx; ny = -ny; }\n const rn: readonly [number, number, number] = [nx, 0, ny];\n const pt: [number, number, number] = [p.x * M, row.y, p.y * M];\n const qt: [number, number, number] = [q.x * M, row.y, q.y * M];\n const pb: [number, number, number] = [p.x * M, belowY, p.y * M];\n const qb: [number, number, number] = [q.x * M, belowY, q.y * M];\n builder.tri(pt, qt, qb, rn, colors.riser);\n builder.tri(pt, qb, pb, rn, colors.riser);\n }\n }\n continue;\n }\n\n const rib = ribbonOf(rows, i, nbrs, focal);\n if (!rib) continue;\n const { pts, nrm, front, back } = rib;\n\n // The level below this ribbon: the spatially nearest row in front of it, or\n // the landing when nothing is in front (this row is its block's first).\n const belowY = nbrs[i].belowY ?? landingY;\n\n for (let k = 0; k + 1 < pts.length; k++) {\n const p = pts[k], q = pts[k + 1];\n const np = nrm[k], nq = nrm[k + 1];\n // Skip degenerate input: a duplicated seat position, or a vertex whose\n // normal collapsed. Emitting these produced needle triangles with aspect\n // ratios to 17,776 — meaningless geometry that flat-shades as dark streaks,\n // the exact defect the ring cleanup removed from the cap path.\n if (Math.hypot(q.x - p.x, q.y - p.y) < 1e-6) continue;\n if ((np[0] === 0 && np[1] === 0) || (nq[0] === 0 && nq[1] === 0)) continue;\n\n const pF: [number, number, number] = [(p.x - np[0] * front) * M, row.y, (p.y - np[1] * front) * M];\n const qF: [number, number, number] = [(q.x - nq[0] * front) * M, row.y, (q.y - nq[1] * front) * M];\n const pB: [number, number, number] = [(p.x + np[0] * back) * M, row.y, (p.y + np[1] * back) * M];\n const qB: [number, number, number] = [(q.x + nq[0] * back) * M, row.y, (q.y + nq[1] * back) * M];\n\n // Tread: horizontal, so one shared up normal is exact.\n if (clipRing && clipTest) {\n // A ribbon reaches past its own row on all four sides, and near a section\n // boundary that reach crosses into the NEXT section. Where the neighbour\n // sits lower, the overhang covers its seats: 27 concert-hall seats were\n // buried by 0.20 m under the terrace beside them, which is 0.60 m higher.\n // Clipping to the section's own padded outline keeps a deck inside the\n // section that drew it.\n emitClippedPoly(builder, clipRing, clipTest!, [\n { x: p.x - np[0] * front, y: p.y - np[1] * front },\n { x: q.x - nq[0] * front, y: q.y - nq[1] * front },\n { x: q.x + nq[0] * back, y: q.y + nq[1] * back },\n { x: p.x + np[0] * back, y: p.y + np[1] * back },\n ], row.y, colors.tread);\n } else {\n builder.tri(pF, qF, qB, UP, colors.tread);\n builder.tri(pF, qB, pB, UP, colors.tread);\n }\n\n // 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 seating surface — one function per section, consumed by everything.\n *\n * Before this module the venue had three independently-computed surfaces (tier\n * cap, row treads, seat dots) held apart by hardcoded offsets that had to agree,\n * and never did across chart shapes. Two measured defects came out of that:\n *\n * - The tier cap was a raw earcut triangulation of the section outline with a\n * *radial, nonlinear* height evaluated only at outline vertices. A 90 m chord\n * across a curved bowl is a flat plane through the seating: 23 % of arena\n * seats and 48 % of amphitheatre seats rendered UNDER their own tier cap\n * (worst overshoot 5.54 m).\n * - The cap took each section's front-edge datum from its OUTLINE vertices and\n * the doc focal, while `layout.ts`'s `assignEyeHeights` takes it from the\n * section's SEATS and each seat's own `focalPoint`. Different datum ⇒ a\n * per-section constant disagreement even where tessellation was exact.\n *\n * `deckAt(x, y)` here is the single definition of \"the height of the walkable\n * seating surface\". It mirrors `assignEyeHeights` exactly — same front-edge\n * datum, same drawn-radial-depth rise — so a seat dot and the deck it stands on\n * can no longer be computed from different models. The cap is tessellated\n * against this same function (see `extrudePrism`'s `maxCapError`), so the drawn\n * triangle mesh tracks it rather than chording across it.\n *\n * Pure: no OGL, no DOM. Unit-tested in workers/api/test/view3dSurface.test.ts.\n */\n\nimport type { ChartObject, ExpandedSeat, Point, SectionObject } from '../../core/types';\nimport { pointInPolygonWithHoles } from '../../core/layout';\nimport { buildSectionRake } from '../../core/rake';\nimport { resolveSection, distanceToPolyline, type ResolvedRow } from '../../core/venueStructure';\nimport { CHART_UNITS_PER_METRE, METRES_PER_CHART_UNIT, SEATED_EYE_HEIGHT_M, sectionGeometry } from '../../core/units';\nimport { outsetRing } from './geometry';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport { MIN_REACH_U, seatClusterPatch } from './deckBands';\n\n/** Top of the thin slab drawn for a section with no authored height/rake. Seats\n * on a flat chart rest on this, so it is part of the surface definition rather\n * than a constant duplicated by the seat builder. */\nexport const FLAT_SLAB_TOP_M = 0.05;\n\n/**\n * Subdivision TARGET for the drawn tier cap, metres — how closely it tries to\n * track `deckAt`. Consumed by `sceneModel` when it extrudes a tier.\n *\n * A target, not a guarantee: refinement is uniform per section (the only way to\n * stay watertight — see `emitCapUniform`) and bounded by a triangle budget, so a\n * section whose surface folds sharply stops refining before it reaches this.\n * {@link SEAT_CLEARANCE_M} is what actually protects the seats.\n */\nexport const CAP_MAX_ERROR_M = 0.05;\n\n/**\n * Clearance between a seat dot's origin and the deck it stands on, metres.\n *\n * The cap's error is ONE-SIDED and that is what makes a single clearance work.\n * `deckAt` is a cone — front-edge height plus a linear function of radial\n * distance — which is convex, so a triangle interpolating it lies at or above it\n * everywhere inside the triangle, never below. The cap can therefore only ever\n * float above the true surface, and only a seat needs protecting from it.\n *\n * The size, however, is MEASURED rather than proven. Refinement aims at\n * {@link CAP_MAX_ERROR_M} but is uniform per section and capped by a triangle\n * budget, and the residual peaks at the surface's fold, where a section's flat\n * front plateau turns into its rake. Measured worst cap error across all four\n * harness charts is 13 cm. 15 cm carries that while staying under the ~22 cm\n * seat-dot radius, so it is invisible. THIS is the number that must exceed the\n * cap's real error — the test suite asserts exactly that relationship.\n *\n * The test suite enforces the real invariant directly — zero buried seats on\n * every harness chart — so a regression in either number fails loudly rather\n * than silently eating the margin. If a chart ever needs a tighter cap, the fix\n * is fold-aware refinement (splitting triangles that straddle `frontU`), not a\n * bigger clearance.\n */\nexport const SEAT_CLEARANCE_M = 0.15;\n\n/**\n * How far outside its authored outline a section still owns seats, chart units.\n *\n * Matches the deck padding in `buildTier` exactly (1.5 seat-dot radii), because\n * it answers the same question: the deck is drawn at that padded ring, so a seat\n * standing on drawn deck must belong to the section that drew it. Any smaller and\n * boundary seats are orphaned onto the floor beneath their own tier.\n */\nexport const SEAT_OWNERSHIP_PAD_U = SEAT_DOT_RADIUS_M * 1.5 * CHART_UNITS_PER_METRE;\n\n/** One section's resolved seating surface. */\nexport interface SectionSurface {\n readonly sectionId: string;\n /** World-metre height of the seating deck at a chart-unit point. */\n deckAt(x: number, y: number): number;\n /**\n * ANALYTIC unit normal of the deck at a chart-unit point, world axes (y up).\n *\n * The drawn cap is a piecewise-linear approximation of `deckAt`, so its FACE\n * normals are discontinuous across every shared edge — measured at 46 % of cap\n * edges breaking >2°, worst 110°, which flat-shades into the dark streaks\n * across a raked deck. Worse, the near-degenerate triangles earcut emits at a\n * densified boundary (aspect ratios to 8.7e6) have numerically meaningless\n * face normals, so they shade as random dark needles.\n *\n * `deckAt` is a cone and therefore differentiable everywhere except its fold,\n * so the true normal is available in closed form and costs nothing. Shading the\n * cap with it instead of the face normal makes the surface read as smooth\n * regardless of tessellation AND makes a sliver's shading harmless — it now\n * matches its neighbours even when its own geometry is degenerate.\n */\n normalAt(x: number, y: number): [number, number, number];\n /** True when the section carries no rake — the deck is a constant plane. */\n readonly flat: boolean;\n /** Bottom of the section prism (floor base), world metres. */\n readonly bottomY: number;\n /**\n * The section's rows with the world-metre LEVEL each one sits at, front first.\n *\n * This is what the deck is actually built from. A row is a level ribbon and\n * consecutive ribbons are joined by a riser, so the drawn surface passes\n * exactly through every row instead of approximating a field between them —\n * and because a ribbon is horizontal, its cap error against the seats it\n * carries is zero by construction rather than by measurement.\n *\n * Empty for a flat section, and for a section with fewer than two rows (which\n * keeps the old smooth-cone path, since there is no row structure to build on).\n */\n readonly rowLevels: readonly {\n readonly pts: readonly Point[];\n readonly y: number;\n readonly depth: number;\n readonly blockId: number;\n /** Deck footprint for an entry that is a TABLE rather than a row — see\n * `BandRow.patch` in deckBands.ts for why a ring of seats cannot be decked\n * by offsetting a polyline. */\n readonly patch?: readonly Point[];\n }[];\n /** The landing height for the parts of the outline that hold no rows. */\n readonly landingY: number;\n}\n\nexport interface VenueSurfaces {\n /** Surface by section object id (NOT logical id — heights are per object). */\n bySection: Map<string, SectionSurface>;\n /** Owning section object id per seat index, or null when a seat sits outside\n * every section (free-standing rows). Parallel to the input seat array. */\n seatOwner: Array<string | null>;\n /** Deck height in world metres for seat index i (falls back to the seat's own\n * resolved eye height when it owns no section). */\n seatDeckY(i: number): number;\n /**\n * Seat pitch in CHART UNITS for seat index i, or undefined when its section\n * has no resolved rows.\n *\n * The spacing a seat actually has to itself — the smaller of its own row's\n * seat spacing and the gap to the neighbouring row. Callers size a seat's\n * marker from this.\n *\n * It is emphatically NOT the distance to the nearest other seat. That measure\n * shrank 80 amphitheatre dots to as little as 0.089 m, every one of them at a\n * WEDGE BOUNDARY: the last seat of one wedge sits 0.212 m from the first seat\n * of the next across an aisle, far closer than the 0.55 m spacing inside either\n * row. Two seats being near each other across an aisle says nothing about how\n * much room either one has.\n */\n seatPitchU(i: number): number | undefined;\n}\n\ninterface Bbox { minX: number; minY: number; maxX: number; maxY: number }\n\nfunction bboxOf(pts: Point[]): Bbox {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const p of pts) {\n if (p.x < minX) minX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.x > maxX) maxX = p.x;\n if (p.y > maxY) maxY = p.y;\n }\n return { minX, minY, maxX, maxY };\n}\n\n/** Even-odd point-in-ring test, for the closed table patches above. */\nfunction pointInRing(ring: readonly Point[], x: number, y: number): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const a = ring[i], b = ring[j];\n if ((a.y > y) !== (b.y > y) && x < ((b.x - a.x) * (y - a.y)) / (b.y - a.y) + a.x) inside = !inside;\n }\n return inside;\n}\n\n/** Hard ceiling on rake rise so a mis-authored or focal-wrapping section can\n * never produce a runaway spike (defect guard, carried over from buildTier). */\nconst MAX_TIER_RISE_M = 25;\n\nexport interface SurfaceUnit {\n objects: ChartObject[];\n focal: Point;\n baseHeightM: number;\n}\n\n/**\n * Resolve every section's seating surface and each seat's owning section.\n *\n * The front-edge datum is taken from the section's member SEATS when it has any\n * (matching `assignEyeHeights`), and only falls back to the outline's nearest\n * vertex for a section with no seats — where nothing can disagree with it.\n */\nexport function buildVenueSurfaces(units: SurfaceUnit[], seats: ExpandedSeat[]): VenueSurfaces {\n const bySection = new Map<string, SectionSurface>();\n const seatOwner = new Array<string | null>(seats.length).fill(null);\n const seatDeck = new Float64Array(seats.length);\n /** Level of the seat's own row, when its section resolved into rows. */\n const seatRowLevel = new Array<number | undefined>(seats.length).fill(undefined);\n /** Pitch of the seat's own row (chart units), when resolved. */\n const seatPitch = new Array<number | undefined>(seats.length).fill(undefined);\n\n // --- Pass 1: ownership + per-section seat front distance -------------------\n interface Acc {\n section: SectionObject;\n unit: SurfaceUnit;\n /** Nearest drawn focal distance among member seats (chart units). */\n frontU: number;\n hasSeats: boolean;\n /** Member seat positions grouped by row — the input the rake axis is fitted from. */\n rows: Map<string, Point[]>;\n /** Indices of this section's member seats, for the structure resolver. */\n seatIndices: number[];\n }\n const acc = new Map<string, Acc>();\n const boxes: Array<{ id: string; box: Bbox; section: SectionObject; unit: SurfaceUnit; outline: Point[] }> = [];\n /**\n * Object ids that expand into a RING of seats rather than a run of them.\n *\n * `expandTable` stamps every chair with `rowId = table.id`, so the structure\n * resolver — which only ever sees seats — groups a whole table into what it\n * calls a row. Geometrically it is nothing of the kind, and the deck builder\n * has to know the difference. Reading it off the authored objects is exact;\n * inferring \"these seats look like a ring\" from the points would be a second\n * opinion about a fact the chart already states.\n */\n const tableRowIds = new Set<string>();\n for (const unit of units) {\n for (const o of unit.objects) {\n if (o.type === 'table') tableRowIds.add(o.id);\n if (o.type !== 'section' || !o.outline || o.outline.length < 3) continue;\n // Ownership is tested against the PADDED outline — the same ring the deck\n // is actually drawn with (see `buildTier`'s `outsetRing`). Testing the raw\n // outline instead left seats authored just outside it unowned, so they fell\n // to the floor while the drawn deck covered them: measured as 9 buried\n // cinema seats and a 0.20 m drop on the uber arena. A seat resting on drawn\n // deck belongs to the section that drew it.\n const owned = outsetRing(o.outline, SEAT_OWNERSHIP_PAD_U);\n acc.set(o.id, { section: o, unit, frontU: Infinity, hasSeats: false, rows: new Map(), seatIndices: [] });\n boxes.push({ id: o.id, box: bboxOf(owned), section: o, unit, outline: owned });\n }\n }\n\n for (let i = 0; i < seats.length; i++) {\n const s = seats[i];\n for (const b of boxes) {\n // Bbox prefilter keeps the 14k-seat charts cheap; the polygon test only\n // runs for genuine candidates.\n if (s.x < b.box.minX || s.x > b.box.maxX || s.y < b.box.minY || s.y > b.box.maxY) continue;\n if (!pointInPolygonWithHoles({ x: s.x, y: s.y }, b.outline, b.section.holes)) continue;\n seatOwner[i] = b.id;\n const a = acc.get(b.id)!;\n a.hasSeats = true;\n const f = s.focalPoint ?? b.unit.focal;\n const d = Math.hypot(s.x - f.x, s.y - f.y);\n if (d < a.frontU) a.frontU = d;\n // Group by row, exactly as assignEyeHeights does — the rake axis is fitted\n // from the same grouping in both files, or the two models diverge again.\n a.seatIndices.push(i);\n const rowKey = s.rowId || `__seat-${i}`;\n const arr = a.rows.get(rowKey);\n if (arr) arr.push({ x: s.x, y: s.y }); else a.rows.set(rowKey, [{ x: s.x, y: s.y }]);\n break; // first containing section wins, exactly as assignEyeHeights does\n }\n }\n\n // --- Pass 2: build one surface function per section ------------------------\n for (const [id, a] of acc) {\n const geo = sectionGeometry(a.section, { floorBaseHeightM: a.unit.baseHeightM });\n const bottomY = a.unit.baseHeightM;\n const rakeTan = geo.rake > 0 ? Math.tan((geo.rake * Math.PI) / 180) : 0;\n // An authored override wins over inference (see `SectionObject.surfaceKind`).\n // `rakedRows` cannot manufacture relief a section does not carry, so it only\n // prevents a raked section from being flattened, never the reverse.\n const inferredFlat = geo.rake <= 0.01 && geo.height <= bottomY + 0.001;\n const kind = a.section.surfaceKind;\n const flat = kind === 'flat' ? true : kind === 'rakedRows' ? geo.rake > 0.01 ? false : inferredFlat : inferredFlat;\n\n // Resolve the structure FIRST: when it yields row levels, the rake field is\n // never consulted and building it is pure waste. Profiled at 7 % of scene\n // build on a 50k venue — `sampleRows` doing inverse-distance work for a field\n // nothing would read.\n const structure = a.hasSeats\n ? resolveSection(id, seats, a.seatIndices, a.unit.focal)\n : { sectionId: id, rows: [] as ResolvedRow[], blockCount: 0 };\n const needsRake = structure.rows.length < 2;\n const rake = needsRake\n ? buildSectionRake([...a.rows.values()].map((points) => ({ points })), a.unit.focal)\n : null;\n\n let frontU = Infinity;\n if (rake) {\n if (a.hasSeats) {\n for (const pts of a.rows.values()) {\n for (const p of pts) {\n const d = rake.depthAt(p.x, p.y);\n if (d < frontU) frontU = d;\n }\n }\n }\n if (!a.hasSeats || !Number.isFinite(frontU)) {\n frontU = Infinity;\n for (const p of a.section.outline) {\n const d = rake.depthAt(p.x, p.y);\n if (d < frontU) frontU = d;\n }\n }\n }\n const baseFloor = bottomY + FLAT_SLAB_TOP_M;\n // A level deck sits at the section's AUTHORED height, not on the floor.\n //\n // This used to be `bottomY + FLAT_SLAB_TOP_M` outright, which meant asking\n // for `surfaceKind: 'flat'` silently threw the authored `height` away: a\n // 7.4 m loge box and a 2.6 m choir terrace both rendered with every seat at\n // 0.20 m (slab top plus seat clearance). It made `flat` unusable for the\n // exact things it exists for — boxes, suites and terraces, which are genuinely\n // LEVEL platforms — so both curated venues had to fake a shallow rake instead.\n //\n // The datum is now taken from the same expression the raked path uses\n // (`levelFor`/`levelForBlockDepth` below, at zero rise), so \"flat\" and\n // \"raked at 0°\" agree by construction rather than by coincidence. A section\n // with no authored height still lands on `baseFloor` exactly as before, which\n // is why legacy height-less charts do not move: `inferredFlat` only fires when\n // `geo.height <= bottomY + 0.001`, so this max can only bite when a height was\n // explicitly authored alongside `surfaceKind: 'flat'`.\n const flatTop = Math.max(baseFloor, geo.height);\n\n /** Height for a depth ordinate measured from the venue focal (fallback path). */\n const levelFor = (depthU: number): number => {\n const depthM = Math.max(0, depthU - frontU) * METRES_PER_CHART_UNIT;\n const rise = Math.min(depthM * rakeTan, MAX_TIER_RISE_M);\n return Math.max(baseFloor, geo.height + rise);\n };\n\n /** Height for a depth measured back from a BLOCK's own front row. */\n const levelForBlockDepth = (blockDepthU: number): number => {\n const depthM = Math.max(0, blockDepthU) * METRES_PER_CHART_UNIT;\n const rise = Math.min(depthM * rakeTan, MAX_TIER_RISE_M);\n return Math.max(baseFloor, geo.height + rise);\n };\n\n // One level per row, from the resolved structure. THIS is the deck.\n //\n // Depth is measured inside the row's own BLOCK, so every block of a tier\n // starts at the tier's authored height. Measuring it from the venue focal\n // (which is what `rake.depthAt` does, and what this used to do) raised a\n // bowl's side wedges as though they were further back: sec-gall's six blocks\n // are one tier authored at 6.20 m and started at 6.35 to 10.65 m.\n // A table's deck is its own footprint, grown past its outermost seat by the\n // same clearance a ribbon carries past its end seat (MIN_REACH_U). The\n // radius is taken from where the seats ACTUALLY are, not from the table's\n // authored `radius`: `expandTable` seats a round table at `radius + 16`\n // chart units, and per-chair `dx/dy` overrides move individual seats again,\n // so the authored radius understates the ring it has to cover. On the\n // nightclub that is 50 u of real reach against an authored 34.\n const rowLevels = flat\n ? []\n : structure.rows.map((r) => ({\n pts: r.pts,\n y: levelForBlockDepth(r.blockDepth),\n depth: r.blockDepth,\n blockId: r.blockId,\n ...(tableRowIds.has(r.id)\n ? { patch: seatClusterPatch(r.pts, MIN_REACH_U) }\n : {}),\n }));\n const landingY = rowLevels.length ? rowLevels[0].y : flatTop;\n\n // Bounding circles over the rows, so the nearest-row lookup below prunes\n // instead of walking every polyline in the section. A 50k venue has sections\n // with thousands of rows and this is called per cap sample.\n // Bounded over the entry's DRAWN extent — the patch when it has one, since\n // that is the ground it actually covers and a bound taken from the seat ring\n // alone would prune away the deck at the patch's rim.\n const rowBounds = rowLevels.map((r) => {\n const ext = r.patch && r.patch.length >= 3 ? r.patch : r.pts;\n let cx = 0, cy = 0;\n for (const p of ext) { cx += p.x; cy += p.y; }\n const n = ext.length || 1;\n cx /= n; cy /= n;\n let rad = 0;\n for (const p of ext) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > rad) rad = d;\n }\n return { cx, cy, rad };\n });\n\n const deckAt = flat\n ? (): number => flatTop\n : rowLevels.length >= 2\n ? (x: number, y: number): number => {\n // The deck is the level of the row you are standing on. A step\n // function, deliberately: it is the drawn geometry, not an\n // approximation of it, so a seat and its ribbon cannot disagree.\n let best = Infinity, bestY = landingY;\n for (let i = 0; i < rowLevels.length; i++) {\n const b = rowBounds[i];\n // Admissible lower bound — cannot discard a row that would have won.\n if (Math.hypot(x - b.cx, y - b.cy) - b.rad >= best) continue;\n // Over a table, the deck is the table's patch — and a point inside\n // the patch is ON it, at distance zero. Measuring to the seat ring's\n // polyline instead would hand the middle of a table to whichever\n // ROW happened to be nearest, so the deck height and the drawn patch\n // would disagree by a whole step.\n const patch = rowLevels[i].patch;\n const d = patch && patch.length >= 3\n ? (pointInRing(patch, x, y) ? 0 : distanceToPolyline(patch, x, y))\n : distanceToPolyline(rowLevels[i].pts, x, y);\n if (d < best) { best = d; bestY = rowLevels[i].y; }\n }\n return bestY;\n }\n : (x: number, y: number): number => (rake ? levelFor(rake.depthAt(x, y)) : flatTop);\n\n // The gradient of the same expression. Height rises with radial distance\n // only, so the surface tilts purely along the outward radial direction and\n // the slope is exactly `rakeTan` — except on the flat front plateau\n // (d <= frontU), past the runaway clamp, and where the baseFloor floor wins,\n // all of which are locally horizontal.\n const UP: [number, number, number] = [0, 1, 0];\n // Every ribbon and every landing is HORIZONTAL, so the deck's normal is up\n // everywhere and the analytic-normal machinery has nothing left to correct.\n // The only non-horizontal deck surfaces are the risers, and those carry their\n // own normals from the band builder.\n const normalAt = rowLevels.length >= 2 || flat\n ? (): [number, number, number] => UP\n : (x: number, y: number): [number, number, number] => {\n if (!rake) return UP;\n const d = rake.depthAt(x, y);\n if (d <= frontU) return UP;\n const depthM = (d - frontU) * METRES_PER_CHART_UNIT;\n if (depthM * rakeTan >= MAX_TIER_RISE_M) return UP; // clamped: flat again\n if (geo.height + depthM * rakeTan <= baseFloor) return UP; // floored: flat\n const [gx, gy] = rake.gradientAt(x, y);\n if (gx === 0 && gy === 0) return UP;\n const inv = 1 / Math.hypot(rakeTan, 1);\n return [-gx * rakeTan * inv, inv, -gy * rakeTan * inv];\n };\n\n // A seat takes its own row's level DIRECTLY. Searching for the nearest row\n // would give the same answer almost always and the wrong one occasionally\n // (where two blocks interleave), and it would cost a polyline search per seat\n // on a chart that may hold 50,000 of them. Row membership is already known.\n if (!flat) {\n for (const r of structure.rows) {\n const y = levelForBlockDepth(r.blockDepth);\n for (const si of r.seatIndices) seatRowLevel[si] = y;\n }\n }\n\n // Per-row pitch: the median spacing ALONG the row (immune to the aisle, which\n // falls between rows, not inside one) capped by the gap to the nearest other\n // row, so a dot cannot grow into the row in front either.\n for (const r of structure.rows) {\n const gaps: number[] = [];\n for (let k = 1; k < r.pts.length; k++) {\n const d = Math.hypot(r.pts[k].x - r.pts[k - 1].x, r.pts[k].y - r.pts[k - 1].y);\n if (d > 1e-6) gaps.push(d);\n }\n gaps.sort((x, y) => x - y);\n const along = gaps.length ? gaps[Math.floor(gaps.length / 2)] : Infinity;\n\n // Measured against rows in the SAME BLOCK only. A row in a neighbouring\n // block runs BESIDE this one, not in front of it, and near a block boundary\n // it can pass within a hair of the probe: measured across the catalog that\n // collapsed the pitch to 0.04 mm on the opera house and 0.8 mm on the\n // esports arena, which then drove every dot there to the visibility floor.\n // This is the same mistake as sizing a seat against a neighbour across an\n // aisle, one level up.\n let across = Infinity;\n const probe = r.pts[Math.floor(r.pts.length / 2)];\n if (probe) {\n for (const other of structure.rows) {\n if (other === r || other.blockId !== r.blockId) continue;\n const d = distanceToPolyline(other.pts, probe.x, probe.y);\n if (d > 1e-6 && d < across) across = d;\n }\n }\n // Never below half the in-row spacing. Two rows can genuinely be authored\n // on top of each other (chairs around a table cross their neighbours'\n // arcs), and a seat's own row spacing is still real when that happens.\n const pitch = Math.min(along, Math.max(across, along * 0.5));\n if (Number.isFinite(pitch) && pitch > 0) {\n for (const si of r.seatIndices) seatPitch[si] = pitch;\n }\n }\n\n bySection.set(id, { sectionId: id, deckAt, normalAt, flat, bottomY, rowLevels, landingY });\n }\n\n // --- Pass 3: bake each seat's deck height from its owner's surface ---------\n for (let i = 0; i < seats.length; i++) {\n const ownerId = seatOwner[i];\n const s = seats[i];\n if (ownerId) {\n const own = seatRowLevel[i];\n seatDeck[i] = (own ?? bySection.get(ownerId)!.deckAt(s.x, s.y)) + SEAT_CLEARANCE_M;\n continue;\n }\n // Seat outside every section: fall back to its own resolved eye height, the\n // only surface information it carries.\n const eye = s.eyeHeightM;\n seatDeck[i] = Number.isFinite(eye) ? Math.max(0, (eye as number) - SEATED_EYE_HEIGHT_M) : 0;\n }\n\n return {\n bySection,\n seatOwner,\n seatDeckY: (i: number): number => seatDeck[i],\n seatPitchU: (i: number): number | undefined => seatPitch[i],\n };\n}\n","/**\n * The DOM half of venue labels: one absolutely-positioned element per visible\n * label, repositioned from projected 3D anchors as the camera moves.\n *\n * Kept out of `index.ts` so the render loop stays about rendering, and out of\n * `labels.ts` so the anchor/LOD logic stays pure and testable.\n *\n * ## Accessibility\n *\n * These are real DOM text nodes, which is the point. A screen reader can read the\n * venue's structure — zones, sections, booth names — where a GPU-drawn glyph is\n * invisible to it. The overlay itself is `aria-hidden` only for the decorative\n * pointer-events layer; the labels are a live region-free list of static text,\n * announced in document order.\n *\n * Pointer events pass straight through: a label must never eat a seat tap.\n */\n\nimport {\n DENSE_LABEL_BUDGET, cullOverlapping, focusScore, pickDenseLabels, projectToScreen,\n visibleLabelKinds, type SceneLabel,\n} from './labels';\n\n/**\n * Minimum gap before the farther label is dropped, CSS px — wide and short,\n * matching the shape of a line of text rather than a disc around it.\n */\nconst SEPARATION_X_PX = 88;\nconst SEPARATION_Y_PX = 20;\n\n/** Per-kind styling. Sizes are CSS px at a nominal viewport. */\nconst KIND_STYLE: Record<SceneLabel['kind'], { size: number; weight: string; opacity: number }> = {\n zone: { size: 15, weight: '600', opacity: 0.95 },\n section: { size: 12, weight: '500', opacity: 0.88 },\n ga: { size: 12, weight: '500', opacity: 0.88 },\n booth: { size: 11, weight: '500', opacity: 0.85 },\n annotation: { size: 11, weight: '400', opacity: 0.75 },\n // Row and seat identity are quieter than the structure they sit inside: at\n // this range the venue is already understood and the label is a detail, so it\n // must not compete with the seating it is printed over.\n row: { size: 10.5, weight: '600', opacity: 0.8 },\n seat: { size: 9.5, weight: '500', opacity: 0.72 },\n};\n\n/**\n * Per-kind declutter spacing, CSS px. Seat and row labels are short strings sat\n * on a tight grid, so the wide section-name box would throw away almost all of\n * them — a row of seat numbers needs to survive side by side.\n */\nconst DENSE_KINDS = new Set<SceneLabel['kind']>(['row', 'seat']);\n/**\n * Per-kind, because the two are different shapes of text. A seat number is 1–3\n * characters and wants to survive shoulder to shoulder along a row; a row name\n * is a word (\"ORCH-L\") and needs roughly twice the width before the next one is\n * legible. One shared value crowded the row names into each other.\n */\nconst DENSE_SEPARATION: Record<'row' | 'seat', { x: number; y: number }> = {\n row: { x: 62, y: 16 },\n // Widened from 24: at close range seat labels stopped overlapping at all, so\n // the separation had no work left to do and the budget was carrying the whole\n // load. A wider box means the few labels that ARE kept are spread across the\n // seating instead of clustering into one stack.\n seat: { x: 46, y: 18 },\n};\n\nexport interface LabelOverlayOptions {\n /** `ChartTheme.fontFamily`, when the chart authors one. */\n fontFamily?: string;\n /** Ink colour for labels that carry no authored colour. */\n ink?: string;\n}\n\nexport class LabelOverlay {\n private root: HTMLDivElement;\n private nodes = new Map<string, HTMLDivElement>();\n private labels: SceneLabel[] = [];\n private opts: LabelOverlayOptions;\n private forcedDense = false;\n\n constructor(container: HTMLElement, opts: LabelOverlayOptions = {}) {\n this.opts = opts;\n this.root = document.createElement('div');\n this.root.setAttribute('data-view3d-labels', '');\n const s = this.root.style;\n s.position = 'absolute';\n s.inset = '0';\n // Never intercept a seat tap — the canvas below owns all pointer input.\n s.pointerEvents = 'none';\n s.overflow = 'hidden';\n if (opts.fontFamily) s.fontFamily = opts.fontFamily;\n container.appendChild(this.root);\n }\n\n /**\n * Hide or show the whole overlay without disturbing which labels exist.\n *\n * The in-scene panorama sphere is GL, so it draws BEHIND every DOM label —\n * row and seat labels floated on top of a 360 photo until this existed. The\n * old DOM panorama never needed it because its opaque div covered them.\n *\n * Deliberately not `setLabels([])`: that destroys the nodes and the declutter\n * state, so closing the panorama would rebuild and re-rank the whole overlay\n * and flash. Visibility is a view concern, not a data one.\n */\n setVisible(visible: boolean): void {\n this.root.style.visibility = visible ? '' : 'hidden';\n }\n\n setLabels(labels: SceneLabel[]): void {\n this.labels = labels;\n for (const [id, node] of this.nodes) {\n if (!labels.some((l) => l.id === id)) { node.remove(); this.nodes.delete(id); }\n }\n }\n\n /** Section/row drill-in makes their dense labels explicit, regardless of the\n * whole-venue distance heuristic used during free orbit. */\n setForcedDense(enabled: boolean): void {\n this.forcedDense = enabled;\n }\n\n /**\n * Reposition every label for the current camera.\n *\n * `viewProjection` is column-major, as OGL supplies it.\n */\n update(\n viewProjection: ArrayLike<number>,\n width: number,\n height: number,\n cameraDistance: number,\n venueRadius: number,\n /**\n * Camera world position, for ranking the dense rungs by real distance.\n * Optional so callers that only need structure labels need not supply it;\n * without it the dense rungs fall back to ranking by screen centre alone.\n */\n cameraWorld?: readonly [number, number, number],\n ): void {\n if (!this.labels.length) return;\n const kinds = visibleLabelKinds(cameraDistance, venueRadius);\n if (this.forcedDense) {\n kinds.add('row');\n kinds.add('seat');\n }\n\n const candidates: Array<{\n label: SceneLabel;\n screen: ReturnType<typeof projectToScreen>;\n focus: number;\n }> = [];\n for (const label of this.labels) {\n if (!kinds.has(label.kind)) continue;\n const screen = projectToScreen(viewProjection, label.anchor, width, height);\n if (!screen.visible) continue;\n const world = cameraWorld\n ? Math.hypot(\n label.anchor[0] - cameraWorld[0],\n label.anchor[1] - cameraWorld[1],\n label.anchor[2] - cameraWorld[2],\n )\n : 1;\n candidates.push({ label, screen, focus: focusScore(screen, world, width, height) });\n }\n\n // Nearest-wins declutter, then paint. Everything not kept is hidden rather\n // than removed, so a small camera move does not thrash the DOM. Dense kinds\n // declutter against their OWN spacing and separately from the structure\n // labels, so a section name never suppresses the seat numbers under it.\n const structure = candidates.filter((c) => !DENSE_KINDS.has(c.label.kind));\n const kept = [\n ...cullOverlapping(structure, SEPARATION_X_PX, SEPARATION_Y_PX),\n // The dense rungs are BUDGETED, not merely deduplicated — see\n // DENSE_LABEL_BUDGET for why the overlap test alone gets worse the closer\n // the camera gets.\n ...(['row', 'seat'] as const).flatMap((kind) => pickDenseLabels(\n candidates.filter((c) => c.label.kind === kind),\n DENSE_SEPARATION[kind].x,\n DENSE_SEPARATION[kind].y,\n DENSE_LABEL_BUDGET[kind],\n )),\n ];\n const keptIds = new Set(kept.map((k) => k.label.id));\n\n for (const { label, screen } of kept) {\n const node = this.nodeFor(label);\n const st = node.style;\n st.display = '';\n st.transform = `translate(-50%, -50%) translate(${screen.x.toFixed(1)}px, ${screen.y.toFixed(1)}px)`;\n }\n for (const [id, node] of this.nodes) {\n if (!keptIds.has(id)) node.style.display = 'none';\n }\n }\n\n private nodeFor(label: SceneLabel): HTMLDivElement {\n let node = this.nodes.get(label.id);\n if (node) 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, plus\n * ONE more instanced draw for the near-field chairs whenever the camera is close\n * enough that any seat qualifies. That mesh is attached and detached from the\n * scene graph rather than drawn empty, so an overview frame still costs exactly\n * the three calls it always did.\n */\n\nimport { Geometry, Mesh, Program, Transform, type OGLRenderingContext } from 'ogl';\nimport { SEAT_STATES, upholsteryTone, type RGB } from '../palette';\nimport { createBackgroundProgram, createChairProgram, createSeatProgram, createSolidProgram } from './materials';\nimport { CHAIR_MAX_INSTANCES } from '../lod';\nimport { buildChairMesh } from './seatChair';\nimport type { SceneModel } from './sceneModel';\nimport { seatOccupantSeed, type DirtyRun } from './seatInstances';\n\n/**\n * Fill an iColor buffer range from the current iState values (state → colour).\n *\n * Reads the THEME's colours, not the module palette, so an organizer's brand\n * selection colour applies to the 3D seats as it already does in the picker.\n */\nfunction writeSeatColors(\n iColor: Float32Array,\n iState: Float32Array,\n start: number,\n count: number,\n states: readonly RGB[],\n /** Per-instance category colours; an AVAILABLE seat wears its own. */\n iCategory?: Float32Array,\n): void {\n for (let i = start; i < start + count; i++) {\n // State index 0 is 'available'. It is the only state that defers to the\n // category, exactly as 2D does: `paintSeat` fills a free seat with\n // `seatBaseColor(categoryKey)` and only overrides once it is held, sold or\n // selected. Those overrides carry information the category cannot, so they\n // must keep winning.\n const useCategory = iCategory && iState[i] === 0;\n const c = useCategory ? null : (states[iState[i]] ?? states[0]);\n iColor[i * 3] = c ? c[0] : iCategory![i * 3];\n iColor[i * 3 + 1] = c ? c[1] : iCategory![i * 3 + 1];\n iColor[i * 3 + 2] = c ? c[2] : iCategory![i * 3 + 2];\n }\n}\n\n// Two-triangle quad in [-1,1] (billboard base).\nconst SEAT_QUAD = new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]);\n// Fullscreen triangle.\nconst BG_TRI = new Float32Array([-1, -1, 3, -1, -1, 3]);\n\nexport interface GpuScene {\n /** Main scene (solids + seats), drawn with the camera. */\n main: Transform;\n /** Background scene, drawn first without depth. */\n background: Transform;\n seatProgram: Program;\n solidProgram: Program;\n /** Near-field chair program (shares uFocusFloor / fade-band uniforms). */\n chairProgram: Program;\n /** Shared instanced seat geometry (reused by the pick pass — no buffer copy). */\n seatGeometry: Geometry;\n /** Merged solid geometry (reused as the pick occluder). */\n solidGeometry: Geometry;\n drawCalls: number;\n /** Upload only the changed instance-state ranges (never the whole buffer). */\n uploadSeatStateRuns(runs: DirtyRun[]): void;\n /**\n * Hand the chair mesh the seat indices the near-field gather selected.\n *\n * `indices[0…count)` are instance indices into the seat cloud — the SAME\n * indices the dots and the pick pass use, which is what guarantees a chair and\n * its dot are always the same seat. Passing count 0 detaches the mesh.\n */\n setNearSeats(indices: Int32Array, count: number): void;\n /** How many chairs are currently instanced (0 = the near field is off). */\n nearSeatCount(): number;\n dispose(): void;\n}\n\nexport function buildGpuScene(gl: OGLRenderingContext, model: SceneModel): GpuScene {\n const main = new Transform();\n const background = new Transform();\n\n // --- Background ---\n const bgGeo = new Geometry(gl, { position: { size: 2, data: BG_TRI } });\n // The chart's own background, not the library's — see theme.ts.\n const bgProg = createBackgroundProgram(gl, model.theme.background.top as number[], model.theme.background.bottom as number[]);\n const bgMesh = new Mesh(gl, { geometry: bgGeo, program: bgProg });\n bgMesh.frustumCulled = false;\n bgMesh.setParent(background);\n\n // --- Solids (floor + tiers + stage + décor + GA, merged) ---\n const solidGeo = new Geometry(gl, {\n position: { size: 3, data: model.solids.position },\n normal: { size: 3, data: model.solids.normal },\n color: { size: 3, data: model.solids.color },\n floorIndex: { size: 1, data: model.solids.floor },\n });\n const solidProg = createSolidProgram(gl);\n const solidMesh = new Mesh(gl, { geometry: solidGeo, program: solidProg });\n solidMesh.frustumCulled = false;\n solidMesh.setParent(main);\n\n // --- Seats (one instanced billboard mesh) ---\n // Per-instance colour resolved CPU-side from iState (no dynamically-indexed\n // array uniform — OGL only binds an array uniform whose value is a plain\n // Array, and a dynamic LUT index is best avoided anyway).\n const seatProg = createSeatProgram(gl);\n const iColor = new Float32Array(model.seats.count * 3);\n const stateColors: RGB[] = SEAT_STATES.map((st) => model.theme.seatStates[st]);\n writeSeatColors(iColor, model.seats.iState, 0, model.seats.count, stateColors, model.seats.iCategory);\n const seatGeo = new Geometry(gl, {\n position: { size: 2, data: SEAT_QUAD },\n iOffset: { size: 3, data: model.seats.iPosition, instanced: 1 },\n iColor: { size: 3, data: iColor, instanced: 1 },\n // Per-seat world-radius ceiling: what stops distant rows merging into one\n // mass when the shader grows a dot to hold its minimum pixel size.\n iMaxRadius: { size: 1, data: model.seats.iMaxRadius, instanced: 1 },\n iPhysicalSeat: { size: 1, data: model.seats.iPhysicalSeat, instanced: 1 },\n // Accommodation ring colour; (0,0,0) means the seat carries no access type.\n iRing: { size: 3, data: model.seats.iRing, instanced: 1 },\n iFloor: { size: 1, data: model.seats.iFloor, instanced: 1 },\n });\n const seatMesh = new Mesh(gl, { geometry: seatGeo, program: seatProg });\n seatMesh.frustumCulled = false;\n if (model.seats.count > 0) seatMesh.setParent(main);\n\n const colorAttr = seatGeo.attributes.iColor;\n\n // --- Near-field chairs (one extra instanced draw, only when in range) ------\n // Fixed-capacity instance buffers: the near set is bounded by\n // CHAIR_MAX_INSTANCES whatever the venue holds, so these are allocated once at\n // ~200 KB total and only ever refilled — never grown, never reallocated per\n // frame. `instancedCount` is what varies.\n const chairBase = buildChairMesh();\n const chairProg = createChairProgram(gl);\n const CAP = CHAIR_MAX_INSTANCES;\n const cOffset = new Float32Array(CAP * 3);\n const cColor = new Float32Array(CAP * 3);\n const cRadius = new Float32Array(CAP);\n const cYaw = new Float32Array(CAP);\n const cRing = new Float32Array(CAP * 3);\n const cFloor = new Float32Array(CAP);\n const cPhysicalSeat = new Float32Array(CAP);\n // Occupancy + a stable per-person variation, in one attribute.\n //\n // Negative means the seat is EMPTY and the occupant geometry collapses. A\n // non-negative value is a per-seat hash in [0,1) that varies build and skin\n // tone, so a full row is not a rank of identical mannequins — the same trick\n // the generated panorama used on its silhouettes.\n const cSeed = new Float32Array(CAP);\n const chairGeo = new Geometry(gl, {\n position: { size: 3, data: chairBase.position },\n normal: { size: 3, data: chairBase.normal },\n part: { size: 1, data: chairBase.part },\n index: { data: chairBase.index },\n iOffset: { size: 3, data: cOffset, instanced: 1 },\n iColor: { size: 3, data: cColor, instanced: 1 },\n iRadius: { size: 1, data: cRadius, instanced: 1 },\n iYaw: { size: 1, data: cYaw, instanced: 1 },\n iRing: { size: 3, data: cRing, instanced: 1 },\n iFloor: { size: 1, data: cFloor, instanced: 1 },\n iPhysicalSeat: { size: 1, data: cPhysicalSeat, instanced: 1 },\n iSeed: { size: 1, data: cSeed, instanced: 1 },\n });\n const chairMesh = new Mesh(gl, { geometry: chairGeo, program: chairProg });\n chairMesh.frustumCulled = false;\n let nearCount = 0;\n /** The gathered set, kept so a state change can recolour it without a re-gather. */\n let nearIndices: Int32Array = new Int32Array(0);\n\n const writeChairColors = (): void => {\n const src = model.seats;\n for (let k = 0; k < nearCount; k++) {\n const i = nearIndices[k];\n // An available chair wears its category as UPHOLSTERY, not as the map's\n // full-strength tier colour. Only the category case is muted: held, sold\n // and selected are saying something the buyer needs to see from where they\n // are sitting, and quietening those would lose real information.\n const useCategory = src.iCategory && src.iState[i] === 0;\n const c = useCategory ? null : (stateColors[src.iState[i]] ?? stateColors[0]);\n if (c) {\n cColor[k * 3] = c[0];\n cColor[k * 3 + 1] = c[1];\n cColor[k * 3 + 2] = c[2];\n } else {\n const u = upholsteryTone([src.iCategory[i * 3], src.iCategory[i * 3 + 1], src.iCategory[i * 3 + 2]]);\n cColor[k * 3] = u[0];\n cColor[k * 3 + 1] = u[1];\n cColor[k * 3 + 2] = u[2];\n }\n }\n chairGeo.attributes.iColor.needsUpdate = true;\n };\n\n const scene: GpuScene = {\n main,\n background,\n seatProgram: seatProg,\n solidProgram: solidProg,\n chairProgram: chairProg,\n seatGeometry: seatGeo,\n solidGeometry: solidGeo,\n drawCalls: 3,\n setNearSeats(indices: Int32Array, count: number): void {\n const n = Math.min(count, CAP);\n nearIndices = indices;\n nearCount = n;\n if (n === 0) {\n // Detached rather than drawn with instancedCount 0: an overview frame\n // must cost the same three draw calls it did before the near field\n // existed, and a zero-instance draw is still a state change + a call.\n if (chairMesh.parent) chairMesh.setParent(null);\n chairGeo.instancedCount = 0;\n scene.drawCalls = 3;\n return;\n }\n const src = model.seats;\n for (let k = 0; k < n; k++) {\n const i = indices[k];\n cOffset[k * 3] = src.iPosition[i * 3];\n cOffset[k * 3 + 1] = src.iPosition[i * 3 + 1];\n cOffset[k * 3 + 2] = src.iPosition[i * 3 + 2];\n // The chair's OWN pitch rule, not the dot's ceiling — see iChairWidth.\n cRadius[k] = src.iChairWidth[i];\n cYaw[k] = src.iYaw[i];\n cRing[k * 3] = src.iRing[i * 3];\n cRing[k * 3 + 1] = src.iRing[i * 3 + 1];\n cRing[k * 3 + 2] = src.iRing[i * 3 + 2];\n cFloor[k] = src.iFloor[i];\n cPhysicalSeat[k] = src.iPhysicalSeat[i];\n cSeed[k] = seatOccupantSeed(src.iState[i], i);\n }\n writeChairColors();\n chairGeo.attributes.iOffset.needsUpdate = true;\n chairGeo.attributes.iRadius.needsUpdate = true;\n chairGeo.attributes.iYaw.needsUpdate = true;\n chairGeo.attributes.iRing.needsUpdate = true;\n chairGeo.attributes.iFloor.needsUpdate = true;\n chairGeo.attributes.iPhysicalSeat.needsUpdate = true;\n chairGeo.attributes.iSeed.needsUpdate = true;\n chairGeo.instancedCount = n;\n if (!chairMesh.parent) chairMesh.setParent(main);\n scene.drawCalls = 4;\n },\n nearSeatCount(): number {\n return nearCount;\n },\n uploadSeatStateRuns(runs: DirtyRun[]): void {\n if (!runs.length) return;\n // A selection or availability change has to reach the chairs too, or a\n // seat picked at close range stays available-green under the buyer. The\n // gathered set is small and bounded, so it is simply rewritten rather than\n // diffed against the runs.\n if (nearCount) writeChairColors();\n // Refresh only the changed instance colours from the (already-mutated)\n // iState, then upload just those contiguous ranges — never the whole buffer.\n // iCategory must be passed HERE too, not just on the initial fill: a seat\n // whose hold expires goes back to 'available', and without it that seat\n // would repaint flat green while every untouched neighbour kept its\n // category colour.\n for (const run of runs) {\n writeSeatColors(iColor, model.seats.iState, run.start, run.length, stateColors, model.seats.iCategory);\n }\n const buffer = colorAttr.buffer;\n if (!buffer) {\n // Not uploaded yet (no draw has happened) — full upload on next draw.\n colorAttr.needsUpdate = true;\n return;\n }\n // Direct bufferSubData: OGL's render-state boundBuffer cache is not touched\n // here, which is safe because OGL rebinds attribute buffers per draw via the\n // geometry's VAO; if a future dynamic attribute relies on the cache, rebind\n // through OGL instead. 3 floats per instance (vec3 iColor).\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n for (const run of runs) {\n const sub = iColor.subarray(run.start * 3, (run.start + run.length) * 3);\n gl.bufferSubData(gl.ARRAY_BUFFER, run.start * 3 * Float32Array.BYTES_PER_ELEMENT, sub);\n }\n },\n dispose(): void {\n // OGL geometries/programs delete their GL resources on remove().\n bgGeo.remove();\n bgProg.remove();\n solidGeo.remove();\n solidProg.remove();\n seatGeo.remove();\n seatProg.remove();\n chairGeo.remove();\n chairProg.remove();\n },\n };\n return scene;\n}\n","/**\n * Inline GLSL (WebGL2 / GLSL ES 3.00) for the three scene programs. Zero\n * textures, zero shadow maps, zero post: a procedural matcap-style hemisphere +\n * warm key + fresnel rim on solids, a soft top-lit round dot for seats, and a\n * vertical-gradient + vignette background. OGL injects the built-in matrix\n * uniforms (modelViewMatrix / projectionMatrix / normalMatrix) by name.\n */\n\nimport { Program } from 'ogl';\nimport { SEAT_DOT_RADIUS_M } from './seatInstances';\nimport { CHAIR_FULL_M, CHAIR_NONE_M, SEAT_MIN_PIXELS_NEAR } from '../lod';\nimport { BACK_BASE_M, BACK_RAKE_SLOPE } from './seatChair';\nimport type { OGLRenderingContext } from 'ogl';\n\n/**\n * The near-field weight, shared verbatim by the chair and the dot so the two\n * always sum to one at every depth and no seat can be drawn twice or not at all.\n * Mirrors `chairWeight()` in lod.ts.\n */\nconst CHAIR_WEIGHT_GLSL = /* glsl */ `\nfloat chairWeight(float depth) {\n return 1.0 - smoothstep(uChairFull, uChairNone, depth);\n}`;\n\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 float iPhysicalSeat;// 1 = chair; 0 = empty wheelchair bay\nin vec3 iRing; // accommodation ring colour; (0,0,0) = not accessible\nin float iFloor; // owning floor index\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uSeatRadius;\nuniform float uSeatScale;\nuniform float uMinPixels;\nuniform float uPixelToWorld; // (2*tan(fovY/2)) / viewportHeightPx\nuniform float uFocusFloor; // -1 = show every floor\nuniform float uChairFull; // view depth at which the chair mesh is full size\nuniform float uChairNone; // ...and at which it has scaled away entirely\nout vec2 vUv;\nout vec3 vColor;\nout float vBudget; // 1 = dot holds its minimum pixel size, <1 = it cannot\nout vec3 vRing;\nout float vDim;\nout float vDotWeight; // 1 = the dot IS this seat, 0 = the chair has taken over\nout float vPhysicalSeat;\n${CHAIR_WEIGHT_GLSL}\nvoid main() {\n vec4 mv = modelViewMatrix * vec4(iOffset, 1.0);\n float depth = max(-mv.z, 0.001);\n // Hand the seat over to the chair mesh as it comes into range. Derived from\n // this instance's OWN depth rather than from a global uniform, so a row two\n // metres away and the far side of the bowl resolve differently in the same\n // frame — which is the entire point of a ladder over a switch.\n vDotWeight = 1.0 - chairWeight(depth);\n float minR = uMinPixels * depth * uPixelToWorld; // screen-space floor\n // Grow to hold the pixel floor, but never past this seat's own pitch ceiling:\n // unbounded growth is what merges neighbouring rows into one mass at range.\n float r = min(max(uSeatRadius * uSeatScale, minR), iMaxRadius);\n // How much of the requested pixel floor the dot could actually afford. Below 1\n // it is losing legibility to distance, and the fragment stage dissolves it\n // toward the tier top rather than letting a sub-pixel dot alias and shimmer.\n vBudget = minR > 0.0 ? clamp(r / minR, 0.0, 1.0) : 1.0;\n mv.xy += position * r; // camera-facing billboard\n // Seat the dot ON the deck instead of centring it in the deck. iOffset is the\n // exact surface point, so half the billboard would otherwise sit below the cap\n // and be clipped by it — and because uMinPixels grows r with distance, no\n // constant world-space lift can prevent that at every range. Offsetting by r\n // along the screen projection of WORLD up is self-correcting: it is full at a\n // grazing view (where slicing happens) and vanishes looking straight down\n // (where the dot must stay centred on its seat).\n mv.xy += normalize(vec3(modelViewMatrix * vec4(0.0, 1.0, 0.0, 0.0))).xy * r;\n vUv = position;\n vColor = iColor;\n vRing = iRing;\n vPhysicalSeat = iPhysicalSeat;\n vDim = (uFocusFloor < -0.5 || abs(iFloor - uFocusFloor) < 0.5) ? 0.0 : 1.0;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst SEAT_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nin vec3 vColor;\nin float vBudget;\nin vec3 vRing;\nin float vDim;\nin float vDotWeight;\nin float vPhysicalSeat;\nuniform float uSeatFade; // fade toward tier colour with distance (LOD)\nuniform vec3 uFadeColor;\nout vec4 fragColor;\nvoid main() {\n // Empty wheelchair provision is a square bay, never a round chair marker.\n float d = mix(max(abs(vUv.x), abs(vUv.y)), length(vUv), vPhysicalSeat);\n if (d > 1.0) discard;\n float alpha = smoothstep(1.0, 0.72, d);\n float shade = 0.80 + 0.28 * (0.5 - vUv.y * 0.5); // subtle top-lit\n vec3 c = vColor * shade;\n c = mix(c, uFadeColor, uSeatFade);\n // Accommodation ring — the 3D echo of the coloured ring 2D draws around every\n // accessible seat. Painted INSIDE the dot's own radius rather than outside it,\n // so an accessible seat still respects the row-pitch ceiling and cannot grow\n // into its neighbour just for carrying a ring.\n float ringMask = step(0.001, dot(vRing, vRing));\n float ring = smoothstep(0.58, 0.70, d) * (1.0 - smoothstep(0.90, 1.0, d));\n c = mix(c, vRing, ring * ringMask * 0.95);\n // A dot that can no longer afford its pixel floor dissolves instead of\n // aliasing; the tier cap underneath already carries the section's category\n // tint, so the block reads as coloured seating rather than empty concrete.\n alpha *= smoothstep(0.35, 1.0, vBudget);\n // Seats on an unfocused floor recede with their structure.\n c = mix(c, uFadeColor, vDim * 0.75);\n alpha *= mix(1.0, 0.30, vDim);\n // Yield to the chair. The chair grows out of this exact point, so through the\n // band the dot is always at least as big as the chair inside it and the seat\n // never thins out to nothing in between.\n alpha *= vDotWeight;\n if (alpha <= 0.0) discard;\n fragColor = vec4(c, alpha);\n}`;\n\n// --- Near-field seat chairs ------------------------------------------------\n// One instanced draw over the 72-vertex base mesh in ./seatChair.ts, carrying\n// only the seats the CPU gathered as near (see ./nearField.ts). Lit by the same\n// rig as the solids so a chair reads as part of the room and not as a decal.\nconst CHAIR_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position; // local: x/z in units of the seat radius, y in METRES\nin vec3 normal;\nin float part; // 0 = pedestal, 1 = pad, 2 = back, 3 = body, 4 = head\nin vec3 iOffset; // per-instance world deck point (identical to the dot's)\nin vec3 iColor; // per-instance state colour\nin float iRadius; // per-instance horizontal half-width, world metres\nin float iYaw; // per-instance facing, radians (local +Z -> facing dir)\nin vec3 iRing; // accommodation ring colour; (0,0,0) = not accessible\nin float iFloor;\nin float iSeed; // <0 = seat is empty; else per-person hash in [0,1)\nin float iPhysicalSeat;// 1 = chair; 0 = empty wheelchair bay\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uChairFull;\nuniform float uChairNone;\nuniform float uFocusFloor;\nuniform float uBackRake; // metres of z per metre of rise, above uBackBase\nuniform float uBackBase; // local height at which the back starts\nout vec3 vColor;\nout vec3 vNormalWorld;\nout vec3 vNormalView;\nout vec3 vPosView;\nout float vPart;\nout float vHeight; // local height in metres, for the vertical occlusion ramp\nout vec3 vRing;\nout float vDim;\nout float vOccupant; // 1 = this vertex belongs to a person, not to the chair\nout vec3 vOccupantTint;\n${CHAIR_WEIGHT_GLSL}\nvoid main() {\n vec4 anchor = modelViewMatrix * vec4(iOffset, 1.0);\n float w = chairWeight(max(-anchor.z, 0.001));\n // 1. Local units -> world metres. Only x/z scale: narrow rows get narrow\n // chairs, but nobody gets a short one (people are the same height at every\n // seat pitch).\n vec3 p = position;\n // The occupant. Parts 3 and 4 are a person; everything below is furniture.\n //\n // One instanced mesh draws both an empty seat and a taken one, because the\n // alternative — a second geometry and a second draw call gathered per frame —\n // would double the near-field cost to show what is already known per instance.\n // An unoccupied seat collapses its person to a point at the seat, which the\n // rasteriser discards, exactly as the chair itself collapses at w=0.\n float occupant = step(2.5, part);\n float taken = step(0.0, iSeed);\n vOccupant = occupant;\n if (iPhysicalSeat < 0.5) {\n p = vec3(0.0); // sellable space, but no physical chair/person\n } else if (occupant > 0.5) {\n if (taken < 0.5) {\n p = vec3(0.0); // empty seat: no person\n } else {\n // Vary the build so a sold-out row is people rather than a rank of\n // identical mannequins: +/-6% height and +/-8% width off the hash.\n p.y *= 0.94 + 0.12 * iSeed;\n p.xz *= 0.92 + 0.16 * fract(iSeed * 7.13);\n }\n }\n p.xz *= iRadius;\n // 2. Lean the back. Done here rather than in the base mesh so the lean is a\n // real angle in METRES — baked into the mesh it would scale with the seat's\n // width and the same chair would lean 20 degrees on a wide stadium row and\n // 6 on a tight theatre one.\n // Only the BACK panel rakes. The old test (part > 1.5) meant \"the back\", and\n // now also catches the occupant — leaning a person by the panel's rule would\n // translate their head backwards by a rake measured from the panel's base.\n float rake = (part > 1.5 && part < 2.5) ? max(p.y - uBackBase, 0.0) * uBackRake : 0.0;\n p.z -= rake;\n // 3. Scale-in. At w=0 the chair is a point at the seat, under a dot at full\n // opacity — which is what makes the handover invisible. sqrt front-loads\n // the growth so the chair is already near full size while the dot is still\n // half there; see chairScale() in lod.ts.\n p *= sqrt(w);\n float c = cos(iYaw), s = sin(iYaw);\n vec3 rp = vec3(p.x * c + p.z * s, p.y, -p.x * s + p.z * c);\n // Normals under the same two transforms, in reverse and inverted-transposed.\n // The xz scale is non-uniform, so an axis-aligned normal does NOT survive it\n // unchanged; and the rake is a shear, whose normal transform adds a y term.\n // Skipping either lights the raked back as though it were still vertical.\n vec3 n = vec3(normal.x / iRadius, normal.y, normal.z / iRadius);\n if (part > 1.5 && part < 2.5) n.y += uBackRake * n.z;\n n = normalize(n);\n vec3 rn = vec3(n.x * c + n.z * s, n.y, -n.x * s + n.z * c);\n vec4 mv = modelViewMatrix * vec4(iOffset + rp, 1.0);\n vPosView = mv.xyz;\n vNormalWorld = rn;\n vNormalView = normalize(mat3(modelViewMatrix) * rn);\n vColor = iColor;\n // Occupant colour, resolved here so the fragment stage needs no extra\n // varyings beyond this one. A person is NOT painted in the seat's state\n // colour: a sold seat is red, and a hall of red people reads as a warning,\n // not an audience. Hair/clothing for the body, a warm tone for the head,\n // both varied by the same per-person hash.\n float t = fract(iSeed * 3.71);\n vec3 clothes = mix(vec3(0.13, 0.15, 0.20), vec3(0.34, 0.30, 0.36), t);\n vec3 skin = mix(vec3(0.52, 0.38, 0.29), vec3(0.86, 0.70, 0.58), fract(iSeed * 11.3));\n vOccupantTint = (part > 3.5) ? skin : clothes;\n vPart = part;\n vHeight = position.y;\n vRing = iRing;\n vDim = (uFocusFloor < -0.5 || abs(iFloor - uFocusFloor) < 0.5) ? 0.0 : 1.0;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst CHAIR_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 vColor;\nin vec3 vNormalWorld;\nin vec3 vNormalView;\nin vec3 vPosView;\nin float vPart;\nin float vHeight;\nin vec3 vRing;\nin float vDim;\nin float vOccupant;\nin vec3 vOccupantTint;\nuniform vec3 uKeyDir;\nuniform vec3 uFadeColor;\nout vec4 fragColor;\nvoid main() {\n vec3 N = normalize(vNormalWorld);\n vec3 V = normalize(-vPosView);\n // The solids' rig, unchanged, so the chairs sit in the venue's light.\n float hemi = 0.5 + 0.5 * N.y;\n float key = max(dot(N, uKeyDir), 0.0);\n vec3 fillDir = normalize(vec3(-uKeyDir.x, 0.25, -uKeyDir.z));\n float fill = max(dot(N, fillDir), 0.0);\n vec3 tint = vColor;\n if (vOccupant > 0.5) tint = vOccupantTint;\n // The accommodation ring, kept legible once the dot (which drew it) is gone:\n // an accessible seat's PEDESTAL is painted in the ring colour, so the marker\n // survives to close range instead of vanishing exactly when the buyer arrives.\n float ringMask = step(0.001, dot(vRing, vRing));\n if (vPart < 0.5 && ringMask > 0.5) tint = vRing;\n // Pad brightest, back a step below it, pedestal darkest. Three untextured\n // boxes only read as one object if they are separated tonally — with a single\n // flat colour the chair silhouettes as a crate.\n // A person is lit as a person, not as upholstery: no part shading, and less\n // of the pad's sheen, so a head does not read as a polished box.\n float partShade = vOccupant > 0.5 ? 0.95 : (vPart < 0.5 ? 0.50 : (vPart < 1.5 ? 1.10 : 0.80));\n // Cheap vertical occlusion: a chair is in a dense row, so the closer a surface\n // sits to the deck the less sky it can actually see. This is the depth cue —\n // without it the pad top, the back and the deck all resolve to the same flat\n // value and the row loses its form entirely.\n // Occupants rise above the 0.92 m chair back, so their ramp uses their own\n // height or every head would clamp to full brightness and float.\n float ao = mix(0.58, 1.0, clamp(vHeight / (vOccupant > 0.5 ? 1.30 : 0.92), 0.0, 1.0));\n vec3 base = tint * partShade * ao * (0.52 + 0.40 * hemi) + tint * key * 0.38 + tint * fill * 0.10;\n float fres = pow(1.0 - max(dot(normalize(vNormalView), V), 0.0), 3.0);\n // A brighter rim than the solids get: it picks out every chair's own edge,\n // which is what stops a block of them merging into one mass up close.\n base += vec3(0.26, 0.31, 0.38) * fres * 0.55;\n base = mix(base, uFadeColor, vDim * 0.75);\n fragColor = vec4(base, 1.0);\n}`;\n\nconst BG_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 position;\nout vec2 vUv;\nvoid main() {\n vUv = position * 0.5 + 0.5;\n gl_Position = vec4(position, 0.999, 1.0);\n}`;\n\nconst BG_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform vec3 uTop;\nuniform vec3 uBottom;\nout vec4 fragColor;\nvoid main() {\n vec3 col = mix(uBottom, uTop, vUv.y);\n vec2 c = vUv - 0.5;\n float vig = 1.0 - dot(c, c) * 0.85; // soft vignette\n fragColor = vec4(col * vig, 1.0);\n}`;\n\n// --- GPU pick pass ---------------------------------------------------------\n// Seats encode gl_InstanceID+1 as an RGB colour (no extra per-instance buffer);\n// solids write pure black + depth first so a seat occluded by a tier reads as\n// \"no hit\". Same billboard maths as the display seat program.\nconst SEAT_PICK_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec3 iOffset;\nin float iMaxRadius;\nin float iPhysicalSeat;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float uSeatRadius;\nuniform float uSeatScale;\nuniform float uMinPixels;\nuniform float uPixelToWorld;\nout vec2 vUv;\nout float vPhysicalSeat;\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 vPhysicalSeat = iPhysicalSeat;\n gl_Position = projectionMatrix * mv;\n}`;\n\nconst SEAT_PICK_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nin float vPhysicalSeat;\nflat in vec3 vPick;\nout vec4 fragColor;\nvoid main() {\n float d = mix(max(abs(vUv.x), abs(vUv.y)), length(vUv), vPhysicalSeat);\n if (d > 1.0) discard; // hit-mask matches chair/bay shape\n fragColor = vec4(vPick, 1.0);\n}`;\n\nconst PICK_DEPTH_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst PICK_DEPTH_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nout vec4 fragColor;\nvoid main() { fragColor = vec4(0.0, 0.0, 0.0, 1.0); }`;\n\nexport function createSeatPickProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: SEAT_PICK_VERT,\n fragment: SEAT_PICK_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n uniforms: {\n uSeatRadius: { value: SEAT_DOT_RADIUS_M },\n uSeatScale: { value: 1 },\n uMinPixels: { value: SEAT_MIN_PIXELS_NEAR },\n uPixelToWorld: { value: 0.002 },\n },\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: SEAT_MIN_PIXELS_NEAR },\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 uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n },\n });\n}\n\n/**\n * The near-field chair program.\n *\n * OPAQUE and depth-writing, unlike the dots. That is the reason the transition\n * is a scale-in and not a cross-fade: a translucent chair either writes depth\n * and punches its own silhouette through the chairs behind it, or does not and\n * shows its own back panel through its own seat pad. Growing an opaque chair out\n * of the seat point avoids both, and it composes correctly with the dot, which\n * still draws afterwards in the transparent pass.\n *\n * `cullFace` stays off to match the rest of the renderer — the meshes are closed\n * opaque boxes, so the depth test already suppresses the back faces' cost, and a\n * winding mistake here would silently blank the chairs rather than fail loudly.\n */\nexport function createChairProgram(gl: OGLRenderingContext): Program {\n return new Program(gl, {\n vertex: CHAIR_VERT,\n fragment: CHAIR_FRAG,\n transparent: false,\n depthTest: true,\n depthWrite: true,\n cullFace: false,\n uniforms: {\n uKeyDir: { value: new Float32Array([0.38, 0.86, 0.34]) },\n uFadeColor: { value: new Float32Array([0.32, 0.37, 0.43]) },\n uChairFull: { value: CHAIR_FULL_M },\n uChairNone: { value: CHAIR_NONE_M },\n uBackRake: { value: BACK_RAKE_SLOPE },\n uBackBase: { value: BACK_BASE_M },\n uFocusFloor: { value: -1 },\n },\n });\n}\n\nexport function createBackgroundProgram(gl: OGLRenderingContext, top: number[], bottom: number[]): Program {\n return new Program(gl, {\n vertex: BG_VERT,\n fragment: BG_FRAG,\n depthTest: false,\n depthWrite: false,\n cullFace: false,\n uniforms: {\n uTop: { value: new Float32Array(top) },\n uBottom: { value: new Float32Array(bottom) },\n },\n });\n}\n","/**\n * GPU color-pick (Slice 2). On a TAP (not hover, not drag) the seat instance\n * index is rendered as RGB into a small scissored offscreen target and a single\n * pixel is read back → O(1) regardless of seat count. Solids are drawn first as\n * black + depth so a seat occluded by a tier reads as \"no hit\".\n *\n * The pick meshes reuse the display geometry buffers (iOffset / position), so no\n * per-seat data is duplicated on the GPU.\n */\n\nimport { Geometry, Mesh, Program, RenderTarget, Transform } from 'ogl';\nimport type { Camera, OGLRenderingContext, Renderer } from 'ogl';\nimport { 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","import type { Point } from '../../core/types';\nimport { M } from '../scene/geometry';\nimport type { Vec3Arr } from './cinematicMath';\n\n/** Seated eye height above the rendered seat deck. */\nexport const SEAT_EYE_ABOVE_DECK_M = 1.02;\n\n/** The authored focal is a floor point; aim at show/stage eye level. */\nexport const FOCAL_LOOK_HEIGHT_M = 1.5;\n\n/**\n * Resolve the truthful pose for an in-scene view from a selected seat.\n *\n * The cinematic deliberately arrives behind and above this pose. Entering 360\n * is the point at which the camera must move to the buyer's actual seated eye,\n * while still looking at the focal resolved for that seat's zone/floor.\n */\nexport function seatViewPose(\n seatDeckWorld: Vec3Arr,\n focalPoint: Point,\n floorBaseHeightM = 0,\n): { eye: Vec3Arr; focal: Vec3Arr } {\n return {\n eye: [seatDeckWorld[0], seatDeckWorld[1] + SEAT_EYE_ABOVE_DECK_M, seatDeckWorld[2]],\n focal: [\n focalPoint.x * M,\n floorBaseHeightM + FOCAL_LOOK_HEIGHT_M,\n focalPoint.y * M,\n ],\n };\n}\n","/**\n * The equirectangular panorama, as real geometry inside the venue scene.\n *\n * What this replaces, and why it had to change:\n *\n * `crossfade/panorama.ts` draws the 360 as a DOM div with `background-repeat:\n * repeat-x`, panned by hand and windowed to a 70° vertical slice. To show it,\n * `openPanorama` calls `loop.stop()` — it FREEZES the WebGL venue and fades a\n * flat image over the corpse. So the immersive payoff of the whole 3D flight is\n * the moment the product stops using its renderer.\n *\n * A `repeat-x` background is also not a sphere. It is a cylinder at best: pan is\n * linear in longitude (correct), but pitch is a vertical translation of a flat\n * bitmap, so verticals bow and the image stretches away from the horizon. A real\n * 360 photo looks right in the 2D popover — which uses an actual sphere via\n * Photo Sphere Viewer — and subtly wrong here, on the surface the marketing\n * story leads with.\n *\n * The fix is not to import Photo Sphere Viewer. That is Three.js-based, and this\n * chunk already owns a WebGL stack (OGL) and is deliberately kept at ~71 KB\n * gzipped because it loads only when a buyer opens 3D. Pulling in a second\n * renderer to draw one textured sphere would roughly triple it.\n *\n * So: a unit sphere, inward-facing, sampled equirectangularly, drawn by the\n * scene's own renderer with the scene's own camera. The panorama stops being an\n * overlay and becomes somewhere the camera can be, which is also what makes the\n * fly-to-seat handoff a continuous move rather than a cut between two different\n * rendering technologies.\n *\n * Everything below the `mount` function is pure and unit-tested; the GL parts\n * are a thin wrapper over it.\n */\n\nimport { Geometry, Mesh, Program, Texture } from 'ogl';\nimport type { OGLRenderingContext } from 'ogl';\n\n/**\n * Longitude/latitude tessellation of the sphere.\n *\n * The sphere is viewed from its exact centre, so silhouette smoothness never\n * matters — only how much the equirect sampling is distorted by interpolating\n * UVs across a triangle. 64×32 keeps the worst-case angular error under a third\n * of a degree at the horizon, which is far below a pixel at any sane FOV, for\n * 4,096 triangles. Going finer costs vertices to fix an error nothing can see.\n */\nexport const PANO_SEGMENTS_LON = 64;\nexport const PANO_SEGMENTS_LAT = 32;\n\n/**\n * Radius in metres. Arbitrary in principle — the camera sits at the centre and\n * an equirect sphere has no parallax — but it must be large enough to enclose\n * the near clip plane and small enough to stay inside the far plane, or the\n * panorama clips against its own geometry. 500 m clears both for every venue in\n * the catalog, including the 50k-seat stadium.\n */\nexport const PANO_RADIUS_M = 500;\n\nexport interface SphereVertexData {\n position: Float32Array;\n uv: Float32Array;\n index: Uint16Array;\n}\n\n/**\n * A UV sphere with INWARD-facing triangles and equirectangular UVs.\n *\n * Inward-facing is done by winding, not by disabling culling: the scene draws\n * with back-face culling on, and turning it off for one mesh would leak that\n * state onto everything drawn after it in the same pass. Reversing the triangle\n * winding makes the inside surface the front face, so the sphere obeys exactly\n * the same pipeline state as the venue.\n *\n * Mapping, matching `generatePanorama.ts` and the 2D viewer so one image reads\n * identically in all three:\n * u = 0.5 + yaw/360 (u 0.5 is yaw 0, the direction the camera faces)\n * v = (90 - pitch)/180 (v 0 is the top of the image)\n *\n * The seam at u=0/1 is duplicated rather than wrapped — a shared vertex would\n * have to carry both u=0 and u=1 and would interpolate the whole image backwards\n * across that one column of triangles.\n */\nexport function buildPanoSphere(\n segmentsLon: number = PANO_SEGMENTS_LON,\n segmentsLat: number = PANO_SEGMENTS_LAT,\n radius: number = PANO_RADIUS_M,\n): SphereVertexData {\n const lonCount = segmentsLon + 1; // +1 duplicates the seam column\n const latCount = segmentsLat + 1; // +1 closes both poles\n const vertexCount = lonCount * latCount;\n const position = new Float32Array(vertexCount * 3);\n const uv = new Float32Array(vertexCount * 2);\n\n for (let iLat = 0; iLat < latCount; iLat++) {\n // v runs 0 (top) → 1 (bottom); polar angle 0 at +Y.\n const v = iLat / segmentsLat;\n const polar = v * Math.PI;\n const sinPolar = Math.sin(polar);\n const cosPolar = Math.cos(polar);\n for (let iLon = 0; iLon < lonCount; iLon++) {\n const u = iLon / segmentsLon;\n // u 0.5 must land on yaw 0. The scene's yaw 0 faces -Z (the camera looks\n // down -Z by default), so offset the azimuth by half a turn.\n const azimuth = (u - 0.5) * Math.PI * 2;\n const i = iLat * lonCount + iLon;\n position[i * 3] = radius * sinPolar * Math.sin(azimuth);\n position[i * 3 + 1] = radius * cosPolar;\n position[i * 3 + 2] = -radius * sinPolar * Math.cos(azimuth);\n uv[i * 2] = u;\n uv[i * 2 + 1] = v;\n }\n }\n\n // Two triangles per quad, wound so the INSIDE is the front face.\n const index = new Uint16Array(segmentsLon * segmentsLat * 6);\n let w = 0;\n for (let iLat = 0; iLat < segmentsLat; iLat++) {\n for (let iLon = 0; iLon < segmentsLon; iLon++) {\n const a = iLat * lonCount + iLon;\n const b = a + lonCount;\n index[w++] = a; index[w++] = b; index[w++] = a + 1;\n index[w++] = a + 1; index[w++] = b; index[w++] = b + 1;\n }\n }\n return { position, uv, index };\n}\n\n/**\n * Direction (unit vector, scene axes) the camera looks along for a given\n * bearing and pitch in degrees. Bearing 0 faces the focal/stage, matching\n * `SeatView.initialBearingDeg`; positive pitch looks up.\n *\n * Exported because the transition from the flight has to hand the sphere the\n * camera's final yaw, and a panorama that opens on a different bearing than the\n * flight ended on reads as a cut.\n */\nexport function bearingPitchToDirection(bearingDeg: number, pitchDeg: number): [number, number, number] {\n const yaw = (bearingDeg * Math.PI) / 180;\n const pitch = (pitchDeg * Math.PI) / 180;\n const cosPitch = Math.cos(pitch);\n return [cosPitch * Math.sin(yaw), Math.sin(pitch), -cosPitch * Math.cos(yaw)];\n}\n\nconst PANO_VERT = /* glsl */ `#version 300 es\nprecision highp float;\nin vec3 position;\nin vec2 uv;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nout vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst PANO_FRAG = /* glsl */ `#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform sampler2D uPano;\nuniform float uOpacity;\nout vec4 fragColor;\nvoid main() {\n vec3 rgb = texture(uPano, vUv).rgb;\n fragColor = vec4(rgb, uOpacity);\n}`;\n\nexport interface PanoSphereHandle {\n mesh: Mesh;\n /** Cross-fade control: 0 hides the sphere, 1 fully replaces the venue. */\n setOpacity(value: number): void;\n /** Replace the decoded source on the existing GPU texture. */\n setImage(image: HTMLImageElement | HTMLCanvasElement): void;\n dispose(): void;\n}\n\n/**\n * Build the textured sphere mesh. The caller owns adding it to a scene graph and\n * driving `setOpacity` — this module deliberately knows nothing about the flight\n * or the overlay chrome.\n *\n * `depthTest: false` and `depthWrite: false`: the sphere encloses the entire\n * venue, so with depth on it would be occluded by every piece of geometry\n * between the camera and its far wall. It is a backdrop that replaces the view,\n * not a solid in it — so it is drawn without consulting depth, and the caller\n * draws it after the venue when fading in.\n */\nexport function createPanoSphere(\n gl: OGLRenderingContext,\n // Narrower than `TexImageSource` on purpose: the panorama always arrives as a\n // decoded <img> (a URL or the generator's data URL) or a canvas. OGL's texture\n // type does not accept ImageBitmap, and widening here would only move the\n // failure to runtime.\n image: HTMLImageElement | HTMLCanvasElement,\n): PanoSphereHandle {\n const { position, uv, index } = buildPanoSphere();\n const geometry = new Geometry(gl, {\n position: { size: 3, data: position },\n uv: { size: 2, data: uv },\n index: { data: index },\n });\n const texture = new Texture(gl, {\n image,\n // The seam column is duplicated in geometry, so CLAMP is correct and avoids\n // a wrapped bilinear tap bleeding the far edge of the image into it.\n wrapS: gl.CLAMP_TO_EDGE,\n wrapT: gl.CLAMP_TO_EDGE,\n generateMipmaps: true,\n // MUST be false, and this is the one line that decides whether the whole\n // panorama is upside down.\n //\n // OGL defaults `flipY` to true for 2D textures, which is right for the usual\n // case: a UV of 0 means the BOTTOM of a quad, an image's first row is its\n // TOP, and flipping on upload reconciles the two. This sphere is the other\n // case. Its `v` is authored in IMAGE space — v=0 is the top of the equirect\n // and maps to the top of the sphere (`buildPanoSphere`, and the mapping\n // `generatePanorama` and the 2D viewer both use). Flipping on upload as well\n // applies the correction twice: the stadium pitch renders on the ceiling and\n // the audience sits overhead.\n //\n // The geometry tests state the v mapping and pass either way — they never\n // touch GL — so this is not something a unit test can defend. It was caught\n // by looking at a stadium.\n flipY: false,\n });\n const program = new Program(gl, {\n vertex: PANO_VERT,\n fragment: PANO_FRAG,\n uniforms: { uPano: { value: texture }, uOpacity: { value: 0 } },\n transparent: true,\n depthTest: false,\n depthWrite: false,\n });\n const mesh = new Mesh(gl, { geometry, program });\n return {\n mesh,\n setOpacity(value: number): void {\n program.uniforms.uOpacity.value = Math.max(0, Math.min(1, value));\n },\n setImage(nextImage: HTMLImageElement | HTMLCanvasElement): void {\n texture.image = nextImage;\n texture.needsUpdate = true;\n },\n dispose(): void {\n mesh.setParent(null);\n geometry.remove();\n if (texture.texture) gl.deleteTexture(texture.texture);\n program.remove();\n },\n };\n}\n","/**\n * The panorama, mounted as scene geometry instead of as a div over a corpse.\n *\n * `panorama.ts` (still here, still used as the fallback) freezes the render loop\n * and fades a `repeat-x` bitmap over the last WebGL frame. This mounts the same\n * image on the inward-facing sphere from `scene/panoSphere.ts`, leaves the loop\n * running, and moves the camera instead of a background-position. Three things\n * follow from that, and they are the reason for the rewrite:\n *\n * - It is a real sphere. Pitch is a camera rotation, not a vertical translation\n * of a flat bitmap, so verticals stay vertical and nothing stretches away\n * from the horizon.\n * - The venue is still being drawn underneath, so the arrival is a CROSS-FADE\n * between two views of the same place from the same camera, not a cut\n * between two rendering technologies.\n * - Escape/close costs nothing to reverse: the camera is already where it needs\n * to be, because it never went anywhere.\n *\n * The DOM chrome (close button, hint, dialog semantics) is kept deliberately —\n * it is the accessible surface, and none of it belongs in GL.\n */\n\nimport { createPanoSphere, bearingPitchToDirection, type PanoSphereHandle } from '../scene/panoSphere';\nimport type { PanoramaHandle, PanoramaOptions, SeatView } from './panorama';\nimport { clampPanoramaFov, MAX_PITCH_DEG, VFOV_DEG } from './panorama';\nimport type { OGLRenderingContext, Transform, Camera } from 'ogl';\nimport {\n browserPanoramaConstraints,\n loadPanoramaImage,\n planPanoramaDelivery,\n schedulePanoramaUpgrade,\n} from '../../view/panoramaDelivery';\n\n/**\n * Degrees of look per pixel dragged. Matched to the DOM viewer's feel: a drag\n * across the full width of a 1200 px viewport turns roughly 180°.\n */\nconst DEG_PER_PX = 0.15;\n\nexport interface PanoramaSceneDeps {\n gl: OGLRenderingContext;\n /** The scene the venue is drawn into; the sphere joins it and leaves on close. */\n scene: Transform;\n camera: Camera;\n /** Ask for another frame — the loop is render-on-demand, not free-running. */\n requestRender(): void;\n /**\n * Exact world-space eye point for scene mode. The camera is moved here while\n * the buyer looks around, then restored to the cinematic arrival on close.\n */\n cameraOriginWorld?: readonly [number, number, number];\n /**\n * The venue focal (stage/pitch centre) in world metres.\n *\n * Only scene mode needs it, and it genuinely cannot do without it. A generated\n * panorama is DRAWN with the stage at bearing 0, so opening at bearing 0 faces\n * the stage for free. The real scene has no such convention — bearing 0 is\n * world −Z, which points wherever the venue happened to be authored — so\n * opening there stares at whatever is behind the buyer. The bearing is derived\n * from the seat's own line to the focal instead.\n */\n focalWorld?: readonly [number, number, number];\n}\n\n/**\n * Mount the panorama sphere. Returns null if the image cannot be decoded, so the\n * caller can fall back to the DOM viewer rather than stranding the buyer on a\n * black screen — a 360 that fails to load must never be worse than no 360.\n */\nexport async function mountPanoramaSphere(\n container: HTMLElement,\n // NULL means \"look around the real scene\": no image, no sphere, just the\n // camera at the seat. That is the right answer for a generated panorama,\n // which is only ever a low-resolution picture of the geometry already on\n // screen — see `SeatView.generated`. Everything else below (framing,\n // look-around, Escape, restoring the camera) is identical, which is why the\n // two modes share one code path rather than diverging into two viewers that\n // drift apart.\n view: SeatView | null,\n deps: PanoramaSceneDeps,\n opts: PanoramaOptions = {},\n): Promise<PanoramaHandle | null> {\n const fadeMs = opts.fadeMs ?? 400;\n let bearing = view?.initialBearingDeg ?? 0;\n let pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, view?.initialPitchDeg ?? 0));\n let viewFov = VFOV_DEG;\n let disposed = false;\n const loadAbort = new AbortController();\n const abortFromCaller = (): void => loadAbort.abort();\n opts.signal?.addEventListener('abort', abortFromCaller, { once: true });\n let cancelUpgrade = (): void => {};\n\n // Scene mode is the real venue viewed from the selected seat, not from the\n // deliberately flattering cinematic arrival behind and above it. Preserve\n // that arrival so closing 360 returns to the exact pre-view camera.\n const priorPosition: [number, number, number] = [\n deps.camera.position.x,\n deps.camera.position.y,\n deps.camera.position.z,\n ];\n if (!view && deps.cameraOriginWorld) {\n deps.camera.position.set(\n deps.cameraOriginWorld[0],\n deps.cameraOriginWorld[1],\n deps.cameraOriginWorld[2],\n );\n }\n\n // Scene mode opens on the stage, derived from where this seat actually is.\n // The pitch matters as much as the yaw: from a balcony the stage is well below\n // eye level, and opening level puts the whole venue in the bottom of the frame\n // with dead air above it.\n if (!view && deps.focalWorld) {\n const p = deps.camera.position;\n const dx = deps.focalWorld[0] - p.x;\n const dy = deps.focalWorld[1] - p.y;\n const dz = deps.focalWorld[2] - p.z;\n const flat = Math.hypot(dx, dz);\n if (flat > 1e-3) {\n bearing = (Math.atan2(dx, -dz) * 180) / Math.PI;\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, (Math.atan2(dy, flat) * 180) / Math.PI));\n }\n }\n\n let sphere: PanoSphereHandle | null = null;\n if (view) {\n const maxTextureSize = deps.gl.getParameter(deps.gl.MAX_TEXTURE_SIZE) as number;\n const delivery = planPanoramaDelivery(view, browserPanoramaConstraints(maxTextureSize));\n let image: HTMLImageElement;\n try {\n image = await loadPanoramaImage(delivery.initialUrl, loadAbort.signal);\n } catch {\n opts.signal?.removeEventListener('abort', abortFromCaller);\n return null;\n }\n try {\n sphere = createPanoSphere(deps.gl, image);\n } catch {\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n return null;\n }\n // Drawn last and without depth, so it composites over the venue during the\n // fade instead of being occluded by whatever sits between the camera and\n // the far wall of the sphere.\n sphere.mesh.renderOrder = 999;\n sphere.mesh.setParent(deps.scene);\n if (delivery.upgradeUrl) {\n cancelUpgrade = schedulePanoramaUpgrade(() => {\n void loadPanoramaImage(delivery.upgradeUrl!, loadAbort.signal).then((full) => {\n if (disposed || loadAbort.signal.aborted) return;\n sphere?.setImage(full);\n deps.requestRender();\n }).catch(() => { /* retain the decoded preview */ });\n });\n }\n }\n\n // A cinematic flight ENDS pushed in — it narrows the FOV to close the last of\n // the distance to the seat, and `resumeAfterFlight` is what normally restores\n // it. The DOM viewer never noticed because it never used the camera; drawing\n // the panorama through that same camera inherits the zoom and magnifies the\n // equirect to a few unreadable degrees of arc.\n //\n // So the sphere sets its own field of view — the same 70° the DOM viewer\n // windowed to, which is what makes the two paths frame a panorama alike — and\n // puts back whatever it found on the way out, so closing hands the flight's\n // camera back exactly as it was.\n const priorFov = deps.camera.fov;\n deps.camera.perspective({ fov: viewFov, aspect: deps.camera.aspect });\n\n // Looking around REPLACES the camera's orientation, and orbit only re-aims on\n // a frame it considers \"moving\". So closing the panorama used to hand back a\n // camera still pointing wherever the buyer had dragged, at a venue that was no\n // longer in front of it — a black screen until the first orbit drag snapped it\n // home. Capture the orientation on the way in and put it back on the way out,\n // exactly as the FOV is handled.\n const priorQuat = deps.camera.quaternion.slice() as unknown as number[];\n\n /** Keep the camera at the sphere's exact centre — an equirect has no parallax,\n * and any offset would shear the image. */\n const aim = (): void => {\n const p = deps.camera.position;\n sphere?.mesh.position.set(p.x, p.y, p.z);\n const [dx, dy, dz] = bearingPitchToDirection(bearing, pitch);\n deps.camera.lookAt([p.x + dx, p.y + dy, p.z + dz]);\n deps.requestRender();\n };\n aim();\n\n // --- chrome -------------------------------------------------------------\n const root = document.createElement('div');\n const priorFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-label', opts.seatLabel ? `View from ${opts.seatLabel}` : 'View from seat');\n root.tabIndex = -1;\n Object.assign(root.style, {\n position: 'absolute', inset: '0', zIndex: '10',\n cursor: 'grab', touchAction: 'none',\n } as CSSStyleDeclaration);\n\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.textContent = '✕';\n Object.assign(closeBtn.style, {\n position: 'absolute', top: '12px', right: '12px', zIndex: '2',\n width: '44px', height: '44px', borderRadius: '999px', cursor: 'pointer',\n border: '1px solid rgba(255,255,255,0.25)', background: 'rgba(8,12,18,0.6)',\n color: '#e6edf3', fontSize: '15px', lineHeight: '1',\n } as CSSStyleDeclaration);\n root.appendChild(closeBtn);\n\n if (opts.disclosure) {\n const disclosure = document.createElement('div');\n disclosure.textContent = `360° panorama · ${opts.disclosure}`;\n Object.assign(disclosure.style, {\n position: 'absolute', top: '12px', left: '12px', zIndex: '2',\n maxWidth: 'calc(100% - 88px)', padding: '8px 11px', borderRadius: '999px',\n overflow: 'hidden', color: '#dce6f8', background: 'rgba(8,12,18,0.68)',\n font: '600 11px/1.2 ui-sans-serif, system-ui, sans-serif', textOverflow: 'ellipsis',\n whiteSpace: 'nowrap', pointerEvents: 'none', backdropFilter: 'blur(6px)',\n } as CSSStyleDeclaration);\n root.appendChild(disclosure);\n }\n\n const hint = document.createElement('div');\n hint.textContent = 'Drag to look · pinch or scroll to zoom · Esc to close';\n Object.assign(hint.style, {\n position: 'absolute', bottom: '12px', left: '0', right: '0', textAlign: 'center',\n color: 'rgba(230,237,243,0.7)', font: '12px ui-sans-serif, system-ui, sans-serif',\n pointerEvents: 'none',\n } as CSSStyleDeclaration);\n root.appendChild(hint);\n container.appendChild(root);\n closeBtn.focus({ preventScroll: true });\n\n // --- look-around --------------------------------------------------------\n let dragging = false;\n let lastX = 0;\n let lastY = 0;\n let pinchDistance = 0;\n const pointers = new Map<number, { x: number; y: number }>();\n const distanceBetweenPointers = (): number => {\n const [a, b] = [...pointers.values()];\n return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;\n };\n const setViewFov = (nextFov: number): void => {\n const clamped = clampPanoramaFov(nextFov);\n if (clamped === viewFov) return;\n viewFov = clamped;\n deps.camera.perspective({ fov: viewFov, aspect: deps.camera.aspect });\n aim();\n };\n const onDown = (e: PointerEvent): void => {\n if (e.target === closeBtn) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n dragging = pointers.size === 1;\n lastX = e.clientX; lastY = e.clientY;\n if (pointers.size === 2) pinchDistance = distanceBetweenPointers();\n root.setPointerCapture?.(e.pointerId);\n root.style.cursor = 'grabbing';\n };\n const onMove = (e: PointerEvent): void => {\n if (!pointers.has(e.pointerId)) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (pointers.size >= 2) {\n const nextDistance = distanceBetweenPointers();\n if (pinchDistance > 0 && nextDistance > 0) setViewFov(viewFov * (pinchDistance / nextDistance));\n pinchDistance = nextDistance;\n dragging = false;\n return;\n }\n if (!dragging) return;\n // Horizontal follows the HAND: drag right, turn right. The vertical below\n // stays grab-style (drag down to look up), which is the mix the owner asked\n // for after using it — and worth stating because it looks inconsistent in\n // the source and is not. The DOM viewer this replaces dragged the image on\n // both axes; the yaw was the one that read backwards in a real venue.\n bearing += (e.clientX - lastX) * DEG_PER_PX;\n // Clamped to the same ±35° the DOM viewer allowed. A generated panorama has\n // nothing meaningful at the poles, and letting the buyer tumble to straight\n // up reads as a broken control rather than a feature.\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, pitch + (e.clientY - lastY) * DEG_PER_PX));\n lastX = e.clientX; lastY = e.clientY;\n aim();\n };\n const onUp = (e: PointerEvent): void => {\n pointers.delete(e.pointerId);\n pinchDistance = pointers.size === 2 ? distanceBetweenPointers() : 0;\n const remaining = pointers.values().next().value as { x: number; y: number } | undefined;\n dragging = pointers.size === 1;\n if (remaining) { lastX = remaining.x; lastY = remaining.y; }\n else root.style.cursor = 'grab';\n try { root.releasePointerCapture?.(e.pointerId); } catch { /* pointer already released */ }\n };\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n setViewFov(viewFov + e.deltaY * 0.04);\n };\n root.addEventListener('pointerdown', onDown);\n root.addEventListener('pointermove', onMove);\n root.addEventListener('pointerup', onUp);\n root.addEventListener('pointercancel', onUp);\n root.addEventListener('wheel', onWheel, { passive: false });\n\n // Escape closes. On `window`, not on `root`, because the hint promises it\n // unconditionally and a div only receives keys while focused — the DOM viewer\n // this replaces bound it the same way, and dropping it made the hint lie.\n // `stopPropagation` so the host page's own Escape handling (closing a modal,\n // leaving 3D) does not also fire on the same press.\n const onKey = (e: KeyboardEvent): void => {\n if (disposed) return;\n if (e.key === 'Tab') {\n e.preventDefault();\n closeBtn.focus({ preventScroll: true });\n return;\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n handle.close();\n return;\n }\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n bearing += e.key === 'ArrowLeft' ? -4 : 4;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n pitch = Math.max(-MAX_PITCH_DEG, Math.min(MAX_PITCH_DEG, pitch + (e.key === 'ArrowUp' ? 4 : -4)));\n } else if (e.key === '+' || e.key === '=') {\n setViewFov(viewFov - 5);\n e.preventDefault();\n return;\n } else if (e.key === '-') {\n setViewFov(viewFov + 5);\n e.preventDefault();\n return;\n } else {\n return;\n }\n e.preventDefault();\n aim();\n };\n window.addEventListener('keydown', onKey);\n\n // --- fade ---------------------------------------------------------------\n let raf = 0;\n const fadeTo = (target: number, done?: () => void): void => {\n // Scene mode has nothing to fade — the buyer is already looking at the\n // venue — so the transition is the FOV widening, not a dissolve.\n if (!sphere) { done?.(); return; }\n const from = target === 1 ? 0 : 1;\n const start = performance.now();\n const step = (): void => {\n if (disposed) return;\n const t = fadeMs <= 0 ? 1 : Math.min(1, (performance.now() - start) / fadeMs);\n sphere?.setOpacity(from + (target - from) * t);\n deps.requestRender();\n if (t < 1) raf = requestAnimationFrame(step);\n else done?.();\n };\n raf = requestAnimationFrame(step);\n };\n fadeTo(1);\n\n const teardown = (): void => {\n if (disposed) return;\n disposed = true;\n cancelUpgrade();\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n cancelAnimationFrame(raf);\n root.removeEventListener('pointerdown', onDown);\n root.removeEventListener('pointermove', onMove);\n root.removeEventListener('pointerup', onUp);\n root.removeEventListener('pointercancel', onUp);\n root.removeEventListener('wheel', onWheel);\n window.removeEventListener('keydown', onKey);\n root.remove();\n if (priorFocus?.isConnected) priorFocus.focus({ preventScroll: true });\n sphere?.dispose();\n deps.camera.position.set(priorPosition[0], priorPosition[1], priorPosition[2]);\n deps.camera.perspective({ fov: priorFov, aspect: deps.camera.aspect });\n deps.camera.quaternion.set(priorQuat[0], priorQuat[1], priorQuat[2], priorQuat[3]);\n deps.requestRender();\n };\n\n const handle: PanoramaHandle = {\n close(): void {\n if (disposed) return;\n const finish = (): void => { teardown(); opts.onClose?.(); };\n cancelAnimationFrame(raf);\n fadeTo(0, finish);\n },\n dispose: teardown,\n };\n closeBtn.addEventListener('click', () => handle.close());\n return handle;\n}\n","/**\n * view3d analytics — a tiny, decoupled event emitter for the venue view. The\n * caller (app/harness) supplies `onAnalytics`; this class owns the per-mount\n * state (first-orbit latch, panorama dwell timing) and, crucially, wraps EVERY\n * callback invocation in try/catch so a throwing analytics sink can never break\n * rendering. No DOM, no GL — unit-testable in isolation.\n */\n\nexport type Analytics3DCallback = (event: string, props?: Record<string, unknown>) => void;\n\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, buildMs?: number): void {\n this.emit('3d_opened', {\n seats,\n hasHeights,\n ...(buildMs === undefined ? {} : { buildMs: Math.round(buildMs) }),\n });\n }\n\n /** First user-driven orbit/dolly per mount only (the intro ease is not user\n * input, so callers must gate this on real pointer/wheel gestures). */\n orbitEngaged(): void {\n if (this.orbitLatched) return;\n this.orbitLatched = true;\n this.emit('3d_orbit_engaged');\n }\n\n seatPicked(seatId: string, sectionId: string | undefined): void {\n this.emit('3d_seat_picked', { seatId, sectionId });\n }\n\n cinematicPlayed(durationMs: number): void {\n this.emit('3d_cinematic_played', { durationMs, reducedMotion: false });\n }\n\n cinematicSkipped(): void {\n this.emit('3d_cinematic_skipped', { reducedMotion: true });\n }\n\n cinematicCancelled(): void {\n this.emit('3d_cinematic_cancelled');\n }\n\n panoramaOpened(): void {\n this.panoramaOpenedAt = now();\n this.emit('3d_panorama_opened');\n }\n\n panoramaClosed(): void {\n const viewMs = this.panoramaOpenedAt ? Math.round(now() - this.panoramaOpenedAt) : 0;\n this.panoramaOpenedAt = 0;\n this.emit('3d_panorama_closed', { viewMs });\n }\n\n panoramaFailed(seatId: string, phase: 'source' | 'mount'): void {\n this.emit('3d_panorama_failed', { seatId, phase });\n }\n\n panoramaFallback(seatId: string): void {\n this.emit('3d_panorama_fallback', { seatId });\n }\n\n contextLost(): void {\n this.emit('3d_context_lost');\n }\n\n contextRestored(): void {\n this.emit('3d_context_restored');\n }\n}\n","/**\n * Establish a positioning context only when the host is genuinely static.\n *\n * Buyer overlays are positioned by stylesheet rules. Checking only the inline\n * style would overwrite `position:absolute` and collapse the 3D canvas.\n */\nexport function establishPositioningContext(container: HTMLElement): () => void {\n const originalInline = container.style.position;\n if (getComputedStyle(container).position !== 'static') return () => {};\n\n container.style.position = 'relative';\n return () => {\n // Preserve any host change made while the renderer was mounted.\n if (container.style.position === 'relative') container.style.position = originalInline;\n };\n}\n","import type { ChartDoc, ExpandedSeat } from '../core/types';\nimport { buildSceneModel, type SceneModel } from './scene/sceneModel';\n\nexport interface PreparedVenue3D {\n model: SceneModel;\n buildMs: number;\n source: 'worker' | 'main';\n}\n\nexport interface PrepareVenue3DInput {\n doc: ChartDoc;\n seats: ExpandedSeat[];\n eventConfigurationId?: string;\n}\n\nfunction prepareOnMain(input: PrepareVenue3DInput): PreparedVenue3D {\n const started = performance.now();\n const model = buildSceneModel(input);\n return { model, buildMs: performance.now() - started, source: 'main' };\n}\n\n/**\n * Build the CPU-heavy merged mesh and seat buffers off the UI thread. Worker\n * failure is non-fatal: old browsers and restrictive hosts fall back to the\n * exact same pure compiler on the main thread.\n */\nexport function prepareVenue3D(input: PrepareVenue3DInput): Promise<PreparedVenue3D> {\n if (typeof Worker === 'undefined') return Promise.resolve(prepareOnMain(input));\n\n return new Promise((resolve) => {\n const worker = new Worker(new URL('./scene/scene.worker.ts', import.meta.url), { type: 'module' });\n let settled = false;\n const finish = (result: PreparedVenue3D): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n worker.terminate();\n resolve(result);\n };\n const fallback = (): void => finish(prepareOnMain(input));\n const timeout = window.setTimeout(fallback, 15_000);\n worker.onerror = fallback;\n worker.onmessage = (event: MessageEvent<{ model: SceneModel; buildMs: number }>) => {\n finish({ model: event.data.model, buildMs: event.data.buildMs, source: 'worker' });\n };\n worker.postMessage(input);\n });\n}\n"],"mappings":"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","names":["Quat","Vec3","norm","now","scale","scale","polygonClipping","polygonClipping","earcut","out","polygonClipping","poly","earcut","belowY","pointInRing","polygonClipping","sub","Mesh","Transform","Transform","Mesh","Vec3","Vec3","now","Geometry","Mesh","Program","DEG","moved","Vec3","Quat"]}