@seatlayer/core 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -95,7 +95,7 @@ interface CategoryTier {
95
95
  * venues (and seats.io) expose so buyers can filter for exactly what they need
96
96
  * and organizers can mark seats precisely. `wheelchair` is the legacy default.
97
97
  */
98
- type AccessibilityType = 'wheelchair' | 'companion' | 'semi-ambulatory' | 'hearing' | 'sign-language' | 'plus-size' | 'lift-armrest';
98
+ type AccessibilityType = 'wheelchair' | 'companion' | 'semi-ambulatory' | 'hearing' | 'cart' | 'sign-language' | 'plus-size' | 'lift-armrest';
99
99
  interface AccessibilityMeta {
100
100
  key: AccessibilityType;
101
101
  /** Full descriptive label (designer checkbox, picker legend). */
@@ -135,6 +135,13 @@ interface SeatOverride {
135
135
  accessible?: boolean;
136
136
  /** Accessibility accommodations of this seat (empty/absent = none). */
137
137
  accessibility?: AccessibilityType[];
138
+ /**
139
+ * Physical wheelchair provision. Absent keeps legacy seat rendering;
140
+ * `seat-present` is an explicit removable/fixed accessible chair, while
141
+ * `no-seat` is an empty wheelchair bay that remains one sellable inventory
142
+ * unit. This is deliberately distinct from `skip`, which removes inventory.
143
+ */
144
+ wheelchairSpaceType?: 'seat-present' | 'no-seat';
138
145
  /** Commercial selling/view attributes are deliberately not accessibility. */
139
146
  commercial?: SeatCommercialAttributes;
140
147
  /** Seat-specific view photo; falls back to the row photo. */
@@ -218,10 +225,23 @@ interface ChartTheme {
218
225
  interface RowObject {
219
226
  type: 'row';
220
227
  id: string;
228
+ /** Present when this row was materialized by the in-canvas reference scan.
229
+ * A re-scan of the same asset replaces rows carrying this marker and NEVER
230
+ * touches hand-authored rows — the same replace-generated-only invariant as
231
+ * applyReferenceInventory. */
232
+ referenceScan?: {
233
+ assetId: string;
234
+ };
221
235
  /** Row label, e.g. "A". Seat labels are `${label}-${n}`. */
222
236
  label: string;
223
237
  /** Buyer-facing row name. `label` remains the legacy inventory prefix. */
224
238
  displayLabel?: string;
239
+ /**
240
+ * Buyer-facing type word override (seats.io "Displayed type"). Replaces the
241
+ * hardcoded "Row" in the picker tooltip/confirm/cart, e.g. "Table", "Bench",
242
+ * "Aisle". ≤24 chars; absent = the default "Row". Pure presentation.
243
+ */
244
+ displayType?: string;
225
245
  labelPresentation?: LabelPresentation;
226
246
  /** Position of the FIRST seat. */
227
247
  origin: Point;
@@ -273,6 +293,48 @@ interface RowObject {
273
293
  rowIndex: number;
274
294
  segmentIndex: number;
275
295
  };
296
+ /**
297
+ * Membership in one buyer-facing segmented row. Physical component rows and
298
+ * their `${rowId}:${slotIndex}` inventory ids remain authoritative; this
299
+ * metadata only supplies logical ordering/presentation and explicit aisle
300
+ * continuity. The descriptor is repeated on every component so selecting any
301
+ * one can resolve the complete logical row without a chart-level side table.
302
+ */
303
+ segmentedRow?: {
304
+ kind: 'segmented-row-v1';
305
+ groupId: string;
306
+ componentIndex: number;
307
+ componentCount: number;
308
+ /** The first component must use `start`; later boundaries are explicit. */
309
+ boundaryBefore: 'start' | 'continuous' | 'break';
310
+ /** Buyer-facing row name; technical component `label` values never change. */
311
+ displayLabel: string;
312
+ displayType?: string;
313
+ labelPresentation?: LabelPresentation;
314
+ viewFromSeatUrl?: string;
315
+ /** Presentation intent for a continuous node-defined centreline. */
316
+ smoothing?: boolean;
317
+ };
318
+ /**
319
+ * Versioned provenance for rows created by the multiple/intertwined block
320
+ * generator. Manual geometry edits remove this marker rather than allowing a
321
+ * later regeneration to overwrite hand-authored work.
322
+ */
323
+ rowBlockGeneration?: {
324
+ kind: 'row-block-v1';
325
+ groupId: string;
326
+ style: 'multiple' | 'intertwined';
327
+ rowIndex: number;
328
+ rowCount: number;
329
+ seatsPerRow: number;
330
+ origin: Point;
331
+ rotation: number;
332
+ rowGap: number;
333
+ seatSpacing: number;
334
+ curve: number;
335
+ /** Stable canonical generator signature shared by every intact member. */
336
+ signature: string;
337
+ };
276
338
  /** First seat number (default 1). Roman/letters read it as a 1-based ordinal
277
339
  * (start 1 → I / A). */
278
340
  seatLabelStart?: number;
@@ -291,13 +353,16 @@ interface RowObject {
291
353
  * - `even` 2,4,6 — even numbers from the first even ≥ start.
292
354
  * - `updown` 1,3,5,…,6,4,2 — odd-up-even-back; REPLACES direction (uses
293
355
  * physical left→right order); start shifts.
356
+ * - `updown-descending` …5,3,1,2,4,6 — odd-back-even-up; the distinct
357
+ * reverse up/down sequence. Also replaces
358
+ * direction and uses physical order.
294
359
  * - `roman` I,II,III — honours direction + step + start (uppercase).
295
360
  * - `letters-upper` A,B,C…Z,AA — honours direction + step + start.
296
361
  * - `letters-lower` a,b,c…z,aa — honours direction + step + start.
297
362
  * Like `step`/`direction` today, the scheme changes the seat's inventory
298
363
  * label (its booking identity), by design.
299
364
  */
300
- scheme?: 'decimal' | 'odd' | 'even' | 'updown' | 'roman' | 'letters-upper' | 'letters-lower';
365
+ scheme?: 'decimal' | 'odd' | 'even' | 'updown' | 'updown-descending' | 'roman' | 'letters-upper' | 'letters-lower';
301
366
  /** Optional prefix prepended to every seat number, e.g. 'R' → 'R1', 'R2'. */
302
367
  prefix?: string;
303
368
  /**
@@ -328,15 +393,42 @@ interface RowObject {
328
393
  interface GAAreaObject {
329
394
  type: 'gaArea';
330
395
  id: string;
396
+ /** Stable technical/inventory label. */
331
397
  label: string;
398
+ /** Buyer-facing area name; technical `label` and GA unit ids stay stable. */
399
+ displayLabel?: string;
400
+ /** Buyer-facing type word override (seats.io "Displayed type"), ≤24 chars.
401
+ * Absent = the default type word. Pure presentation. */
402
+ displayType?: string;
332
403
  /** Closed polygon, in chart units. */
333
404
  points: Point[];
334
405
  /** Explicit aisles/pillars/cutouts excluded from the sellable GA surface. */
335
406
  holes?: Point[][];
336
407
  capacity: number;
337
408
  categoryKey: string;
409
+ /**
410
+ * Durable inventory provenance for a surface produced by Join Areas.
411
+ *
412
+ * A GA unit is identified by the id and zero-based range of the area that
413
+ * originally authored it, not by the current polygon which happens to own
414
+ * it. Keeping those source ranges means a geometric join never renumbers an
415
+ * already published/booked unit. Ordinary (never-joined) areas omit this
416
+ * field and implicitly own `[0, capacity)` under their own id.
417
+ *
418
+ * The ranges must be non-overlapping, contain positive whole counts, and sum
419
+ * exactly to `capacity`; validation rejects malformed metadata. Capacity
420
+ * growth appends a new range under the surviving area id, while shrinking a
421
+ * joined area is deliberately refused because it would silently destroy
422
+ * stable inventory identities.
423
+ */
424
+ inventorySegments?: GAInventorySegment[];
338
425
  referenceInventorySource?: ReferenceInventorySource;
339
426
  }
427
+ interface GAInventorySegment {
428
+ sourceAreaId: string;
429
+ startIndex: number;
430
+ count: number;
431
+ }
340
432
  /**
341
433
  * Durable evidence link for sellable objects generated from a private reference.
342
434
  * The client supplies facts and stable logical-section ids, never coordinates.
@@ -385,20 +477,46 @@ interface ReferenceInventoryExclusionSource {
385
477
  evidence: 'user-confirmed' | 'authoritative-source';
386
478
  sourceDescription: string;
387
479
  }
480
+ /** Open-path stroke semantics. Optional ShapeObject fields retain the legacy
481
+ * round/round/no-ending rendering when absent. */
482
+ type ShapeLineCap = 'butt' | 'round' | 'square';
483
+ type ShapeLineJoin = 'miter' | 'round' | 'bevel';
484
+ type ShapeLineEnding = 'none' | 'arrow';
388
485
  /** Non-bookable décor: stage, walls, exits. */
389
486
  interface ShapeObject {
390
487
  type: 'shape';
391
488
  id: string;
392
- kind: 'rect' | 'ellipse' | 'polygon';
489
+ /**
490
+ * Closed area shapes (`rect`/`ellipse`/`polygon`) take a `fill`; open path
491
+ * primitives (`line` = two points, `polyline` = n points) are stroke-only and
492
+ * never filled. All kinds honour the optional `stroke`.
493
+ */
494
+ kind: 'rect' | 'ellipse' | 'polygon' | 'line' | 'polyline';
393
495
  label?: string;
394
496
  /** For rect/ellipse: bounding box. For a stage polygon: the base (pre-shape) box, so its kind can be regenerated. */
395
497
  x?: number;
396
498
  y?: number;
397
499
  width?: number;
398
500
  height?: number;
399
- /** For polygon. */
501
+ /** For polygon/line/polyline. */
400
502
  points?: Point[];
401
503
  fill?: string;
504
+ /** Optional outline. `width` is in chart units; both fields are required together. */
505
+ stroke?: {
506
+ color: string;
507
+ width: number;
508
+ };
509
+ /** Open line/polyline only. Absent fields preserve round/round/no-ending legacy rendering. */
510
+ lineCap?: ShapeLineCap;
511
+ /** Controls corners between open-path segments. */
512
+ lineJoin?: ShapeLineJoin;
513
+ /** Independent open-path start/end decorations. Closed outlines never use these fields. */
514
+ startEnding?: ShapeLineEnding;
515
+ endEnding?: ShapeLineEnding;
516
+ /** Rect only — corner rounding radius in chart units, clamped to half the short side at edit time. */
517
+ cornerRadius?: number;
518
+ /** Whole-shape opacity 0.1–1 (default 1). */
519
+ opacity?: number;
402
520
  /** Degrees clockwise about the shape's center (default 0). Applied at render time. */
403
521
  rotation?: number;
404
522
  /**
@@ -411,18 +529,45 @@ interface ShapeObject {
411
529
  /** For a stage: which `StageKind` its polygon was generated from. */
412
530
  stageKind?: string;
413
531
  }
414
- /** Seats arranged around a table; bookable per-seat or as a whole. */
532
+ type RectTableSide = 'top' | 'bottom' | 'left' | 'right';
533
+ /** Exact rectangular-table chair distribution. The four keys are deliberately
534
+ * required: zero means that edge has no chair, while the sum is the authored
535
+ * `seatCount`. Numeric chair identity remains `${table.id}:${index}` in the
536
+ * canonical top, bottom, left, right expansion order. */
537
+ interface RectTableSeatCounts {
538
+ top: number;
539
+ bottom: number;
540
+ left: number;
541
+ right: number;
542
+ }
543
+ /** Seats arranged around a table. Grouped selling is activated only by an
544
+ * event's explicit inventory-model-2 snapshot; model-1 events continue to
545
+ * treat every authored chair as an independent unit. */
415
546
  interface TableObject {
416
547
  type: 'table';
417
548
  id: string;
418
549
  /** e.g. "T1" — seat labels are `${label}-${n}`. */
419
550
  label: string;
551
+ /** Buyer-facing table name; technical chair/group labels stay stable. */
552
+ displayLabel?: string;
553
+ /** Buyer-facing type word override (seats.io "Displayed type"), ≤24 chars.
554
+ * Absent = the default "Row"/"Table" word. Pure presentation. */
555
+ displayType?: string;
420
556
  center: Point;
421
557
  shape: 'round' | 'rect';
422
558
  /** Seats around the perimeter (round) or along the enabled edges (rect). */
423
559
  seatCount: number;
424
560
  /** Rect tables: which edges get seats (default ['top','bottom']). */
425
- sides?: Array<'top' | 'bottom' | 'left' | 'right'>;
561
+ sides?: RectTableSide[];
562
+ /**
563
+ * Rect tables only: exact chairs on every edge. Absent preserves the legacy
564
+ * `seatCount` + `sides` round-robin distribution byte-for-byte. When present,
565
+ * all four values are whole numbers >= 0 and their sum equals `seatCount`.
566
+ */
567
+ seatCountsBySide?: RectTableSeatCounts;
568
+ /** Individual-chair semantic overrides. Grouped whole/variable tables cannot
569
+ * author these because their only sellable identity is the table itself. */
570
+ overrides?: SeatOverride[];
426
571
  rotation: number;
427
572
  /** Round tables. */
428
573
  radius?: number;
@@ -437,15 +582,26 @@ interface TableObject {
437
582
  width?: number;
438
583
  height?: number;
439
584
  categoryKey: string;
440
- /** Whole-table booking: buyers get all seats or none (per-event override later). */
585
+ /** One buyer owns the complete table at exactly `seatCount` guests. */
441
586
  bookAsWhole?: boolean;
587
+ /** One buyer owns the complete table and chooses a bounded guest quantity. */
588
+ variableOccupancy?: boolean;
589
+ /** Required inclusive guest bounds when `variableOccupancy` is true. */
590
+ minOccupancy?: number;
591
+ maxOccupancy?: number;
442
592
  referenceInventorySource?: ReferenceInventorySource;
443
593
  }
444
594
  /** A booth: one bookable unit rendered as a block (trade shows, VIP boxes). */
445
595
  interface BoothObject {
446
596
  type: 'booth';
447
597
  id: string;
598
+ /** Stable technical/inventory label. */
448
599
  label: string;
600
+ /** Buyer-facing booth name; technical `label` stays stable. */
601
+ displayLabel?: string;
602
+ /** Buyer-facing type word override (seats.io "Displayed type"), ≤24 chars.
603
+ * Absent = the default type word. Pure presentation. */
604
+ displayType?: string;
449
605
  center: Point;
450
606
  width: number;
451
607
  height: number;
@@ -466,6 +622,18 @@ interface SectionObject {
466
622
  label: string;
467
623
  /** Buyer-facing section name; logical/id fields remain stable. */
468
624
  displayLabel?: string;
625
+ /**
626
+ * Buyer-facing entrance/door hint shown in the picker section card
627
+ * ("Entrance X"). ≤40 chars; absent = no entrance line. Pure presentation.
628
+ */
629
+ entrance?: string;
630
+ /**
631
+ * Organizer-supplied view image inherited by buyer inventory whose owning
632
+ * row/table/booth sits in this logical section. Seat and row photos take
633
+ * precedence; multipart components are kept in sync by the shared section
634
+ * metadata operation.
635
+ */
636
+ viewFromSeatUrl?: string;
469
637
  labelPresentation?: LabelPresentation;
470
638
  /**
471
639
  * Stable management/inventory identity shared by disconnected visual
@@ -512,11 +680,35 @@ interface SectionObject {
512
680
  * Tier height. 0 = floor (default). Higher values lift the section in the
513
681
  * picker's isometric ("3D") view, drawn on extruded side faces. Same field a
514
682
  * future multi-floor mode reuses — authored in 2D, never drawn by the user.
683
+ *
684
+ * This is the coarse, back-compat source for {@link height}/{@link rake}: when
685
+ * those are absent, {@link sectionGeometry} derives real geometry from this
686
+ * tier so legacy charts render pixel-identical.
515
687
  */
516
688
  elevation?: number;
689
+ /**
690
+ * 3D foundations (Phase A, additive — no migration; charts are JSON blobs).
691
+ * Metres the section's **front edge** sits above floor 0 (a balcony/tier floor
692
+ * height). Absent ⇒ derived from the coarse {@link elevation} tier via
693
+ * {@link sectionGeometry}. Deliberately two scalars, not a foundation polygon:
694
+ * front-height + {@link rake} fully determine a rectangular tier's back-height.
695
+ *
696
+ * NOTE: no consumer reads this raw field directly — all callers go through
697
+ * {@link sectionGeometry}. Phase B consumers (iso view lift in
698
+ * `SeatmapRenderer`, per-seat eye-height in the `generatePanorama` 360°
699
+ * generator) are intentionally NOT wired in Phase A. Range 0–120 m.
700
+ */
701
+ height?: number;
702
+ /**
703
+ * Degrees of seating incline within the section (0 = flat; typical stalls
704
+ * 5–15°, steep tiers 25–35°). Absent ⇒ 0. Consumed alongside {@link height}
705
+ * by the future Phase B iso-lift shear and 360° sightline math — never in
706
+ * Phase A. Range 0–45°.
707
+ */
708
+ rake?: number;
517
709
  /** Uniform scale about the outline centroid (1 = as drawn). Scales members too. */
518
710
  scale?: number;
519
- /** 0–1: how strongly member-row curves are bent toward a common arc fitted to the outline. */
711
+ /** 0–100: reviewed strength last used to bend member rows toward a common fitted arc. */
520
712
  smoothing?: number;
521
713
  /** Degrees clockwise about the outline centroid (default 0). Rotates members too. */
522
714
  rotation?: number;
@@ -530,23 +722,42 @@ interface ZoneDef {
530
722
  id: string;
531
723
  label: string;
532
724
  color?: string;
725
+ /**
726
+ * Authored point this zone faces. Optional only for legacy documents: runtime
727
+ * consumers fall back to the active floor/chart focal, while publication of
728
+ * a zone-mode draft requires every used zone to carry an explicit point.
729
+ */
730
+ focalPoint?: Point;
533
731
  }
534
732
  /**
535
733
  * Selection layer — a hit-test/dim filter in the designer, NOT z-order management.
536
734
  * Fixed set of four; derived from object type via `layerOf()` (no per-object field yet).
537
735
  */
538
736
  type SelectionLayer = 'interactive' | 'background' | 'foreground' | 'surroundings';
737
+ /** Shape roles emitted by the curated venue-landmark palette. Keep this list in
738
+ * lockstep with `DECOR_PRESETS`; the selection-layer unit test fails if either
739
+ * vocabulary changes without an explicit routing decision. `reference-focal`
740
+ * is source-backed venue context rather than an authoring-palette preset. */
741
+ declare const SURROUNDINGS_SHAPE_ROLES: readonly ["reference-focal", "bar", "entrance", "exit", "restroom", "screen", "sound", "concession", "coat", "wall"];
539
742
  /** Derive an object's selection layer from its type. */
540
743
  declare function layerOf(obj: ChartObject): SelectionLayer;
541
744
  /** Free-standing text on the chart (aisle names, door labels…). */
542
745
  interface TextObject {
543
746
  type: 'text';
544
747
  id: string;
748
+ /** Persisted provenance for objects created from the venue-icon palette. */
749
+ semanticKind?: 'icon';
545
750
  text: string;
546
751
  position: Point;
547
752
  fontSize: number;
753
+ /** Optional CSS family stack for this annotation; absent inherits ChartTheme.fontFamily. */
754
+ fontFamily?: string;
548
755
  rotation: number;
549
756
  color?: string;
757
+ /** Render weight (default false). Maps to Konva fontStyle bold. */
758
+ bold?: boolean;
759
+ /** Render slant (default false). Maps to Konva fontStyle italic. */
760
+ italic?: boolean;
550
761
  }
551
762
  /**
552
763
  * A raster/vector decor graphic drawn IN the chart, beneath the seats and
@@ -570,6 +781,12 @@ interface DecorImageObject {
570
781
  rotation?: number;
571
782
  /** 0–1 (default 1). Multiplied by any chart-theme dimming at render time. */
572
783
  opacity?: number;
784
+ /**
785
+ * Z-layer relative to the interactive seat layer. `background` (default)
786
+ * draws beneath the seats/sections; `foreground` draws above them (a roof
787
+ * canopy, an overlay graphic). Absent = background — no migration needed.
788
+ */
789
+ layer?: 'background' | 'foreground';
573
790
  /** Optional caption for the designer inspector / accessibility (not drawn). */
574
791
  label?: string;
575
792
  }
@@ -583,8 +800,25 @@ type ChartObject = RowObject | GAAreaObject | ShapeObject | TableObject | BoothO
583
800
  interface Floor {
584
801
  id: string;
585
802
  name: string;
803
+ /**
804
+ * Absolute physical deck height in metres above the venue/stage datum.
805
+ * Optional for backwards compatibility; an absent value resolves to ground
806
+ * level (0 m). Section tiers and rakes are separate, section-local metadata.
807
+ * Range 0–120 m.
808
+ */
809
+ baseHeightM?: number;
586
810
  objects: ChartObject[];
587
811
  focalPoint: Point;
812
+ /**
813
+ * Private organizer trace/calibration layer. This is authoring evidence and
814
+ * must never be served to, or rendered by, a buyer surface.
815
+ */
816
+ referenceImage?: ChartReferenceImage;
817
+ /**
818
+ * Buyer-visible aesthetic background. Canonical documents store URL-only
819
+ * images here. Historical `assetId` values are interpreted as a trace layer
820
+ * by the background compatibility helpers.
821
+ */
588
822
  backgroundImage?: ChartDoc['backgroundImage'];
589
823
  }
590
824
  interface ReferenceCalibration {
@@ -598,6 +832,64 @@ interface ReferenceCalibration {
598
832
  /** Derived and stored for deterministic geometry compilers. */
599
833
  pixelsPerUnit: number;
600
834
  }
835
+ /**
836
+ * A single human-placed seat probe: the author clicks one seat on the reference
837
+ * image and the server reads the surrounding seat lattice from it.
838
+ *
839
+ * COORDINATE-POLICY CARVE-OUT (owner decision 2026-07-21). The reference
840
+ * blueprint pipeline runs `coordinatePolicy: 'opaque-region-ids-only'` — the
841
+ * server is the sole source of chart coordinates and MCP clients select opaque
842
+ * `reg_*` ids, never points. This type is a deliberate, narrow exception on the
843
+ * same grounds as `ReferenceCalibration`: the point is placed by a human in the
844
+ * designer canvas, not proposed by a model.
845
+ *
846
+ * Therefore this is DESIGNER-ONLY and is intentionally NOT exposed over MCP.
847
+ * That is an accepted, documented exception to the MCP-parity rule — the
848
+ * server-is-sole-source guarantee for model-driven edits is worth more than
849
+ * parity here. Do not "fix" it by adding a seed point to an MCP tool schema.
850
+ */
851
+ interface ReferenceSeatSeed {
852
+ /** Seed centre in immutable source-image pixels (never chart coordinates). */
853
+ source: Point;
854
+ /** Half-width of the author's sizing ring, in source pixels — the "this is how
855
+ * big one seat is" hint that replaces seats.io's zoom-until-it-matches step. */
856
+ radius: number;
857
+ /** Whether `radius` was fitted from image pixels or set by hand. Detection
858
+ * weights an author-set radius more heavily than one we guessed. */
859
+ origin: 'auto-fit' | 'manual';
860
+ }
861
+ /** One detected row in a scan proposal — a straight seat run in CHART
862
+ * coordinates (the server maps source pixels through referencePixelToChart;
863
+ * clients never see source-pixel geometry back). */
864
+ interface ReferenceScanRowProposal {
865
+ start: Point;
866
+ end: Point;
867
+ seatCount: number;
868
+ }
869
+ /** Detected rows attributed to one compiled section (or unattributed when the
870
+ * lattice extends outside every compiled polygon). */
871
+ interface ReferenceScanSectionProposal {
872
+ /** Id of the compiled SectionObject the rows landed in; null = unattributed. */
873
+ sectionId: string | null;
874
+ name: string;
875
+ rows: ReferenceScanRowProposal[];
876
+ seatCount: number;
877
+ /** 0..1 — how well this section's lattice agreed with the probe's pitch. */
878
+ confidence: number;
879
+ /** Index of the seed (multi-probe) whose pitch produced these rows. */
880
+ seedIndex: number;
881
+ }
882
+ /** Server response for an in-canvas reference scan. A PROPOSAL — nothing is
883
+ * committed until the author applies it in the designer (chartOps + undo). */
884
+ interface ReferenceScanProposal {
885
+ assetId: string;
886
+ /** Measured seat diameter / centre-to-centre pitch, in chart units. */
887
+ seatDiameter: number;
888
+ seatPitch: number;
889
+ totalSeats: number;
890
+ totalRows: number;
891
+ sections: ReferenceScanSectionProposal[];
892
+ }
601
893
  /** Coordinate-free physical scale derived by server code from a confirmed
602
894
  * semantic feature. Unlike manual two-point calibration, no source points pass
603
895
  * through an MCP client or language model. */
@@ -649,7 +941,16 @@ interface ChartDoc {
649
941
  * is kept mirroring floor 0 so single-floor readers never branch. */
650
942
  floors?: Floor[];
651
943
  objects: ChartObject[];
652
- /** Floor-plan photo the organizer traces over (designer-only aid, also rendered dimly in picker if kept). */
944
+ /**
945
+ * Private floor-plan source used for tracing, calibration, scanning and
946
+ * reference-backed generation. Buyer projections always remove this field.
947
+ */
948
+ referenceImage?: ChartReferenceImage;
949
+ /**
950
+ * Buyer-visible aesthetic background. Canonical values are URL-only.
951
+ * Compatibility: a historical value containing `assetId` is trace-only and
952
+ * is never rendered or exposed to buyers.
953
+ */
653
954
  backgroundImage?: ChartReferenceImage;
654
955
  /** Brand/venue theming (colors); categories carry their own colors separately. */
655
956
  theme?: ChartTheme;
@@ -671,6 +972,18 @@ interface ExpandedSeat {
671
972
  x: number;
672
973
  y: number;
673
974
  rowId: string;
975
+ /** Owning logical section and navigation zone, resolved once at expand time. */
976
+ sectionId?: string;
977
+ zoneId?: string;
978
+ /** Zone focal when authored, otherwise the active floor/chart legacy fallback. */
979
+ focalPoint?: Point;
980
+ /** Buyer-facing segmented-row identity. `rowId` stays the physical owner id. */
981
+ logicalRowId?: string;
982
+ /**
983
+ * Seat order inside the logical row. A deliberate missing integer is inserted
984
+ * at every aisle boundary, so numerical adjacency cannot bridge a gap.
985
+ */
986
+ logicalSeatIndex?: number;
674
987
  categoryKey: string;
675
988
  /** 'booth' units render as blocks (dimensions looked up via rowId = booth id). */
676
989
  kind?: 'seat' | 'booth';
@@ -678,13 +991,24 @@ interface ExpandedSeat {
678
991
  accessible?: boolean;
679
992
  /** Specific accessibility accommodations (absent = none) — picker badges/filters these. */
680
993
  accessibility?: AccessibilityType[];
994
+ /** Physical wheelchair provision resolved from the seat override. */
995
+ wheelchairSpaceType?: 'seat-present' | 'no-seat';
681
996
  commercial?: SeatCommercialAttributes;
682
997
  /** Organizer-supplied view-from-seat image (inherited from the row). */
683
998
  viewUrl?: string;
684
999
  /** Per-seat label size/color override; absent = inherit the row/theme default. */
685
1000
  labelStyle?: LabelStyle;
1001
+ /**
1002
+ * Real-world eye height in metres above the focal/stage datum, resolved at
1003
+ * expand time from the owning section's `{height, rake}` + drawn depth (Phase B2).
1004
+ * Feeds the auto-360° generator's stage-pitch math. Absent ⇒ flat seated eye
1005
+ * height (legacy / seats in no section) — so old charts stay pixel-identical.
1006
+ */
1007
+ eyeHeightM?: number;
686
1008
  }
687
1009
  type SeatStatus = 'free' | 'held' | 'booked' | 'not_for_sale';
1010
+ /** Buyer canvas projection. Perspective is a view-only projected-2.5D lane. */
1011
+ type RendererViewMode = 'flat' | 'isometric' | 'perspective';
688
1012
  interface RendererCallbacks {
689
1013
  onSelect?: (seat: ExpandedSeat) => void;
690
1014
  onDeselect?: (seat: ExpandedSeat) => void;
@@ -759,6 +1083,11 @@ interface RenderedBookableLabelEvidence {
759
1083
  seatId: string;
760
1084
  label: string;
761
1085
  kind: 'seat' | 'booth';
1086
+ /** Painted inventory silhouette. Empty wheelchair bays are deliberately
1087
+ * square, while physical seats retain the ordinary circular marker. */
1088
+ markerShape: 'circle' | 'square' | 'booth';
1089
+ /** Physical wheelchair provision represented by this inventory unit. */
1090
+ wheelchairSpaceType?: 'seat-present' | 'no-seat';
762
1091
  categoryKey: string;
763
1092
  sectionId?: string;
764
1093
  zoneId?: string;
@@ -769,6 +1098,13 @@ interface RenderedBookableLabelEvidence {
769
1098
  fill: string;
770
1099
  ink: string;
771
1100
  opacity: number;
1101
+ /** Buyer-visible accessibility glyph evidence. Filter emphasis uses a
1102
+ * screen-space minimum so wheelchair provision remains recognizable at fit. */
1103
+ accessibilityMarker?: {
1104
+ glyphVisible: boolean;
1105
+ glyphWidthPx: number;
1106
+ emphasizedByFilter: boolean;
1107
+ };
772
1108
  /** Direct Konva shape bounds and the production near-miss rescue combined. */
773
1109
  pointerTarget: {
774
1110
  active: boolean;
@@ -849,6 +1185,15 @@ interface RendererQualityEvidence {
849
1185
  width: number;
850
1186
  height: number;
851
1187
  };
1188
+ /** Runtime projection actually used for the pixels and hit graph below. */
1189
+ projection: RendererViewMode;
1190
+ /** Phase-C proof metadata. Present only in the projected-2.5D lane. */
1191
+ perspective?: {
1192
+ model: 'pinhole-exact-seat-anchors';
1193
+ sectionSurfaceModel: 'tangent-plane';
1194
+ exactSeatAnchorCount: number;
1195
+ depthSorted: true;
1196
+ };
852
1197
  canvasBackground: string;
853
1198
  effectiveScale: number;
854
1199
  rung: LodRung;
@@ -1033,13 +1378,14 @@ interface ISeatmapRenderer {
1033
1378
  */
1034
1379
  setColorblindSafe?(on: boolean): void;
1035
1380
  /**
1036
- * Switch the projection. `'flat'` = normal top-down; `'isometric'` = the "3D"
1037
- * view (affine skew/rotate + elevation lift), hit-testing preserved in screen
1038
- * space. Purely visual the chart is authored flat. Animated unless reduced-motion.
1381
+ * Switch the projection. `'flat'` = normal top-down; `'isometric'` = the
1382
+ * legacy affine preview; `'perspective'` = projected 2.5D with exact pinhole
1383
+ * seat anchors/native hit shapes and bounded per-section tangent surfaces.
1384
+ * Purely visual — the chart is authored flat.
1039
1385
  */
1040
- setViewMode?(mode: 'flat' | 'isometric'): void;
1386
+ setViewMode?(mode: RendererViewMode): void;
1041
1387
  /** Current projection (defaults to 'flat' when unimplemented). */
1042
- getViewMode?(): 'flat' | 'isometric';
1388
+ getViewMode?(): RendererViewMode;
1043
1389
  /** Multi-floor (Batch 5): switch the shown floor; list floors; read the active id. */
1044
1390
  setActiveFloor?(floorId: string): void;
1045
1391
  getFloors?(): {
@@ -1108,6 +1454,8 @@ declare const CHART_STORAGE_KEY = "seatmap.chart";
1108
1454
  */
1109
1455
  /** Base (pre-override) seat centre per index — shared by expandRow + designer edit mode. */
1110
1456
  declare function rowSeatPositions(row: RowObject): Point[];
1457
+ /** Sellable row slots: skipped slots are absent; empty wheelchair bays remain. */
1458
+ declare function rowInventoryCount(row: RowObject): number;
1111
1459
  /**
1112
1460
  * Every seat slot of a row INCLUDING skipped ones (designer seat-edit mode uses
1113
1461
  * this to draw un-skip handles). Overrides (dx/dy/label/categoryKey) are applied
@@ -1123,10 +1471,17 @@ interface RowSeatSlot {
1123
1471
  skipped: boolean;
1124
1472
  accessible: boolean;
1125
1473
  accessibility: AccessibilityType[];
1474
+ wheelchairSpaceType?: SeatOverride['wheelchairSpaceType'];
1126
1475
  commercial?: RowObject['commercial'];
1127
1476
  viewUrl?: string;
1128
1477
  labelStyle?: LabelStyle;
1129
1478
  }
1479
+ /** Every authored table-chair slot, including skipped inventory. This mirrors
1480
+ * row seat slots so Designer, MCP and buyer/event expansion share one semantic
1481
+ * source while retaining the table's stable numeric slot identity. */
1482
+ interface TableSeatSlot extends RowSeatSlot {
1483
+ side?: RectTableSide;
1484
+ }
1130
1485
  /**
1131
1486
  * The seat NUMBER part of a row seat's label (the row prefix is prepended by the
1132
1487
  * caller). Applies the row's numbering scheme, direction, step, start and label
@@ -1143,6 +1498,15 @@ declare function expandRow(row: RowObject): ExpandedSeat[];
1143
1498
  * bottom edges (split evenly, any remainder to the top), 16u outside the edge,
1144
1499
  * the whole set rotated about the table centre.
1145
1500
  */
1501
+ /** Legacy rect tables distribute aggregate capacity round-robin across enabled
1502
+ * sides, then emit chairs in canonical top/bottom/left/right order. */
1503
+ declare function tableSeatCountsBySide(t: TableObject): RectTableSeatCounts;
1504
+ /** Expand every authored table chair, including skipped slots needed by the
1505
+ * Designer to restore inventory. */
1506
+ declare function expandTableSlots(t: TableObject): TableSeatSlot[];
1507
+ /** Sellable individual table chairs. Grouped tables own one atomic inventory
1508
+ * unit elsewhere and therefore retain their full authored chair capacity. */
1509
+ declare function tableInventoryCount(t: TableObject): number;
1146
1510
  declare function expandTable(t: TableObject): ExpandedSeat[];
1147
1511
  /** Expand a booth into its single bookable block unit. */
1148
1512
  declare function expandBooth(b: BoothObject): ExpandedSeat[];
@@ -1158,6 +1522,14 @@ declare function polygonLabelPoint(outer: Point[], holes: Point[][] | undefined)
1158
1522
  * shape: bbox centre; text: its position.
1159
1523
  */
1160
1524
  declare function objectCenter(o: ChartObject): Point;
1525
+ /**
1526
+ * Resolve one bookable object's owning physical section using the canonical
1527
+ * first-match rule. Generated reference inventory may name a logical section,
1528
+ * but that provenance is trusted only when the stored geometry confirms it.
1529
+ * Keeping this primitive in layout lets section inventory, category painting,
1530
+ * and buyer view inheritance share one ownership decision.
1531
+ */
1532
+ declare function owningSectionForObject(objects: ChartObject[], object: ChartObject): SectionObject | undefined;
1161
1533
  /**
1162
1534
  * Normalized floor list (Batch 5): a multi-floor chart's `floors`, or a synthetic
1163
1535
  * single floor wrapping a single-floor chart's `objects`. Every consumer that needs
@@ -1175,8 +1547,15 @@ declare function allObjects(doc: ChartDoc): ChartObject[];
1175
1547
  * only for the 3D overview render — 2D still shows one floor via `floorObjects`.
1176
1548
  */
1177
1549
  declare function stackFloors(doc: ChartDoc, spread?: number): ChartDoc;
1178
- /** Expand every seat-bearing object across all floors (rows, tables, booths). */
1179
- declare function expandChart(doc: ChartDoc): ExpandedSeat[];
1550
+ interface ExpandChartOptions {
1551
+ /** Physical height for a single-floor projection extracted from a multi-floor
1552
+ * document. Full multi-floor documents resolve each floor directly. */
1553
+ floorBaseHeightM?: number;
1554
+ }
1555
+ /** Expand every seat-bearing object across all floors (rows, tables, booths).
1556
+ * Multi-floor ownership is resolved one floor at a time: local coordinates may
1557
+ * overlap between floors and must never assign a seat to another floor's section. */
1558
+ declare function expandChart(doc: ChartDoc, options?: ExpandChartOptions): ExpandedSeat[];
1180
1559
  /** Axis-aligned bounds over every object plus the background image, with padding. */
1181
1560
  declare function chartBounds(doc: ChartDoc): {
1182
1561
  x: number;
@@ -1189,10 +1568,63 @@ declare const MAX_GA_CAPACITY = 100000;
1189
1568
  declare const MAX_EVENT_INVENTORY = 100000;
1190
1569
  /** Stable internal inventory label for one unit of a GA area's capacity. */
1191
1570
  declare function gaUnitLabel(areaId: string, index: number): string;
1571
+ /** Strict, bounded validation for durable Join Areas inventory provenance. */
1572
+ declare function validGAInventorySegments(area: GAAreaObject): boolean;
1573
+ /** Canonical source ranges. Invalid explicit provenance intentionally yields no
1574
+ * labels: chart validation blocks publication instead of inventing identities. */
1575
+ declare function gaInventorySegments(area: GAAreaObject): GAInventorySegment[];
1192
1576
  declare function gaUnitLabels(area: GAAreaObject): string[];
1577
+ /** Append-only capacity semantics for an already joined GA surface. */
1578
+ declare function growJoinedGAInventory(area: GAAreaObject, nextCapacity: number): GAInventorySegment[] | undefined;
1193
1579
  declare function gaAreasOf(doc: ChartDoc): GAAreaObject[];
1194
1580
  declare function isGaUnitLabel(label: string): boolean;
1195
1581
 
1582
+ /**
1583
+ * Real-world scale primitives — the ONE place the app fixes chart-unit ↔ metre ↔
1584
+ * renderer-world scale, and the section 3D-geometry resolver that rides on it.
1585
+ *
1586
+ * Everything that converts between chart units, metres, and renderer "world"
1587
+ * units derives from {@link METRES_PER_CHART_UNIT}. Renderer world units and
1588
+ * chart units are 1:1, so metres → world is exactly {@link CHART_UNITS_PER_METRE}
1589
+ * (the single m→world conversion constant Phase B consumers use).
1590
+ *
1591
+ * This is a leaf module (types only) so both `layout.ts` and `sections.ts` can
1592
+ * import it without the two forming an import cycle.
1593
+ */
1594
+
1595
+ /**
1596
+ * Metres of front-edge height one elevation tier represents. Chosen (not guessed)
1597
+ * so `elevation × TIER_HEIGHT_M` metres, scaled back through
1598
+ * {@link CHART_UNITS_PER_METRE}, equals the legacy `elevation × LIFT_PER_STEP`
1599
+ * world lift exactly — an un-authored chart stays pixel-identical. ≈ 1.329 m/tier.
1600
+ */
1601
+ declare const TIER_HEIGHT_M: number;
1602
+ /**
1603
+ * Resolve a section's real 3D geometry, applying the legacy-elevation fallback so
1604
+ * old charts and new charts share ONE code path. Every consumer (iso lift, 360°
1605
+ * eye-height, author lint, any 2D depth cue) must call this and never read the raw
1606
+ * {@link SectionObject.height}/{@link SectionObject.rake} fields — that is what
1607
+ * keeps un-authored charts rendering identically to today.
1608
+ *
1609
+ * - `height`: authored absolute metres if present, else owning-floor base height
1610
+ * plus `elevation × TIER_HEIGHT_M`.
1611
+ * - `rake`: authored degrees if present, else 0 (flat).
1612
+ *
1613
+ * Malformed values are bounded here as a last defensive barrier. Structural
1614
+ * validation still reports them so drafts can be repaired instead of silently
1615
+ * persisting a renderer-only interpretation.
1616
+ *
1617
+ * Pure — no document mutation, no side effects.
1618
+ */
1619
+ interface SectionGeometryContext {
1620
+ /** Absolute physical height of the owning floor above the venue datum. */
1621
+ floorBaseHeightM?: number;
1622
+ }
1623
+ declare function sectionGeometry(section: Pick<SectionObject, 'elevation' | 'height' | 'rake'>, context?: SectionGeometryContext): {
1624
+ height: number;
1625
+ rake: number;
1626
+ };
1627
+
1196
1628
  /**
1197
1629
  * Section membership + hide/show resolution (Batch 3.3).
1198
1630
  *
@@ -1267,6 +1699,116 @@ declare function hiddenObjectIds(doc: ChartDoc, hidden: ReadonlySet<string>): Se
1267
1699
  */
1268
1700
  declare function applyHidden(doc: ChartDoc, hidden: ReadonlySet<string>): ChartDoc;
1269
1701
 
1702
+ declare const RENDERED_QUALITY_REPORT_VERSION = 3;
1703
+ type RenderedEvidenceState = 'overview' | 'interaction';
1704
+ type RenderedQualityFindingCode = 'bookable-label-undersized' | 'bookable-label-contrast-low' | 'bookable-label-collision' | 'hierarchy-label-undersized' | 'hierarchy-label-contrast-low' | 'hierarchy-label-outside-section' | 'hierarchy-label-collision' | 'hierarchy-bookable-collision' | 'overview-section-category-paint' | 'overview-category-detail-visible' | 'overview-row-hints-visible' | 'overview-availability-clutter' | 'overview-ga-detail-visible' | 'free-text-undersized' | 'free-text-contrast-low' | 'free-text-collision' | 'free-text-bookable-collision' | 'free-text-hierarchy-collision' | 'ga-contrast-low' | 'detail-rung-missing' | 'detail-inventory-not-visible' | 'pointer-target-undersized' | 'pointer-target-inactive' | 'selected-state-missing' | 'selected-state-contrast-low' | 'held-state-missing' | 'booked-state-missing' | 'status-state-indistinct' | 'section-focus-missing' | 'category-filter-missing' | 'category-filter-ineffective' | 'target-category-not-visible' | 'target-category-filter-mismatch';
1705
+ interface RenderedQualitySample {
1706
+ primaryId: string;
1707
+ primaryLabel: string;
1708
+ secondaryId?: string;
1709
+ secondaryLabel?: string;
1710
+ measured?: number;
1711
+ minimum?: number;
1712
+ }
1713
+ interface RenderedQualityFinding {
1714
+ code: RenderedQualityFindingCode;
1715
+ message: string;
1716
+ count: number;
1717
+ samples: RenderedQualitySample[];
1718
+ }
1719
+ interface RenderedQualityReport {
1720
+ version: typeof RENDERED_QUALITY_REPORT_VERSION;
1721
+ passed: boolean;
1722
+ state: RenderedEvidenceState;
1723
+ /** Server-selected category exercised by a category-specific detail scene. */
1724
+ targetCategoryKey: string | null;
1725
+ /** Exact overview checks discharged by this browser-rendered report. */
1726
+ resolvedRules: string[];
1727
+ viewport: {
1728
+ width: number;
1729
+ height: number;
1730
+ };
1731
+ canvasBackground: string;
1732
+ effectiveScale: number;
1733
+ rung: RendererQualityEvidence['rung'];
1734
+ inventory: {
1735
+ totalBookableUnits: number;
1736
+ totalLabelledBookableUnits: number;
1737
+ visibleBookableLabels: number;
1738
+ hiddenBookableLabels: number;
1739
+ visibleHierarchyLabels: number;
1740
+ visibleFreeTextLabels: number;
1741
+ visibleGAAreas: number;
1742
+ };
1743
+ overviewStyle?: RendererQualityEvidence['overviewStyle'];
1744
+ /**
1745
+ * Coordinate-free identity evidence from the exact buyer scene. The Worker
1746
+ * uses this to compare persisted reference semantics with what Chromium
1747
+ * actually painted, without exposing screen boxes or accepting geometry.
1748
+ */
1749
+ composition: {
1750
+ hierarchy: Array<{
1751
+ id: string;
1752
+ kind: 'section' | 'zone';
1753
+ label: string;
1754
+ visible: boolean;
1755
+ }>;
1756
+ labelledObjects: Array<{
1757
+ objectId: string;
1758
+ kind: RenderedFreeTextEvidence['kind'];
1759
+ text: string;
1760
+ visible: boolean;
1761
+ }>;
1762
+ gaAreas: Array<{
1763
+ areaId: string;
1764
+ label: string;
1765
+ categoryKey: string;
1766
+ sectionId?: string;
1767
+ visible: boolean;
1768
+ }>;
1769
+ bookableSectionIds: string[];
1770
+ categoryKeys: string[];
1771
+ /** Categories whose non-dimmed bookable paint is visible in this scene. */
1772
+ activeCategoryKeys: string[];
1773
+ };
1774
+ metrics: {
1775
+ minimumRenderedBookableLabelPx: number | null;
1776
+ minimumBookableLabelContrast: number | null;
1777
+ minimumRenderedHierarchyLabelPx: number | null;
1778
+ minimumHierarchyLabelContrast: number | null;
1779
+ minimumRenderedFreeTextPx: number | null;
1780
+ minimumFreeTextContrast: number | null;
1781
+ minimumGAContrast: number | null;
1782
+ minimumEffectivePointerTargetPx: number | null;
1783
+ selectedRingContrast: number | null;
1784
+ };
1785
+ interaction: {
1786
+ applicable: {
1787
+ detail: boolean;
1788
+ pointer: boolean;
1789
+ held: boolean;
1790
+ booked: boolean;
1791
+ sectionFocus: boolean;
1792
+ categoryFilter: boolean;
1793
+ };
1794
+ selectedUnits: number;
1795
+ heldUnits: number;
1796
+ bookedUnits: number;
1797
+ activePointerTargets: number;
1798
+ focusedSectionId: string | null;
1799
+ focusBackdropVisible: boolean;
1800
+ categoryFilterKeys: string[] | null;
1801
+ };
1802
+ findings: RenderedQualityFinding[];
1803
+ }
1804
+ /**
1805
+ * Inspect what the buyer renderer actually decided to paint at one exact
1806
+ * viewport. This is deliberately downstream of ChartDoc validation: screen
1807
+ * size, LOD visibility, fitted hierarchy text, transient paint, and label
1808
+ * collisions cannot be proven from stored geometry alone.
1809
+ */
1810
+ declare function inspectRenderedQualityEvidence(evidence: RendererQualityEvidence, state?: RenderedEvidenceState, targetCategoryKey?: string | null): RenderedQualityReport;
1811
+
1270
1812
  /**
1271
1813
  * SeatmapRenderer — the shared canvas rendering core (buyer picker shell).
1272
1814
  *
@@ -1285,8 +1827,17 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1285
1827
  private seatLayer;
1286
1828
  private overlayLayer;
1287
1829
  private labelGroup;
1830
+ /** Per-elevated-section label containers. Keeping the labels grouped lets the
1831
+ * Phase-B lift move thousands of seat labels with one transform per section
1832
+ * instead of rewriting every label on every tween frame. */
1833
+ private labelLiftGroups;
1834
+ /** Foreground decor images — drawn on the overlay layer, ABOVE the seats. */
1835
+ private fgDecorGroup;
1288
1836
  private seats;
1289
1837
  private seatById;
1838
+ /** One build per chart/floor; section membership queries only inspect seats in
1839
+ * the section bounds instead of rescanning the full 13k venue per section. */
1840
+ private seatIndex;
1290
1841
  /** Multi-floor (Batch 5): the last-set chart + which floor we're rendering. */
1291
1842
  private chartDoc;
1292
1843
  private activeFloorId;
@@ -1302,13 +1853,11 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1302
1853
  private seatLabelById;
1303
1854
  /** Coloured accommodation ring per accessible seat (few per chart). */
1304
1855
  private accessRingById;
1305
- /** Centred accessibility glyph per accessible seat shown once the seat is
1306
- * big enough on-screen (see {@link SEAT_GLYPH_MIN_PX}); the ring is the
1307
- * smaller-zoom fallback. Kept in a map so zoom toggles touch only the handful
1308
- * of accessible seats, never all 13k nodes. */
1856
+ /** Centred glyph per physical wheelchair provision. It keeps a small
1857
+ * screen-space floor at every LOD and grows when the Wheelchair filter is
1858
+ * active. Kept in a map so zoom updates touch only the handful of matching
1859
+ * seats, never all 13k nodes. */
1309
1860
  private accessGlyphById;
1310
- /** Whether the accessibility glyph is legible at the current camera scale. */
1311
- private accessGlyphVisible;
1312
1861
  /** Authored free-text nodes obey the same rendered-size visibility floor. */
1313
1862
  private freeTextById;
1314
1863
  /** Stage/rink landmarks retain a readable screen-space caption at overview. */
@@ -1334,6 +1883,8 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1334
1883
  /** One selected seat being inspected before it is committed to the cart. */
1335
1884
  private selectionFocusId;
1336
1885
  private hoverRing;
1886
+ /** Seat currently owning the shared hover ring (null while not hovering). */
1887
+ private hoveredId;
1337
1888
  /** Keyboard-navigation focus ring + the currently focused seat id. */
1338
1889
  private focusRing;
1339
1890
  private focusedId;
@@ -1352,6 +1903,8 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1352
1903
  private sections;
1353
1904
  private zones;
1354
1905
  private seatSection;
1906
+ /** Seats outside every authored section retain the legacy path in one group. */
1907
+ private unsectionedSeatGroup;
1355
1908
  private catPrice;
1356
1909
  /** ISO 4217 currency for on-map "FROM …" prices (undefined ⇒ money default). */
1357
1910
  private currency;
@@ -1366,6 +1919,9 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1366
1919
  private focusedSectionId;
1367
1920
  /** Light backdrop panel drawn behind the focused section (removed on clear). */
1368
1921
  private focusBackdrop;
1922
+ /** Non-listening visual dim outside the focused section. One overlay replaces
1923
+ * descendant opacity mutations across every seat in the venue. */
1924
+ private focusDimOverlay;
1369
1925
  /** Object id → floor id (multi-floor only) — resolves a deck tap in the 3D stack. */
1370
1926
  private objectFloor;
1371
1927
  /** Zone id → colour (drives extruded side faces in iso view). */
@@ -1377,10 +1933,33 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1377
1933
  private isoT;
1378
1934
  private isoTarget;
1379
1935
  private isoRaf;
1936
+ /** Public view target; perspective is a separate non-affine Phase-C lane. */
1937
+ private viewMode;
1938
+ private perspectiveCamera;
1939
+ /** Ground-plane tangent applied once to every renderer layer. */
1940
+ private perspectiveBaseAffine;
1941
+ private perspectiveBaseInverse;
1942
+ /** Exact pinhole anchor expressed in the overlay tangent-layer coordinates. */
1943
+ private perspectiveSeatLocal;
1944
+ /** Exact projected point (before Stage pan/zoom), keyed by seat. */
1945
+ private perspectiveSeatProjected;
1946
+ /** Positive camera depth, used for deterministic far→near paint order. */
1947
+ private perspectiveSeatDepth;
1948
+ /** Exact pinhole size ratio at each seat anchor. */
1949
+ private perspectiveSeatScale;
1950
+ /** Seats whose native Konva nodes currently carry the exact projected pose.
1951
+ * Large sectioned venues apply these lazily as a section enters the live-seat
1952
+ * viewport; the overview rung hides the seat layer completely. */
1953
+ private perspectiveAppliedSeats;
1954
+ private perspectiveBounds;
1380
1955
  /** Chart centre the iso projection pivots about (bounds centre). */
1381
1956
  private isoCentre;
1382
1957
  /** rAF for an in-flight camera glide (focusRegion / setRung); 0 = none. */
1383
1958
  private glideRaf;
1959
+ /** While the camera tween is active, defer polygon label fitting/collision to
1960
+ * the settled frame. Geometry remains smoothly stage-scaled, while expensive
1961
+ * point-in-polygon text work no longer repeats at 120 Hz. */
1962
+ private glideInProgress;
1384
1963
  /** Set in destroy() so an in-flight iso tween bails. */
1385
1964
  private destroyed;
1386
1965
  /** Cached scale the section/zone labels were last sized for (scale-compensation). */
@@ -1407,6 +1986,28 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1407
1986
  private panStarted;
1408
1987
  /** Maximum displacement from gesture start — suppress taps only after a real pan/pinch. */
1409
1988
  private moved;
1989
+ /**
1990
+ * Ghost-click guard. Konva binds `_pointerup` to mouseup, touchend AND
1991
+ * pointerup, so `on('click tap')` is two handlers for one finger tap unless
1992
+ * the browser's synthesized compatibility click is suppressed. Konva only
1993
+ * suppresses it when the touch lands on a LISTENING SHAPE (Stage.js returns
1994
+ * early before its preventDefault() when `!shape || !shape.isListening()`),
1995
+ * so a near-miss rescued by nearestSeatToScreen used to fire `tap` (select)
1996
+ * then `click` (deselect) 1-3ms apart and net to nothing. With
1997
+ * SEAT_TAP_SLOP_PX=14 against a ~12px seat radius, that broken annulus is
1998
+ * ~3.7x the area of the seat itself — i.e. most successful mobile taps.
1999
+ * `touch-action: none` does NOT suppress compatibility mouse events.
2000
+ */
2001
+ private lastTapAt;
2002
+ /**
2003
+ * True when this is the browser's synthesized compatibility click following a
2004
+ * finger tap we already handled. Discriminates on `e.type` deliberately —
2005
+ * `PointerEvent extends MouseEvent`, so an `instanceof MouseEvent` test
2006
+ * matches genuine pointer taps too. One shared timestamp across the layer and
2007
+ * stage handlers: a tap consumed at the layer must also suppress the ghost at
2008
+ * the stage, and only `click` is ever rejected, so bubbling stays intact.
2009
+ */
2010
+ private isGhostClick;
1410
2011
  /**
1411
2012
  * Manage-mode rubber-band marquee (option-gated). `start`/`cur` are WORLD-space
1412
2013
  * points (overlayLayer rides the stage transform); `rect` is the on-canvas
@@ -1421,6 +2022,9 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1421
2022
  /** The chart to render: all floors stacked (3D overview), the active floor, or
1422
2023
  * the whole chart for single-floor charts. */
1423
2024
  private floorView;
2025
+ /** Physical floor height is authored data; the 900-unit stacked overview
2026
+ * spread is presentation-only and must never leak into real 3D geometry. */
2027
+ private floorBaseHeightMFor;
1424
2028
  /** Toggle the 3D all-floors stacked overview (Batch 5). Re-renders; no-op on
1425
2029
  * single-floor charts. The caller re-applies statuses + animates the iso view. */
1426
2030
  setStacked(on: boolean): void;
@@ -1533,9 +2137,9 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1533
2137
  * geometry stays flat, so hit-testing (Konva's own, plus the manual section
1534
2138
  * inverse) keeps landing on the projected seats/sections.
1535
2139
  */
1536
- setViewMode(mode: 'flat' | 'isometric'): void;
2140
+ setViewMode(mode: RendererViewMode): void;
1537
2141
  /** Current projection — reflects the tween target, not the mid-tween isoT. */
1538
- getViewMode(): 'flat' | 'isometric';
2142
+ getViewMode(): RendererViewMode;
1539
2143
  /** Iso angle (rad) + y-squash for the current isoT. */
1540
2144
  private isoParams;
1541
2145
  /** Effective vertical scale = stage scale × iso squash — legibility math uses this. */
@@ -1544,14 +2148,40 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1544
2148
  private isoForward;
1545
2149
  /** Inverse of isoForward (iso-world → world) for screen-space hit-testing. */
1546
2150
  private isoInverse;
2151
+ /** Precompute the Phase-C pinhole camera and every exact seat anchor. */
2152
+ private buildPerspectiveProjection;
2153
+ /** Apply an affine to a Konva container while keeping `pivot` fixed logically. */
2154
+ private setContainerAffine;
2155
+ private resetContainerTransform;
2156
+ /** Exact raked surface height used for the bounded section tangent plane. */
2157
+ private sectionSurfaceHeight;
2158
+ /** Desired perspective-space point for a section surface, before Stage pan/zoom. */
2159
+ private projectedSectionPoint;
2160
+ /** Shared-layer local point which the ground affine paints at `projected`. */
2161
+ private perspectiveLocalPoint;
1547
2162
  /**
1548
2163
  * Local-space offset that, once the layer applies the iso affine, lifts an
1549
- * object straight UP in iso-world by `elevation × LIFT_PER_STEP × isoT`
1550
- * (= inverse-linear of the pure vertical lift). Zero at isoT=0.
2164
+ * object straight UP in iso-world by `liftWorld × isoT` world units
2165
+ * (= inverse-linear of the pure vertical lift). Zero at isoT=0. `liftWorld` is
2166
+ * the section's resolved real height in world units (Phase B1).
1551
2167
  */
1552
2168
  private isoLiftLocal;
1553
- /** Restore the three layers to identity (flat) — byte-for-byte the original. */
2169
+ /** Restore projection layers to identity (flat) — byte-for-byte the original.
2170
+ * `seatLayer` can be skipped when entering perspective from flat: assigning a
2171
+ * top-level transform invalidates all 14k descendant absolute transforms even
2172
+ * while the semantic overview keeps that layer fully transparent. */
1554
2173
  private resetLayerTransforms;
2174
+ /** Restore section-owned containers before applying another projection. */
2175
+ private resetSectionProjection;
2176
+ /** Move native seat/booth hit shapes to or from their exact pinhole anchors. */
2177
+ private applyExactSeatAnchors;
2178
+ /**
2179
+ * Phase C projected 2.5D. Seat/booth anchors are exact pinhole projections;
2180
+ * section top surfaces use one explicitly bounded tangent plane per authored
2181
+ * section. Native Konva shapes move with the anchors, so direct hit testing is
2182
+ * the same graph that paints the buyer-visible unit.
2183
+ */
2184
+ private applyPerspective;
1555
2185
  /**
1556
2186
  * Apply the current isoT to the scene: the base rotate+squash as a decomposed
1557
2187
  * layer transform (so seats/décor/rings project together and Konva's own
@@ -1571,6 +2201,32 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1571
2201
  * tier shifts with one offset in iso view) or the seat layer directly.
1572
2202
  */
1573
2203
  private seatContainer;
2204
+ /** True when a section belongs to the active logical section/zone focus. */
2205
+ private sectionMatchesFocus;
2206
+ /** Visual opacity after the section-focus overlay is composed. */
2207
+ private focusedSeatOpacity;
2208
+ /** Project a section outline through elevation + the active affine view and
2209
+ * then into viewport pixels. The result is conservative (AABB), so a visible
2210
+ * section is never clipped even for rotated or isometric outlines. */
2211
+ private sectionScreenBounds;
2212
+ /** Hide only section groups wholly outside a padded viewport at the live-seat
2213
+ * rung. Overview caching always restores all groups first, so panning a
2214
+ * cached whole-venue bitmap can never reveal missing inventory. */
2215
+ private updateSeatGroupVisibility;
2216
+ /** Seats worth considering for viewport labels. Section culling is a safe
2217
+ * coarse index; the later screen test remains the exact filter. */
2218
+ private visibleSeatCandidates;
2219
+ private seatViewportCulled;
2220
+ /** Local world coordinate at which an elevated seat is actually painted for
2221
+ * the current iso tween. Flat seats and the 2D endpoint are unchanged. */
2222
+ private renderedSeatPoint;
2223
+ /** Overlay labels mirror the seat-layer lift without an O(seats) per-frame
2224
+ * rewrite. The groups are rebuilt with the zoom-dependent label set. */
2225
+ private seatLabelContainer;
2226
+ /** Shared overlay furniture is not parented to section lift groups, so keep
2227
+ * the small transient set aligned explicitly. Selection is capped in buyer
2228
+ * mode, making this constant-sized work during the iso tween. */
2229
+ private syncSeatOverlayPositions;
1574
2230
  private renderSeats;
1575
2231
  /** A booth renders as a click-selectable rounded block (dims from the doc). */
1576
2232
  private renderBoothUnit;
@@ -1581,12 +2237,14 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1581
2237
  */
1582
2238
  private buildAccessGlyph;
1583
2239
  /**
1584
- * Toggle the accessibility glyphs for the current camera scale: shown once the
1585
- * effective on-screen seat radius clears {@link SEAT_GLYPH_MIN_PX}, otherwise
1586
- * hidden so only the ring remains. Iterates the (small) accessible-seat set,
1587
- * never the full node graph, so it is cheap to call on every view change.
2240
+ * Keep physical wheelchair provision readable in screen space at every map
2241
+ * LOD. The active Wheelchair filter promotes it further. Iterates the small
2242
+ * wheelchair-seat set, never the full node graph.
1588
2243
  */
1589
2244
  private updateAccessGlyphs;
2245
+ /** True only for a physical wheelchair provision matching an active buyer filter. */
2246
+ private accessGlyphFilterEmphasized;
2247
+ private accessGlyphShouldShow;
1590
2248
  /**
1591
2249
  * Whether an accessible seat's glyph should show for its current status. It is
1592
2250
  * hidden on seats a buyer cannot take (sold, or another buyer's hold) where the
@@ -1639,6 +2297,11 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
1639
2297
  getFocusedSection(): string | null;
1640
2298
  /** Draw (or replace) the light backdrop panel behind the focused section. */
1641
2299
  private drawFocusBackdrop;
2300
+ /** Dim everything outside the active section with one paint-only shape. Group
2301
+ * opacity would make Konva recursively invalidate absolute-opacity caches on
2302
+ * all 14k descendants. The cut-out follows the live elevation offset and is
2303
+ * non-listening, so direct seat hit-testing is unchanged. */
2304
+ private drawFocusDimOverlay;
1642
2305
  /** Repaint every seat + section block to reflect closed/focus state, then redraw. */
1643
2306
  private repaintSectionsAndSeats;
1644
2307
  /** The world-space rectangle currently visible in the viewport (minimap F3). */
@@ -1864,6 +2527,16 @@ interface PickerSeat {
1864
2527
  /** Buyer-facing label (row `displayLabel` applied). Falls back to `label`;
1865
2528
  * never use for booking — `label` is the inventory/booking identity. */
1866
2529
  displayLabel?: string;
2530
+ /** Buyer-facing object type word authored in Designer. Pure presentation. */
2531
+ displayType?: string;
2532
+ /** @deprecated Use `displayType`; retained for source compatibility. */
2533
+ rowType?: string;
2534
+ /** Stable parent chart-object id (row/table/booth), separate from seat id. */
2535
+ objectId?: string;
2536
+ /** Buyer-facing spatial context shared by selection, hold, and hover callbacks. */
2537
+ sectionLabel?: string;
2538
+ rowLabel?: string;
2539
+ seatNumber?: string;
1867
2540
  categoryKey: string;
1868
2541
  /** Price for the chosen tier when the category has tiers, else the base price. */
1869
2542
  price: number;
@@ -1875,6 +2548,29 @@ interface PickerSeat {
1875
2548
  * note) resolved for this seat; absent when the seat carries none. Lets the
1876
2549
  * buyer cart + confirm surface flag limited-view/premium seats. */
1877
2550
  commercial?: SeatCommercialAttributes;
2551
+ /** Accessibility semantics exposed to SDK callbacks and buyer summaries. */
2552
+ accessibility?: AccessibilityType[];
2553
+ /** Explicit physical wheelchair chair/bay distinction. */
2554
+ wheelchairSpaceType?: 'seat-present' | 'no-seat';
2555
+ /** Physical/bookable source kind; booking identity remains `label`. */
2556
+ objectType?: 'seat' | 'booth' | 'table';
2557
+ /** Grouped-table booking contract; absent for normal chair inventory. */
2558
+ bookingMode?: 'whole' | 'variable';
2559
+ /** Guest quantity represented by this one atomic table line. */
2560
+ quantity?: number;
2561
+ capacity?: number;
2562
+ minOccupancy?: number;
2563
+ maxOccupancy?: number;
2564
+ }
2565
+ /** Accessible quantity/confirmation payload for one model-2 table selection. */
2566
+ interface TableSelectionDetails extends PickerSeat {
2567
+ objectType: 'table';
2568
+ bookingMode: 'whole' | 'variable';
2569
+ quantity: number;
2570
+ capacity: number;
2571
+ minOccupancy: number;
2572
+ maxOccupancy: number;
2573
+ physicalSeatIds: string[];
1878
2574
  }
1879
2575
  /**
1880
2576
  * Rich hover payload for seat tooltips — the PickerSeat plus everything a
@@ -1886,10 +2582,20 @@ interface SeatHoverDetails extends PickerSeat {
1886
2582
  categoryColor: string;
1887
2583
  status: SeatStatus;
1888
2584
  currency: string;
1889
- /** Human-readable spatial context for hover and confirmation UI. */
1890
- sectionLabel?: string;
1891
- rowLabel?: string;
1892
- seatNumber?: string;
2585
+ }
2586
+ interface PickerGAArea {
2587
+ id: string;
2588
+ /** Stable technical/inventory prefix. */
2589
+ label: string;
2590
+ /** Buyer-facing area name and type; absent fields use UI defaults. */
2591
+ displayLabel?: string;
2592
+ displayType?: string;
2593
+ capacity: number;
2594
+ available: number;
2595
+ categoryKey: string;
2596
+ price: number;
2597
+ currency: string;
2598
+ tiers?: CategoryTier[];
1893
2599
  }
1894
2600
  interface HoldConflict {
1895
2601
  label: string;
@@ -1923,6 +2629,8 @@ interface SectionSummary {
1923
2629
  label: string;
1924
2630
  /** Zone label (from ChartDoc.zones); '' when the section has no zone. */
1925
2631
  zoneLabel: string;
2632
+ /** Buyer-facing entrance/door hint ("Entrance X"); absent when unset. */
2633
+ entrance?: string;
1926
2634
  /** Mix/section colour for the card's dot (section.color → zone colour → dominant category). */
1927
2635
  color: string;
1928
2636
  /** Free member seats right now. */
@@ -1943,7 +2651,7 @@ interface ResumeHoldResponse extends HoldResponse {
1943
2651
  interface HoldServerItem {
1944
2652
  label: string;
1945
2653
  objectId: string;
1946
- objectType: 'seat' | 'booth' | 'ga';
2654
+ objectType: 'seat' | 'booth' | 'ga' | 'table';
1947
2655
  categoryKey: string;
1948
2656
  tierId: string | null;
1949
2657
  unitPrice: number;
@@ -1953,6 +2661,7 @@ interface HoldServerItem {
1953
2661
  interface HoldSelectionRequest {
1954
2662
  label: string;
1955
2663
  tierId?: string | null;
2664
+ quantity?: number;
1956
2665
  }
1957
2666
  interface BestAvailableResponse extends HoldResponse {
1958
2667
  labels: string[];
@@ -1969,6 +2678,7 @@ interface PickerTransport {
1969
2678
  startsAt?: number | null;
1970
2679
  currency?: string;
1971
2680
  mode?: string;
2681
+ inventoryModelVersion?: 1 | 2;
1972
2682
  };
1973
2683
  }>;
1974
2684
  objects(key: string): Promise<{
@@ -1977,7 +2687,7 @@ interface PickerTransport {
1977
2687
  closed?: string[];
1978
2688
  }>;
1979
2689
  hold(key: string, selections: HoldSelectionRequest[], ttlMs?: number, replaceHoldId?: string): Promise<HoldResponse>;
1980
- bestAvailable(key: string, qty: number, categoryKey?: string): Promise<BestAvailableResponse>;
2690
+ bestAvailable(key: string, qty: number, categoryKey?: string, zoneId?: string): Promise<BestAvailableResponse>;
1981
2691
  release(key: string, labels: string[], holdId: string): Promise<unknown>;
1982
2692
  /** Optional capability-style lookup used to restore an active browser hold. */
1983
2693
  resume?(key: string, holdId: string): Promise<ResumeHoldResponse>;
@@ -1994,6 +2704,8 @@ interface PickerTransport {
1994
2704
  interface PickerCallbacks extends RendererCallbacks {
1995
2705
  /** Selection changed (manual clicks or a server best-available pick). */
1996
2706
  onSelectionChange?: (seats: PickerSeat[]) => void;
2707
+ /** A grouped table was selected and needs buyer confirmation/guest quantity. */
2708
+ onTableSelectionRequest?: (table: TableSelectionDetails) => void;
1997
2709
  /** A hold opened (manual hold or best-available). */
1998
2710
  onHold?: (hold: HoldInfo) => void;
1999
2711
  /** A prior active hold was restored from its opaque hold id. */
@@ -2075,6 +2787,14 @@ declare class PickerController {
2075
2787
  /** id → buyer-facing spatial metadata used by every tooltip/confirm surface. */
2076
2788
  private seatContext;
2077
2789
  private allIds;
2790
+ /** Model-2 tables are one booking unit whose chairs remain renderer geometry. */
2791
+ private groupedTablesByObject;
2792
+ private groupedTablesByLabel;
2793
+ private groupedTableBySeatId;
2794
+ private tableQuantities;
2795
+ /** Variable quantity is a buyer contract, never an inferred default. */
2796
+ private confirmedVariableTables;
2797
+ private groupedSelectionOverhead;
2078
2798
  private ws;
2079
2799
  private reconnectTimer;
2080
2800
  private attempt;
@@ -2102,6 +2822,10 @@ declare class PickerController {
2102
2822
  getRenderer(): ISeatmapRenderer | null;
2103
2823
  seatByLabel(label: string): ExpandedSeat | undefined;
2104
2824
  idForLabel(label: string): string | undefined;
2825
+ private idsForLabel;
2826
+ private idsForLabels;
2827
+ /** Resolve a physical chair id (or table inventory label) to its table unit. */
2828
+ tableSelection(seatIdOrLabel: string): TableSelectionDetails | null;
2105
2829
  /** Fetch chart, build label maps, mount the renderer, seed statuses, go live. */
2106
2830
  render(host: HTMLDivElement): Promise<{
2107
2831
  doc: ChartDoc;
@@ -2120,6 +2844,17 @@ declare class PickerController {
2120
2844
  deselect(ids: string[]): void;
2121
2845
  setMaxSelection(maxSelection: number): void;
2122
2846
  select(ids: string[]): PickerSeat[];
2847
+ /** Confirm/update the guest quantity for a selected variable table. */
2848
+ setTableQuantity(label: string, quantity: number): boolean;
2849
+ /** Atomically replace an active variable-table hold with a new guest count. */
2850
+ replaceTableQuantity(label: string, quantity: number): Promise<HoldInfo | null>;
2851
+ private rendererSelectionCap;
2852
+ private selectionGuestCount;
2853
+ private handleRendererSelect;
2854
+ private handleRendererDeselect;
2855
+ private resetUnheldTableQuantities;
2856
+ private resetTableQuantitiesForLabels;
2857
+ private tableQuantityRequest;
2123
2858
  /**
2124
2859
  * Hold the current selection (or a given label set). The controller does the
2125
2860
  * renderer/hold side (409 → deselect + repaint the taken seats held) and then
@@ -2146,16 +2881,7 @@ declare class PickerController {
2146
2881
  categoryAvailability(): Record<string, number>;
2147
2882
  /** Seat ids belonging to a currently-closed section (excluded from counts). */
2148
2883
  private closedMemberIds;
2149
- getGAAreas(): {
2150
- id: string;
2151
- label: string;
2152
- capacity: number;
2153
- available: number;
2154
- categoryKey: string;
2155
- price: number;
2156
- currency: string;
2157
- tiers?: CategoryTier[];
2158
- }[];
2884
+ getGAAreas(): PickerGAArea[];
2159
2885
  /** Select a quantity from one GA area and hold it atomically. */
2160
2886
  holdGA(areaId: string, qty: number, options?: {
2161
2887
  tierId?: string | null;
@@ -2165,6 +2891,11 @@ declare class PickerController {
2165
2891
  * flag — gates the buyer widget's "★ Best seats" premium quick-pick (chip is
2166
2892
  * present-only, exactly like the accessibility filter chips). */
2167
2893
  hasPremiumSeats(): boolean;
2894
+ /** Zones which still own visible buyer inventory, in authored order. */
2895
+ getBestAvailableZones(): Array<{
2896
+ id: string;
2897
+ label: string;
2898
+ }>;
2168
2899
  /**
2169
2900
  * Client-side premium pre-pass: find the best contiguous block of `qty`
2170
2901
  * PREMIUM-flagged free seats (orphan-avoiding, closest to the focal point)
@@ -2182,6 +2913,7 @@ declare class PickerController {
2182
2913
  */
2183
2914
  bestAvailable(qty: number, categoryKey?: string, opts?: {
2184
2915
  preferPremium?: boolean;
2916
+ zoneId?: string;
2185
2917
  }): Promise<HoldInfo | null>;
2186
2918
  /**
2187
2919
  * Complete a booking. Requires a transport that supports book() (the SDK
@@ -2237,9 +2969,9 @@ declare class PickerController {
2237
2969
  /** Tap-a-deck-to-enter: leave the 3D stack, drop to flat 2D on `floorId`, and
2238
2970
  * tell the host so it can sync its 2D/3D toggle + floor-switcher state. */
2239
2971
  private handleDeckTap;
2240
- /** Switch the map projection (2D flat ⇄ 3D isometric). No-op on a flat renderer. */
2241
- setViewMode(mode: 'flat' | 'isometric'): void;
2242
- getViewMode(): 'flat' | 'isometric';
2972
+ /** Switch the map projection. No-op on a flat-only renderer. */
2973
+ setViewMode(mode: RendererViewMode): void;
2974
+ getViewMode(): RendererViewMode;
2243
2975
  /** Current LOD rung (for the ZONES/SECTIONS/SEATS pill). */
2244
2976
  getRung(): LodRung;
2245
2977
  /** Jump to a rung; ZONES clears any focused summary (back to overview). */
@@ -2286,6 +3018,7 @@ declare class PickerController {
2286
3018
  private sectionSummary;
2287
3019
  destroy(): void;
2288
3020
  private toSeat;
3021
+ private toTableSeat;
2289
3022
  /** Tooltip payload for a hovered seat — see PickerCallbacks.onSeatHover. */
2290
3023
  private describeSeat;
2291
3024
  private priceFor;
@@ -2438,4 +3171,4 @@ declare function formatMoney(amount: number, currency?: string, fractionDigits?:
2438
3171
  /** Bare symbol for input adornments ("€", "$", "₹"). */
2439
3172
  declare function currencySymbol(currency?: string): string;
2440
3173
 
2441
- export { ACCESSIBILITY_RING_COLOR, ACCESSIBILITY_TYPES, type AccessibilityMeta, type AccessibilityType, type AvailabilityRule, type BoothObject, CHART_STORAGE_KEY, type Category, type CategoryTier, type ChartDoc, type ChartObject, type ChartReferenceImage, type ChartTheme, type CubicPath, DEFAULT_CURRENCY, type DecorImageObject, type Dict, type ExpandedSeat, type Floor, type GAAreaObject, type HoldConflict, type HoldInfo, type HoldSelectionRequest, type HoldServerItem, type ISeatmapRenderer, LABEL_STYLE_MAX_SIZE, LABEL_STYLE_MIN_SIZE, type LabelPresentation, type LabelStyle, type Locale, type LodRung, MAX_EVENT_INVENTORY, MAX_GA_CAPACITY, type PanoramaResult, type PickerCallbacks, PickerController, type PickerOptions, type PickerSeat, type PickerTransport, type Point, type ReferenceAccessibilitySource, type ReferenceCalibration, type ReferenceCategorySource, type ReferenceDerivedScale, type ReferenceInventoryExclusionSource, type ReferenceInventorySource, type RenderedBookableLabelEvidence, type RenderedFreeTextEvidence, type RenderedGAAreaEvidence, type RenderedHierarchyLabelEvidence, type RenderedLabelHiddenReason, type RendererCallbacks, type RendererOptions, type RendererQualityEvidence, type RowObject, type RowSeatSlot, SUPPORTED_LOCALES, type SeatCommercialAttributes, type SeatHoverDetails, type SeatOverride, type SeatStatus, SeatmapRenderer, type SectionCategory, type SectionNode, type SectionObject, type SectionOutlinePath, type SectionPathSegment, type SectionSummary, type SelectionLayer, type ShapeObject, type TableObject, type TextObject, UNGROUPED_ID, type ZoneDef, accessibilityMeta, accessibilityRingColor, allObjects, applyHidden, chartBounds, computeSections, createRenderer, currencySymbol, expandBooth, expandChart, expandRow, expandRowSlots, expandTable, floorObjects, floorsOf, formatDate, formatMoney, gaAreasOf, gaUnitLabel, gaUnitLabels, generateSeatPanorama, generateSeatThumb, getLocale, hiddenObjectIds, isGaUnitLabel, isSectionHidden, layerOf, loadLocale, objectCenter, pointInPolygon, pointInPolygonWithHoles, polygonLabelPoint, resolveLocale, rowSeatPositions, seatLabelPart, setLocale, setMoneyLocale, setStringOverrides, stackFloors, t, tCount };
3174
+ export { ACCESSIBILITY_RING_COLOR, ACCESSIBILITY_TYPES, type AccessibilityMeta, type AccessibilityType, type AvailabilityRule, type BoothObject, CHART_STORAGE_KEY, type Category, type CategoryTier, type ChartDoc, type ChartObject, type ChartReferenceImage, type ChartTheme, type CubicPath, DEFAULT_CURRENCY, type DecorImageObject, type Dict, type ExpandChartOptions, type ExpandedSeat, type Floor, type GAAreaObject, type GAInventorySegment, type HoldConflict, type HoldInfo, type HoldSelectionRequest, type HoldServerItem, type ISeatmapRenderer, LABEL_STYLE_MAX_SIZE, LABEL_STYLE_MIN_SIZE, type LabelPresentation, type LabelStyle, type Locale, type LodRung, MAX_EVENT_INVENTORY, MAX_GA_CAPACITY, type PanoramaResult, type PickerCallbacks, PickerController, type PickerGAArea, type PickerOptions, type PickerSeat, type PickerTransport, type Point, RENDERED_QUALITY_REPORT_VERSION, type RectTableSeatCounts, type RectTableSide, type ReferenceAccessibilitySource, type ReferenceCalibration, type ReferenceCategorySource, type ReferenceDerivedScale, type ReferenceInventoryExclusionSource, type ReferenceInventorySource, type ReferenceScanProposal, type ReferenceScanRowProposal, type ReferenceScanSectionProposal, type ReferenceSeatSeed, type RenderedBookableLabelEvidence, type RenderedEvidenceState, type RenderedFreeTextEvidence, type RenderedGAAreaEvidence, type RenderedHierarchyLabelEvidence, type RenderedLabelHiddenReason, type RenderedQualityFinding, type RenderedQualityFindingCode, type RenderedQualityReport, type RenderedQualitySample, type RendererCallbacks, type RendererOptions, type RendererQualityEvidence, type RendererViewMode, type RowObject, type RowSeatSlot, SUPPORTED_LOCALES, SURROUNDINGS_SHAPE_ROLES, type SeatCommercialAttributes, type SeatHoverDetails, type SeatOverride, type SeatStatus, SeatmapRenderer, type SectionCategory, type SectionNode, type SectionObject, type SectionOutlinePath, type SectionPathSegment, type SectionSummary, type SelectionLayer, type ShapeLineCap, type ShapeLineEnding, type ShapeLineJoin, type ShapeObject, TIER_HEIGHT_M, type TableObject, type TableSeatSlot, type TableSelectionDetails, type TextObject, UNGROUPED_ID, type ZoneDef, accessibilityMeta, accessibilityRingColor, allObjects, applyHidden, chartBounds, computeSections, createRenderer, currencySymbol, expandBooth, expandChart, expandRow, expandRowSlots, expandTable, expandTableSlots, floorObjects, floorsOf, formatDate, formatMoney, gaAreasOf, gaInventorySegments, gaUnitLabel, gaUnitLabels, generateSeatPanorama, generateSeatThumb, getLocale, growJoinedGAInventory, hiddenObjectIds, inspectRenderedQualityEvidence, isGaUnitLabel, isSectionHidden, layerOf, loadLocale, objectCenter, owningSectionForObject, pointInPolygon, pointInPolygonWithHoles, polygonLabelPoint, resolveLocale, rowInventoryCount, rowSeatPositions, seatLabelPart, sectionGeometry, setLocale, setMoneyLocale, setStringOverrides, stackFloors, t, tCount, tableInventoryCount, tableSeatCountsBySide, validGAInventorySegments };