@seatlayer/core 0.48.2 → 0.50.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.
@@ -7,12 +7,13 @@ import {
7
7
 
8
8
  // src/view3d/crossfade/panorama.ts
9
9
  function seatViewDisclosure(view) {
10
- const coverage = view.generated ? "Live 3D \xB7 exact seat-eye" : view.coverage === "exact-seat" ? "Exact seat photo" : view.coverage === "row-representative" ? "Representative row view" : view.coverage === "section-representative" ? "Representative section view" : view.coverage === "venue-representative" ? "Representative venue view" : "Venue photo";
10
+ const demo = view.mediaKind === "demo-render";
11
+ const coverage = view.generated || view.mediaKind === "model" ? "Live 3D \xB7 chart-derived seat-eye" : demo ? view.coverage === "exact-seat" ? "AI-generated exact-seat demo" : view.coverage === "row-representative" ? "AI-generated representative row demo" : view.coverage === "section-representative" ? "AI-generated representative section demo" : "AI-generated illustrative venue demo" : view.coverage === "exact-seat" ? "Exact seat photo" : view.coverage === "row-representative" ? "Representative row view" : view.coverage === "section-representative" ? "Representative section view" : view.coverage === "venue-representative" ? "Representative venue view" : "Venue photo";
11
12
  const year = view.capturedAt && /^\d{4}/.test(view.capturedAt) ? view.capturedAt.slice(0, 4) : "";
12
- return [coverage, year ? `captured ${year}` : "", view.sourceLabel ?? ""].filter(Boolean).join(" \xB7 ");
13
+ return [coverage, year ? `${demo ? "created" : "captured"} ${year}` : "", view.sourceLabel ?? ""].filter(Boolean).join(" \xB7 ");
13
14
  }
14
15
  function isAuthoredSeatView(view) {
15
- if (view.generated) return false;
16
+ if (view.generated || view.mediaKind === "model" || view.mediaKind === "demo-render") return false;
16
17
  const source = view.sourceLabel?.trim() ?? "";
17
18
  return !/^(?:AI[- ]generated|generated\s+(?:demo|preview))/i.test(source);
18
19
  }
@@ -91,7 +92,7 @@ function mountPanorama(container, view, opts = {}) {
91
92
  root.appendChild(closeBtn);
92
93
  if (opts.disclosure) {
93
94
  const disclosure = document.createElement("div");
94
- disclosure.textContent = `360\xB0 panorama \xB7 ${opts.disclosure}`;
95
+ disclosure.textContent = `${opts.disclosurePrefix ?? "360\xB0 panorama"} \xB7 ${opts.disclosure}`;
95
96
  Object.assign(disclosure.style, {
96
97
  position: "absolute",
97
98
  top: "12px",
@@ -337,4 +338,4 @@ export {
337
338
  clampPitchPx,
338
339
  mountPanorama
339
340
  };
340
- //# sourceMappingURL=chunk-I6DPX3EM.js.map
341
+ //# sourceMappingURL=chunk-BE3F7CT3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/view3d/crossfade/panorama.ts"],"sourcesContent":["/**\n * Slice 3 hand-off — the DOM panorama overlay the fly-to-seat cinematic\n * dissolves into. Decoupled by design: the CALLER supplies the equirectangular\n * image (via mountVenue3D's getSeatView), so view3d never imports the app's\n * panorama generator and the chunk stays lean.\n *\n * Technique (mirrors SeatPicker.openSeatView, reimplemented small): an equirect\n * image panned with `repeat-x`; the initial horizontal offset is set so the\n * panorama's bearing matches the final camera yaw — the dissolve reads as the\n * same view sharpening, not a cut. CSS opacity fade is compositor-only.\n */\n\nimport type { SeatViewCoverage } from '../../core/types';\nimport {\n browserPanoramaConstraints,\n loadPanoramaImage,\n planPanoramaDelivery,\n schedulePanoramaUpgrade,\n} from '../../view/panoramaDelivery';\n\nexport interface SeatView {\n url: string;\n /** Lightweight equirect used for first paint before `url` replaces it. */\n previewUrl?: string;\n /**\n * Optional authenticated byte-to-object-URL resolver. The buyer picker uses\n * this for a deferred sharp-image upgrade so progressive view media never\n * needs a bearer in its URL.\n */\n resolveUrl?: (url: string) => Promise<string | null>;\n sourceWidth?: number;\n sourceHeight?: number;\n previewWidth?: number;\n previewHeight?: number;\n /**\n * True when `url` is a SYNTHESISED panorama rather than a real photograph.\n *\n * It decides which of two very different things the buyer gets, so the caller\n * must say rather than the renderer guess. A generated equirect is a picture\n * of a 3D scene the renderer already has: at 2048×1024 it covers 106° of\n * horizontal view in ~600 source pixels, which a 3072-device-pixel viewport\n * magnifies FIVE times, and no amount of art survives that. So a generated\n * view is not drawn as an image at all — the camera simply looks around the\n * real scene, which has no resolution ceiling.\n *\n * A real photo has no such substitute and goes on the sphere, where a 5760 or\n * 8192-wide capture has three to four times the detail to give.\n */\n generated?: boolean;\n /** Explicit media provenance; avoids inferring buyer truth from source copy. */\n mediaKind?: 'photo' | 'demo-render' | 'model';\n /** Bearing (deg, 0 = facing the focal/stage) the panorama should open centred\n * on, to match the camera's final yaw. Default 0 (both face the stage). */\n initialBearingDeg?: number;\n /** Initial vertical orientation. Positive looks up; clamped to the viewer's\n * safe pitch range. Uploaded captures use this to match the live seat pose. */\n initialPitchDeg?: number;\n /** How specifically this image represents the selected inventory unit. */\n coverage?: SeatViewCoverage;\n /** ISO capture date/time when known. Unknown is preferable to invented age. */\n capturedAt?: string;\n /** Optional buyer-safe provenance, for example \"Provided by the venue\". */\n sourceLabel?: string;\n}\n\n/** Honest, compact buyer disclosure for the panorama currently on screen. */\nexport function seatViewDisclosure(view: SeatView): string {\n const demo = view.mediaKind === 'demo-render';\n const coverage = view.generated || view.mediaKind === 'model'\n ? 'Live 3D · chart-derived seat-eye'\n : demo\n ? view.coverage === 'exact-seat'\n ? 'AI-generated exact-seat demo'\n : view.coverage === 'row-representative'\n ? 'AI-generated representative row demo'\n : view.coverage === 'section-representative'\n ? 'AI-generated representative section demo'\n : 'AI-generated illustrative venue demo'\n : view.coverage === 'exact-seat'\n ? 'Exact seat photo'\n : view.coverage === 'row-representative'\n ? 'Representative row view'\n : view.coverage === 'section-representative'\n ? 'Representative section view'\n : view.coverage === 'venue-representative'\n ? 'Representative venue view'\n : 'Venue photo';\n const year = view.capturedAt && /^\\d{4}/.test(view.capturedAt) ? view.capturedAt.slice(0, 4) : '';\n return [coverage, year ? `${demo ? 'created' : 'captured'} ${year}` : '', view.sourceLabel ?? ''].filter(Boolean).join(' · ');\n}\n\n/** True only when the panorama should be presented as organizer-authored media.\n * Representative AI demo assets travel through the same URL field as venue\n * photos, so provenance—not URL presence—must decide the buyer badge. */\nexport function isAuthoredSeatView(view: SeatView): boolean {\n if (view.generated || view.mediaKind === 'model' || view.mediaKind === 'demo-render') return false;\n const source = view.sourceLabel?.trim() ?? '';\n return !/^(?:AI[- ]generated|generated\\s+(?:demo|preview))/i.test(source);\n}\n\nexport interface PanoramaHandle {\n /** Fade out and return to the (frozen) 3D view; calls opts.onClose after. */\n close(): void;\n /** Immediate teardown (dispose) — no fade, no onClose. */\n dispose(): void;\n}\n\n/**\n * Vertical field of view (deg) the windowed panorama shows. The source image is\n * a full 180° equirect sphere; showing it raw wastes ~⅔ of the frame on dead sky\n * and black floor, with the horizon content band squished into the middle. We\n * instead scale the image so only this central slice fills the viewport height,\n * horizon-centred, and let the user drag pitch within ±`MAX_PITCH_DEG`.\n */\nexport const VFOV_DEG = 70;\nexport const MIN_VFOV_DEG = 35;\nexport const MAX_VFOV_DEG = 90;\n\n/** Keep wheel, keyboard and pinch zoom inside a useful, non-disorienting range. */\nexport function clampPanoramaFov(fovDeg: number): number {\n return Math.max(MIN_VFOV_DEG, Math.min(MAX_VFOV_DEG, fovDeg));\n}\n/** Users may look this far up/down from the horizon; well inside the image so\n * the clamp never reveals past its top/bottom edge. */\nexport const MAX_PITCH_DEG = 35;\n\n/**\n * Horizontal background-position (px) that centres `bearingDeg` in the viewport,\n * assuming the equirect image's yaw 0 sits at its horizontal centre. `bgW` is the\n * full scaled image width representing 360° — so this is invariant to the vertical\n * FOV windowing (which scales width and height by the same factor). `repeat-x`\n * handles the wrap, so any real value is valid.\n */\nexport function bearingToOffsetPx(bearingDeg: number, viewportW: number, bgW: number): number {\n const col = (0.5 + bearingDeg / 360) * bgW; // image column (px) for the bearing\n return viewportW / 2 - col;\n}\n\n/**\n * Full scaled image height (px) so that a `vfovDeg`-tall slice fills `viewportH`.\n * The image spans 180° vertically, so height = viewportH · 180/vfov.\n */\nexport function windowedBgHeight(viewportH: number, vfovDeg: number = VFOV_DEG): number {\n return viewportH * (180 / vfovDeg);\n}\n\n/**\n * background-position Y (px) that centres the image's horizon (its vertical\n * centre) in the viewport, offset by `pitchPx` (deviation from the horizon,\n * clamped to ±`MAX_PITCH_DEG`). Positive `pitchPx` looks up.\n */\nexport function horizonOffsetPy(viewportH: number, bgH: number, pitchPx: number): number {\n return (viewportH - bgH) / 2 + clampPitchPx(pitchPx, bgH);\n}\n\n/** Clamp a pitch drag (px) to ±MAX_PITCH_DEG of image travel, and never past the\n * image edge. `bgH` px map the full 180°, so a degree is `bgH/180` px. */\nexport function clampPitchPx(pitchPx: number, bgH: number): number {\n const limit = (MAX_PITCH_DEG / 180) * bgH;\n return Math.max(-limit, Math.min(limit, pitchPx));\n}\n\nexport interface PanoramaOptions {\n fadeMs?: number;\n seatLabel?: string;\n disclosure?: string;\n /** Prefix for the disclosure chip; generated live geometry is not a panorama. */\n disclosurePrefix?: string;\n onClose?: () => void;\n /** Cancels stale preview/full-resolution image work when a flight is retargeted. */\n signal?: AbortSignal;\n}\n\nexport function mountPanorama(container: HTMLElement, view: SeatView, opts: PanoramaOptions = {}): PanoramaHandle {\n const fadeMs = opts.fadeMs ?? 400;\n const bearing = view.initialBearingDeg ?? 0;\n\n const delivery = planPanoramaDelivery(view, browserPanoramaConstraints());\n const loadAbort = new AbortController();\n const abortFromCaller = (): void => loadAbort.abort();\n opts.signal?.addEventListener('abort', abortFromCaller, { once: true });\n const root = document.createElement('div');\n const priorFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-label', opts.seatLabel ? `View from ${opts.seatLabel}` : 'View from seat');\n root.tabIndex = -1;\n Object.assign(root.style, {\n position: 'absolute', inset: '0', zIndex: '10', opacity: '0',\n transition: `opacity ${fadeMs}ms ease`, background: '#05070c',\n overflow: 'hidden', touchAction: 'none',\n } as CSSStyleDeclaration);\n\n const pano = document.createElement('div');\n Object.assign(pano.style, {\n position: 'absolute', inset: '0',\n backgroundImage: `url(\"${delivery.initialUrl}\")`, backgroundRepeat: 'repeat-x',\n cursor: 'grab',\n } as CSSStyleDeclaration);\n root.appendChild(pano);\n\n // Close affordance.\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.textContent = '✕';\n Object.assign(closeBtn.style, {\n position: 'absolute', top: '12px', right: '12px', zIndex: '2',\n width: '44px', height: '44px', borderRadius: '999px', cursor: 'pointer',\n border: '1px solid rgba(255,255,255,0.25)', background: 'rgba(8,12,18,0.6)',\n color: '#e6edf3', fontSize: '15px', lineHeight: '1',\n } as CSSStyleDeclaration);\n root.appendChild(closeBtn);\n\n if (opts.disclosure) {\n const disclosure = document.createElement('div');\n disclosure.textContent = `${opts.disclosurePrefix ?? '360° panorama'} · ${opts.disclosure}`;\n Object.assign(disclosure.style, {\n position: 'absolute', top: '12px', left: '12px', zIndex: '2',\n maxWidth: 'calc(100% - 88px)', padding: '8px 11px', borderRadius: '999px',\n overflow: 'hidden', color: '#dce6f8', background: 'rgba(8,12,18,0.68)',\n font: '600 11px/1.2 ui-sans-serif, system-ui, sans-serif', textOverflow: 'ellipsis',\n whiteSpace: 'nowrap', pointerEvents: 'none', backdropFilter: 'blur(6px)',\n } as CSSStyleDeclaration);\n root.appendChild(disclosure);\n }\n\n const hint = document.createElement('div');\n hint.textContent = 'Drag to look · pinch or scroll to zoom · Esc to close';\n Object.assign(hint.style, {\n position: 'absolute', bottom: '12px', left: '0', right: '0', textAlign: 'center',\n color: 'rgba(230,237,243,0.7)', font: '12px ui-sans-serif, system-ui, sans-serif',\n pointerEvents: 'none',\n } as CSSStyleDeclaration);\n root.appendChild(hint);\n\n container.appendChild(root);\n closeBtn.focus({ preventScroll: true });\n\n // Layout: window a ~70° vertical slice of the sphere (horizon-centred) so the\n // venue fills the frame instead of floating in dead sky + black floor. The\n // image is scaled so that slice is exactly the viewport height; width scales by\n // the same factor, so `bearingToOffsetPx` stays correct. `pitchPx` is the\n // vertical drag deviation from the horizon, clamped to ±35°.\n let bgW = 0;\n let bgH = 0;\n let posX = 0;\n let pitchPx = 0;\n let viewFov = VFOV_DEG;\n const layout = (): void => {\n const vh = root.clientHeight || 1;\n const vw = root.clientWidth || 1;\n const natW = img.naturalWidth || vw * 2;\n const natH = img.naturalHeight || vh;\n const centredImageRatio = bgW > 0 ? (vw / 2 - posX) / bgW : 0;\n const pitchRatio = bgH > 0 ? pitchPx / bgH : 0;\n bgH = windowedBgHeight(vh, viewFov);\n bgW = bgH * (natW / natH);\n pano.style.backgroundSize = `${bgW}px ${bgH}px`;\n if (!posInitialised) {\n posX = bearingToOffsetPx(bearing, vw, bgW);\n pitchPx = clampPitchPx(((view.initialPitchDeg ?? 0) / 180) * bgH, bgH);\n posInitialised = true;\n }\n else posX = vw / 2 - centredImageRatio * bgW;\n pitchPx = clampPitchPx(pitchRatio * bgH, bgH);\n pano.style.backgroundPosition = `${posX}px ${horizonOffsetPy(vh, bgH, pitchPx)}px`;\n };\n let posInitialised = false;\n\n const applyPos = (): void => {\n const vh = root.clientHeight || 1;\n pitchPx = clampPitchPx(pitchPx, bgH);\n pano.style.backgroundPosition = `${posX}px ${horizonOffsetPy(vh, bgH, pitchPx)}px`;\n };\n\n let img = new Image();\n img.onload = layout;\n img.src = delivery.initialUrl;\n let cancelUpgrade = (): void => {};\n if (delivery.upgradeUrl) {\n cancelUpgrade = schedulePanoramaUpgrade(() => {\n const resolved = view.resolveUrl\n ? view.resolveUrl(delivery.upgradeUrl!)\n : Promise.resolve(delivery.upgradeUrl!);\n void resolved.then((url) => {\n if (!url || loadAbort.signal.aborted) return null;\n return loadPanoramaImage(url, loadAbort.signal).then((full) => ({ full, url }));\n }).then((loaded) => {\n if (!loaded) return;\n if (disposed || loadAbort.signal.aborted) return;\n img = loaded.full;\n pano.style.backgroundImage = `url(\"${loaded.url}\")`;\n layout();\n }).catch(() => { /* the preview remains usable */ });\n });\n }\n // If it's already cached, onload may not fire — lay out on next frame too.\n requestAnimationFrame(layout);\n\n // Pan: horizontal (repeat-x wraps seamlessly) + vertical pitch (clamped ±35°).\n let dragging = false;\n let lastX = 0;\n let lastY = 0;\n let pinchDistance = 0;\n const pointers = new Map<number, { x: number; y: number }>();\n const distanceBetweenPointers = (): number => {\n const [a, b] = [...pointers.values()];\n return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;\n };\n const setViewFov = (nextFov: number): void => {\n const clamped = clampPanoramaFov(nextFov);\n if (clamped === viewFov) return;\n viewFov = clamped;\n layout();\n };\n const onDown = (e: PointerEvent): void => {\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n dragging = pointers.size === 1;\n lastX = e.clientX; lastY = e.clientY;\n if (pointers.size === 2) pinchDistance = distanceBetweenPointers();\n pano.style.cursor = 'grabbing';\n try { pano.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onMove = (e: PointerEvent): void => {\n if (!pointers.has(e.pointerId)) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (pointers.size >= 2) {\n const nextDistance = distanceBetweenPointers();\n if (pinchDistance > 0 && nextDistance > 0) setViewFov(viewFov * (pinchDistance / nextDistance));\n pinchDistance = nextDistance;\n dragging = false;\n return;\n }\n if (!dragging) return;\n posX += e.clientX - lastX;\n pitchPx += e.clientY - lastY;\n lastX = e.clientX;\n lastY = e.clientY;\n applyPos();\n };\n const onUp = (e: PointerEvent): void => {\n pointers.delete(e.pointerId);\n pinchDistance = pointers.size === 2 ? distanceBetweenPointers() : 0;\n const remaining = pointers.values().next().value as { x: number; y: number } | undefined;\n dragging = pointers.size === 1;\n if (remaining) { lastX = remaining.x; lastY = remaining.y; }\n else pano.style.cursor = 'grab';\n try { pano.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n setViewFov(viewFov + e.deltaY * 0.04);\n };\n pano.addEventListener('pointerdown', onDown);\n pano.addEventListener('pointermove', onMove);\n pano.addEventListener('pointerup', onUp);\n pano.addEventListener('pointercancel', onUp);\n pano.addEventListener('wheel', onWheel, { passive: false });\n\n let closed = false;\n let disposed = false;\n let fadeTimer = 0;\n const removeListeners = (): void => {\n pano.removeEventListener('pointerdown', onDown);\n pano.removeEventListener('pointermove', onMove);\n pano.removeEventListener('pointerup', onUp);\n pano.removeEventListener('pointercancel', onUp);\n pano.removeEventListener('wheel', onWheel);\n window.removeEventListener('keydown', onKey);\n };\n const teardown = (): void => {\n cancelUpgrade();\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n if (fadeTimer) { window.clearTimeout(fadeTimer); fadeTimer = 0; }\n removeListeners();\n if (root.parentNode) root.parentNode.removeChild(root);\n if (priorFocus?.isConnected) priorFocus.focus({ preventScroll: true });\n };\n const close = (): void => {\n if (closed) return;\n closed = true;\n root.style.opacity = '0';\n // Guard the fade callback: a dispose() (or a retarget that disposes us) inside\n // the fade window clears the timer AND flips `disposed`, so a stray fire can\n // never call onClose into a newer flight/panorama.\n const done = (): void => {\n fadeTimer = 0;\n if (disposed) return;\n teardown();\n opts.onClose?.();\n };\n fadeTimer = window.setTimeout(done, fadeMs);\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === 'Tab') { e.preventDefault(); closeBtn.focus({ preventScroll: true }); return; }\n if (e.key === 'Escape') { e.stopPropagation(); close(); return; }\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n posX += e.key === 'ArrowLeft' ? -32 : 32;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n pitchPx += (e.key === 'ArrowUp' ? 4 : -4) * (bgH / 180);\n } else if (e.key === '+' || e.key === '=') {\n setViewFov(viewFov - 5);\n e.preventDefault();\n return;\n } else if (e.key === '-') {\n setViewFov(viewFov + 5);\n e.preventDefault();\n return;\n } else {\n return;\n }\n e.preventDefault();\n applyPos();\n };\n window.addEventListener('keydown', onKey);\n closeBtn.addEventListener('click', close);\n\n // Fade in on the next frame (0 → 1).\n requestAnimationFrame(() => { root.style.opacity = '1'; });\n\n return {\n close,\n dispose(): void { closed = true; disposed = true; teardown(); },\n };\n}\n"],"mappings":"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","names":[]}
@@ -403,7 +403,8 @@ function resolveSegmentedRowGroups(objects) {
403
403
  totalSeats,
404
404
  canonical: ordered[0],
405
405
  viewFromSeatUrl: first.viewFromSeatUrl,
406
- viewFromSeatMeta: first.viewFromSeatMeta
406
+ viewFromSeatMeta: first.viewFromSeatMeta,
407
+ confidenceEvidence: first.confidenceEvidence
407
408
  });
408
409
  adjacencyOffset += row.seatCount;
409
410
  displayOffset += row.seatCount;
@@ -1237,15 +1238,24 @@ function stackFloors(doc, spread = 900) {
1237
1238
  });
1238
1239
  return { ...doc, objects, floors: void 0 };
1239
1240
  }
1240
- function expandFloorObjects(objects, zones, fallbackFocal, viewFallback) {
1241
+ function expandFloorObjects(objects, zones, fallbackFocal, activeConfigurationId, viewFallback, confidenceFallback) {
1241
1242
  const out = [];
1242
1243
  const segmented = resolveSegmentedRowGroups(objects);
1244
+ const appliesToConfiguration = (meta) => !meta?.eventConfigurationId || meta.eventConfigurationId === activeConfigurationId;
1245
+ const confidenceApplies = (evidence) => !!evidence && (!evidence.eventConfigurationId || evidence.eventConfigurationId === activeConfigurationId);
1246
+ const resolvedConfidence = (evidence, coverage) => confidenceApplies(evidence) ? { ...evidence, coverage } : void 0;
1243
1247
  for (const obj of objects) {
1244
1248
  let seats = [];
1245
1249
  if (obj.type === "row") seats = expandRow(obj);
1246
1250
  else if (obj.type === "table") seats = expandTable(obj);
1247
1251
  else if (obj.type === "booth") seats = expandBooth(obj);
1248
1252
  if (!seats.length) continue;
1253
+ for (const seat of seats) {
1254
+ if (seat.viewUrl && !appliesToConfiguration(seat.viewMeta)) {
1255
+ seat.viewUrl = void 0;
1256
+ seat.viewMeta = void 0;
1257
+ }
1258
+ }
1249
1259
  if (obj.type === "row") {
1250
1260
  const logical = segmented.get(obj.id);
1251
1261
  if (logical) {
@@ -1258,10 +1268,12 @@ function expandFloorObjects(objects, zones, fallbackFocal, viewFallback) {
1258
1268
  if (!overrides.get(physicalIndex)?.displayLabel) {
1259
1269
  seat.displayLabel = seatDisplayLabel(obj, physicalIndex, logical);
1260
1270
  }
1261
- if (!seat.viewUrl && logical.viewFromSeatUrl) {
1271
+ if (!seat.viewUrl && logical.viewFromSeatUrl && appliesToConfiguration(logical.viewFromSeatMeta)) {
1262
1272
  seat.viewUrl = logical.viewFromSeatUrl;
1263
1273
  seat.viewMeta = logical.viewFromSeatMeta;
1264
1274
  }
1275
+ const exactEvidence = overrides.get(physicalIndex)?.confidenceEvidence;
1276
+ seat.confidenceEvidence = resolvedConfidence(exactEvidence, "exact-seat") ?? resolvedConfidence(obj.confidenceEvidence, "row-representative") ?? resolvedConfidence(logical.confidenceEvidence, "row-representative");
1265
1277
  }
1266
1278
  }
1267
1279
  }
@@ -1270,11 +1282,17 @@ function expandFloorObjects(objects, zones, fallbackFocal, viewFallback) {
1270
1282
  const zone = owner?.zone ? zones?.find((candidate) => candidate.id === owner.zone) : void 0;
1271
1283
  const resolvedFocal = zone?.focalPoint ?? fallbackFocal;
1272
1284
  for (const seat of seats) {
1273
- if (!seat.viewUrl && inheritedView) {
1285
+ if (!seat.confidenceEvidence) {
1286
+ const physicalIndex = Number(seat.id.slice(seat.id.lastIndexOf(":") + 1));
1287
+ const exactEvidence = (obj.type === "row" || obj.type === "table") && Number.isInteger(physicalIndex) ? obj.overrides?.find((override) => override.index === physicalIndex)?.confidenceEvidence : void 0;
1288
+ seat.confidenceEvidence = resolvedConfidence(exactEvidence, "exact-seat") ?? (obj.type === "row" ? resolvedConfidence(obj.confidenceEvidence, "row-representative") : void 0);
1289
+ }
1290
+ seat.confidenceEvidence ??= resolvedConfidence(owner?.confidenceEvidence, "section-representative") ?? (confidenceFallback ? resolvedConfidence(confidenceFallback.evidence, confidenceFallback.coverage) : void 0);
1291
+ if (!seat.viewUrl && inheritedView && appliesToConfiguration(owner?.viewFromSeatMeta)) {
1274
1292
  seat.viewUrl = inheritedView;
1275
1293
  seat.viewMeta = owner?.viewFromSeatMeta;
1276
1294
  }
1277
- if (!seat.viewUrl && viewFallback) {
1295
+ if (!seat.viewUrl && viewFallback && appliesToConfiguration(viewFallback.meta)) {
1278
1296
  seat.viewUrl = viewFallback.url;
1279
1297
  seat.viewMeta = viewFallback.meta;
1280
1298
  }
@@ -1292,7 +1310,15 @@ function expandChart(doc, options = {}) {
1292
1310
  for (const floor of doc.floors) {
1293
1311
  const floorFocal = floor.focalPoint ?? doc.focalPoint;
1294
1312
  const floorView = floor.viewFromSeatUrl ? { url: floor.viewFromSeatUrl, meta: floor.viewFromSeatMeta } : doc.viewFromSeatUrl ? { url: doc.viewFromSeatUrl, meta: doc.viewFromSeatMeta } : void 0;
1295
- const seats = expandFloorObjects(floor.objects, doc.zones, floorFocal, floorView);
1313
+ const confidenceFallback = floor.confidenceEvidence ? { evidence: floor.confidenceEvidence, coverage: "floor-representative" } : doc.confidenceEvidence ? { evidence: doc.confidenceEvidence, coverage: "venue-representative" } : void 0;
1314
+ const seats = expandFloorObjects(
1315
+ floor.objects,
1316
+ doc.zones,
1317
+ floorFocal,
1318
+ doc.eventConfigurationId,
1319
+ floorView,
1320
+ confidenceFallback
1321
+ );
1296
1322
  if (options.resolveEyeHeights !== false) {
1297
1323
  assignEyeHeights(floor.objects, floor.focalPoint ?? doc.focalPoint, floor.baseHeightM ?? 0, seats);
1298
1324
  }
@@ -1304,7 +1330,9 @@ function expandChart(doc, options = {}) {
1304
1330
  doc.objects,
1305
1331
  doc.zones,
1306
1332
  doc.focalPoint,
1307
- doc.viewFromSeatUrl ? { url: doc.viewFromSeatUrl, meta: doc.viewFromSeatMeta } : void 0
1333
+ doc.eventConfigurationId,
1334
+ doc.viewFromSeatUrl ? { url: doc.viewFromSeatUrl, meta: doc.viewFromSeatMeta } : void 0,
1335
+ doc.confidenceEvidence ? { evidence: doc.confidenceEvidence, coverage: "venue-representative" } : void 0
1308
1336
  );
1309
1337
  if (options.resolveEyeHeights !== false) {
1310
1338
  assignEyeHeights(doc.objects, doc.focalPoint, options.floorBaseHeightM ?? 0, out);
@@ -1465,4 +1493,4 @@ export {
1465
1493
  expandChart,
1466
1494
  chartBounds
1467
1495
  };
1468
- //# sourceMappingURL=chunk-TCTAS4Z2.js.map
1496
+ //# sourceMappingURL=chunk-GD267BFB.js.map