@crystaldesign/real-time-viewer-next 26.12.0-beta.7 → 26.12.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/build/esm/{DeckCutoutSubsystem-5d0fb500.js → DeckCutoutSubsystem-34516143.js} +3 -1
  2. package/build/esm/{SunLensflare-9cbd6d72.js → SunLensflare-55ce48ee.js} +1 -1
  3. package/build/esm/{WaterSurface-715ff751.js → WaterSurface-ff270e38.js} +1 -1
  4. package/build/esm/{index-cc012549.js → index-8c31a811.js} +162 -43
  5. package/build/esm/index.js +1 -1
  6. package/build/types/real-time-viewer-next/src/RealTimeRendererNext.d.ts +5 -0
  7. package/build/types/real-time-viewer-next/src/RealTimeRendererNext.d.ts.map +1 -1
  8. package/build/types/real-time-viewer-next/src/core/SceneRenderer.d.ts.map +1 -1
  9. package/build/types/real-time-viewer-next/src/core/camera/useMaterialFade.d.ts.map +1 -1
  10. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/collectDeckTriangles.d.ts.map +1 -1
  11. package/build/types/real-time-viewer-next/src/core/materials/fadeState.d.ts +23 -0
  12. package/build/types/real-time-viewer-next/src/core/materials/fadeState.d.ts.map +1 -0
  13. package/build/types/real-time-viewer-next/src/core/materials/fadeState.test.d.ts +2 -0
  14. package/build/types/real-time-viewer-next/src/core/materials/fadeState.test.d.ts.map +1 -0
  15. package/build/types/real-time-viewer-next/src/core/useFrameloopVisibility.d.ts +2 -1
  16. package/build/types/real-time-viewer-next/src/core/useFrameloopVisibility.d.ts.map +1 -1
  17. package/build/types/real-time-viewer-next/src/core/water/CausticReceiver.d.ts.map +1 -1
  18. package/build/types/real-time-viewer-next/src/hooks/useAssembledScene.d.ts.map +1 -1
  19. package/build/types/real-time-viewer-next/src/hooks/useHostPropsSync.d.ts +2 -2
  20. package/build/types/real-time-viewer-next/src/hooks/useHostPropsSync.d.ts.map +1 -1
  21. package/build/types/real-time-viewer-next/src/loadingGates.d.ts +5 -3
  22. package/build/types/real-time-viewer-next/src/loadingGates.d.ts.map +1 -1
  23. package/build/types/real-time-viewer-next/src/store/slices/glGeneration.d.ts +7 -0
  24. package/build/types/real-time-viewer-next/src/store/slices/glGeneration.d.ts.map +1 -1
  25. package/build/types/real-time-viewer-next/src/utils/hashString.d.ts +6 -0
  26. package/build/types/real-time-viewer-next/src/utils/hashString.d.ts.map +1 -0
  27. package/build/types/real-time-viewer-next/src/utils/suspense.d.ts.map +1 -1
  28. package/build/umd/964.real-time-viewer-next.umd.min.js +1 -1
  29. package/build/umd/real-time-viewer-next.umd.min.js +1 -1
  30. package/build/umd/report.html +1 -1
  31. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { p as parseElementKey, u as useStableEnvSlice, a as useRTVStore, b as bootPhase, D as DECK_COLLAR_LOAD_ID } from './index-cc012549.js';
1
+ import { p as parseElementKey, u as useStableEnvSlice, a as useRTVStore, b as bootPhase, c as adoptPristineFadeState, D as DECK_COLLAR_LOAD_ID } from './index-8c31a811.js';
2
2
  import { useRef, useEffect, useMemo, useState, useCallback } from 'react';
3
3
  import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
4
4
  import _defineProperty from '@babel/runtime/helpers/defineProperty';
@@ -2000,6 +2000,8 @@ function meshesMatching(root, patterns) {
2000
2000
  */
2001
2001
  function cloneDeckMaterial(source) {
2002
2002
  var clone = source.clone();
2003
+ // The deck is sampled while the 2D→3D fade may still be driving its material.
2004
+ adoptPristineFadeState(clone);
2003
2005
  if (clone instanceof MeshStandardMaterial) {
2004
2006
  clone.aoMap = null;
2005
2007
  clone.lightMap = null;
@@ -1,5 +1,5 @@
1
1
  import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
2
- import { f as flushPendingBVH, g as collectCollisionTargets } from './index-cc012549.js';
2
+ import { g as flushPendingBVH, h as collectCollisionTargets } from './index-8c31a811.js';
3
3
  import { useMemo, useEffect, useRef } from 'react';
4
4
  import { Billboard } from '@react-three/drei';
5
5
  import { useThree, useFrame } from '@react-three/fiber';
@@ -1,5 +1,5 @@
1
1
  import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
2
- import { w as withRenderTarget, s as setCausticReceiversSuspended, c as waterUniforms, d as disableWaterUniforms, a as useRTVStore, F as FRAME_ORDER, e as sunAnglesToDirection, l as lightingSunTowardDirection } from './index-cc012549.js';
2
+ import { w as withRenderTarget, s as setCausticReceiversSuspended, d as waterUniforms, e as disableWaterUniforms, a as useRTVStore, F as FRAME_ORDER, f as sunAnglesToDirection, l as lightingSunTowardDirection } from './index-8c31a811.js';
3
3
  import { useRef, useMemo, useEffect, useCallback, useState } from 'react';
4
4
  import { useThree, useFrame } from '@react-three/fiber';
5
5
  import { Vector3, WebGLRenderTarget, HalfFloatType, RGBAFormat, LinearFilter, OrthographicCamera, ShaderMaterial, DoubleSide, MeshStandardMaterial, Box3, NoToneMapping, Frustum, Matrix4, Vector4, Vector2, Plane, Scene, Mesh, PlaneGeometry, ClampToEdgeWrapping, UnsignedByteType, LinearMipmapLinearFilter, MeshBasicMaterial, Color } from 'three';
@@ -60,6 +60,21 @@ if (typeof __webpack_public_path__ !== 'undefined') {
60
60
  }
61
61
  }
62
62
 
63
+ /**
64
+ * Two independent 32-bit hashes (djb2 + sdbm) plus the length, for callers that keep a digest of a
65
+ * string instead of the string itself. A collision would have to hit all three at once.
66
+ */
67
+ function hashString(text) {
68
+ var h1 = 5381;
69
+ var h2 = 0;
70
+ for (var i = 0; i < text.length; i++) {
71
+ var c = text.charCodeAt(i);
72
+ h1 = (h1 << 5) + h1 + c | 0;
73
+ h2 = c + (h2 << 6) + (h2 << 16) - h2 | 0;
74
+ }
75
+ return "".concat(text.length, ":").concat(h1, ":").concat(h2);
76
+ }
77
+
63
78
  function _createForOfIteratorHelper$J(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$J(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
64
79
  function _unsupportedIterableToArray$J(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$J(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$J(r, a) : void 0; } }
65
80
  function _arrayLikeToArray$J(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
@@ -68,20 +83,11 @@ var cache = new Map();
68
83
  var generation = 0;
69
84
 
70
85
  /**
71
- * Two independent 32-bit hashes (djb2 + sdbm) over the serialized key tuple. A raw
72
- * product-material key carries the full mat-list (tens of KB) and distinct configurations are
73
- * unbounded — hashed keys keep the map's key memory flat.
86
+ * A raw product-material key carries the full mat-list (tens of KB) and distinct configurations
87
+ * are unbounded — hashed keys keep the map's key memory flat.
74
88
  */
75
89
  function hashKeys(keys) {
76
- var serialized = JSON.stringify(keys);
77
- var h1 = 5381;
78
- var h2 = 0;
79
- for (var i = 0; i < serialized.length; i++) {
80
- var c = serialized.charCodeAt(i);
81
- h1 = (h1 << 5) + h1 + c | 0;
82
- h2 = c + (h2 << 6) + (h2 << 16) - h2 | 0;
83
- }
84
- return "".concat(h1, ":").concat(h2);
90
+ return hashString(JSON.stringify(keys));
85
91
  }
86
92
 
87
93
  /**
@@ -1055,6 +1061,12 @@ var createGLGenerationSlice = function createGLGenerationSlice(set, get) {
1055
1061
  glContextLost: lost
1056
1062
  });
1057
1063
  },
1064
+ renderStopped: false,
1065
+ setRenderStopped: function setRenderStopped(stopped) {
1066
+ if (get().renderStopped !== stopped) set({
1067
+ renderStopped: stopped
1068
+ });
1069
+ },
1058
1070
  glContextLossLoop: false,
1059
1071
  registerContextRestore: function registerContextRestore() {
1060
1072
  var now = Date.now();
@@ -4422,7 +4434,7 @@ var chunk$2;
4422
4434
  /** Memoized so the preload and the lazy mount share ONE chunk request. */
4423
4435
  function loadChunk$2() {
4424
4436
  var _chunk;
4425
- return (_chunk = chunk$2) !== null && _chunk !== void 0 ? _chunk : chunk$2 = import('./WaterSurface-715ff751.js');
4437
+ return (_chunk = chunk$2) !== null && _chunk !== void 0 ? _chunk : chunk$2 = import('./WaterSurface-ff270e38.js');
4426
4438
  }
4427
4439
 
4428
4440
  /**
@@ -4457,7 +4469,7 @@ var chunk$1;
4457
4469
  /** Memoized so the preload and the lazy mount share ONE chunk request. */
4458
4470
  function loadChunk$1() {
4459
4471
  var _chunk;
4460
- return (_chunk = chunk$1) !== null && _chunk !== void 0 ? _chunk : chunk$1 = import('./DeckCutoutSubsystem-5d0fb500.js');
4472
+ return (_chunk = chunk$1) !== null && _chunk !== void 0 ? _chunk : chunk$1 = import('./DeckCutoutSubsystem-34516143.js');
4461
4473
  }
4462
4474
 
4463
4475
  /**
@@ -4502,7 +4514,7 @@ var chunk;
4502
4514
  /** Memoized so the preload and the lazy mount share ONE chunk request. */
4503
4515
  function loadChunk() {
4504
4516
  var _chunk;
4505
- return (_chunk = chunk) !== null && _chunk !== void 0 ? _chunk : chunk = import('./SunLensflare-9cbd6d72.js');
4517
+ return (_chunk = chunk) !== null && _chunk !== void 0 ? _chunk : chunk = import('./SunLensflare-55ce48ee.js');
4506
4518
  }
4507
4519
 
4508
4520
  /**
@@ -8422,6 +8434,48 @@ function dropFailedMaps(mat) {
8422
8434
  }
8423
8435
  }
8424
8436
 
8437
+ /** A material's state before a fade started driving it. */
8438
+
8439
+ var FADE_ORIGINAL_KEY = 'rtvFadeOriginal';
8440
+
8441
+ /**
8442
+ * The 2D↔3D fade (`useMaterialFade`) drives `opacity` / `transparent` on every mesh material in
8443
+ * the scene, so while it runs a material's live values are transient. The pre-fade state travels
8444
+ * ON the material: `Material.clone()` copies `userData`, so a clone taken mid-fade — the deck
8445
+ * collar sampling the deck, a caustic-receiver patch of a shared article material — carries the
8446
+ * state it has to return to instead of freezing the faded values as its own.
8447
+ */
8448
+ function stampFadeOriginal(mat, original) {
8449
+ mat.userData[FADE_ORIGINAL_KEY] = {
8450
+ opacity: original.opacity,
8451
+ transparent: original.transparent
8452
+ };
8453
+ }
8454
+ function readFadeOriginal(mat) {
8455
+ var stamped = mat.userData[FADE_ORIGINAL_KEY];
8456
+ if (typeof (stamped === null || stamped === void 0 ? void 0 : stamped.opacity) !== 'number' || typeof (stamped === null || stamped === void 0 ? void 0 : stamped.transparent) !== 'boolean') return null;
8457
+ return {
8458
+ opacity: stamped.opacity,
8459
+ transparent: stamped.transparent
8460
+ };
8461
+ }
8462
+ function clearFadeOriginal(mat) {
8463
+ delete mat.userData[FADE_ORIGINAL_KEY];
8464
+ }
8465
+
8466
+ /**
8467
+ * Put a fresh clone back into its source's pre-fade state. A clone that keeps the faded values is
8468
+ * restored TO them when the fade ends (they read as its original) — or, mounted after the fade
8469
+ * already ended, is never restored at all.
8470
+ */
8471
+ function adoptPristineFadeState(mat) {
8472
+ var original = readFadeOriginal(mat);
8473
+ if (!original) return;
8474
+ mat.opacity = original.opacity;
8475
+ mat.transparent = original.transparent;
8476
+ clearFadeOriginal(mat);
8477
+ }
8478
+
8425
8479
  function _createForOfIteratorHelper$t(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$t(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
8426
8480
  function _unsupportedIterableToArray$t(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$t(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$t(r, a) : void 0; } }
8427
8481
  function _arrayLikeToArray$t(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
@@ -8512,6 +8566,8 @@ var FRAGMENT_OUTPUT_REPLACEMENT = /* glsl */"\n #ifdef OPAQUE\n diffuseColor
8512
8566
  function patchMaterialAsCausticReceiver(source, uniforms) {
8513
8567
  var _userData;
8514
8568
  var patched = source.clone();
8569
+ // A shared source can be mid-fade, or left faded by a switch to 2D, when the receiver is patched.
8570
+ adoptPristineFadeState(patched);
8515
8571
 
8516
8572
  // One uniform set per receiver material, seeded from the singleton;
8517
8573
  // `refreshCausticReceiverUniforms` keeps them current.
@@ -13907,10 +13963,16 @@ function useMaterialFade() {
13907
13963
  var mats = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
13908
13964
  mats.forEach(function (mat) {
13909
13965
  if (!originals.has(mat)) {
13910
- originals.set(mat, {
13966
+ var _readFadeOriginal;
13967
+ // A material can enter the fade already stamped: a clone of a fading material
13968
+ // (`Material.clone()` copies `userData`) or a cached material an earlier fade left
13969
+ // faded. Its live values are transient then — the stamp is the state to return to.
13970
+ var original = (_readFadeOriginal = readFadeOriginal(mat)) !== null && _readFadeOriginal !== void 0 ? _readFadeOriginal : {
13911
13971
  opacity: mat.opacity,
13912
13972
  transparent: mat.transparent
13913
- });
13973
+ };
13974
+ originals.set(mat, original);
13975
+ stampFadeOriginal(mat, original);
13914
13976
  // Only the `transparent` flip changes the compiled program — recompile once when a
13915
13977
  // material enters the fade, not on every per-frame opacity write.
13916
13978
  mat.transparent = true;
@@ -13929,6 +13991,7 @@ function useMaterialFade() {
13929
13991
  mat.transparent = orig.transparent;
13930
13992
  mat.needsUpdate = true;
13931
13993
  }
13994
+ clearFadeOriginal(mat);
13932
13995
  });
13933
13996
  originals.clear();
13934
13997
  }, []);
@@ -22119,7 +22182,8 @@ var LOG$5 = getLogger('RealTimeViewer', 'FrameloopVisibility');
22119
22182
  * visibility (pause when invisible) and the profile's `maxFps` cap.
22120
22183
  *
22121
22184
  * Visibility toggles `frameloop` between the env-resolved value and `'never'`
22122
- * on tab hide / window blur.
22185
+ * on tab hide / window blur, and while the host's `stopRender` holds
22186
+ * (`renderStopped`).
22123
22187
  *
22124
22188
  * The FPS cap is gated at the renderer level, not via `frameloop` — we wrap
22125
22189
  * `gl.render` and throttle only the final present:
@@ -22266,9 +22330,14 @@ function useFrameloopVisibility() {
22266
22330
  var active = !hidden && focused;
22267
22331
  var _useRTVStore$getState = useRTVStore$1.getState(),
22268
22332
  isCapturing = _useRTVStore$getState.isCapturing,
22269
- glContextLost = _useRTVStore$getState.glContextLost;
22270
- // A lost context overrides everything, captures included.
22271
- var shouldRun = (active || isCapturing) && !glContextLost;
22333
+ glContextLost = _useRTVStore$getState.glContextLost,
22334
+ renderStopped = _useRTVStore$getState.renderStopped,
22335
+ isSceneLoading = _useRTVStore$getState.isSceneLoading;
22336
+ // A host-stopped render pauses the loop, except while a scene load is in flight: the boot
22337
+ // gates end from `useFrame` (environment GLB placement), so a viewer covered mid-boot would
22338
+ // otherwise hold the loading overlay until it is shown again. A capture keeps rendering
22339
+ // regardless of visibility. A lost context overrides everything.
22340
+ var shouldRun = (active && (!renderStopped || isSceneLoading) || isCapturing) && !glContextLost;
22272
22341
  var next = shouldRun ? envFrameloop : 'never';
22273
22342
  if (next === lastApplied) return;
22274
22343
  lastApplied = next;
@@ -22281,7 +22350,8 @@ function useFrameloopVisibility() {
22281
22350
  hidden: hidden,
22282
22351
  focused: focused,
22283
22352
  isCapturing: isCapturing,
22284
- glContextLost: glContextLost
22353
+ glContextLost: glContextLost,
22354
+ renderStopped: renderStopped
22285
22355
  });
22286
22356
  state.setFrameloop(next);
22287
22357
  if (next === 'demand') state.invalidate();
@@ -22321,6 +22391,11 @@ function useFrameloopVisibility() {
22321
22391
  }, function () {
22322
22392
  return apply('glContextLost-change');
22323
22393
  });
22394
+ var unsubRenderStopped = useRTVStore$1.subscribe(function (s) {
22395
+ return "".concat(s.renderStopped, "|").concat(s.isSceneLoading);
22396
+ }, function () {
22397
+ return apply('renderStopped-change');
22398
+ });
22324
22399
  return function () {
22325
22400
  document.removeEventListener('visibilitychange', onVis);
22326
22401
  window.removeEventListener('blur', onBlur);
@@ -22330,6 +22405,7 @@ function useFrameloopVisibility() {
22330
22405
  window.clearInterval(pollId);
22331
22406
  unsubCapturing();
22332
22407
  unsubContextLost();
22408
+ unsubRenderStopped();
22333
22409
  uninstallCap();
22334
22410
  // Don't force `'always'` on teardown — the next mount or the next
22335
22411
  // env change will set the correct value, and slamming `'always'`
@@ -22686,17 +22762,22 @@ function SceneRenderer() {
22686
22762
  window.addEventListener('pagehide', onPageHide, {
22687
22763
  once: true
22688
22764
  });
22689
- canvasCleanupRef.current = function () {
22765
+ var cleanup = function cleanup() {
22690
22766
  for (var _i = 0, _auditTimers = auditTimers; _i < _auditTimers.length; _i++) {
22691
22767
  var timer = _auditTimers[_i];
22692
22768
  window.clearTimeout(timer);
22693
22769
  }
22694
- // This canvas' deadline must not outlive it and flag a future healthy canvas.
22770
+ // Neither this canvas' deadline nor its listeners may outlive it: R3F force-loses the context
22771
+ // ~500 ms after the Canvas unmounts, and a listener still attached would flag that teardown as
22772
+ // a device loss — pausing the successor canvas and tripping the breaker 12 s later.
22695
22773
  window.clearTimeout(restoreDeadline);
22774
+ canvas.removeEventListener('webglcontextlost', onContextLost, false);
22775
+ canvas.removeEventListener('webglcontextrestored', onContextRestored, false);
22696
22776
  window.removeEventListener('pagehide', onPageHide);
22697
22777
  };
22778
+ canvasCleanupRef.current = cleanup;
22698
22779
  var restoreDeadline = 0;
22699
- canvas.addEventListener('webglcontextlost', function (event) {
22780
+ var onContextLost = function onContextLost(event) {
22700
22781
  event.preventDefault();
22701
22782
  CONTEXT_LOG.warn('WebGL context lost — preventing default to allow restore');
22702
22783
  // Pause the frameloop (see `useFrameloopVisibility`): rendering against the dead context
@@ -22709,8 +22790,8 @@ function SceneRenderer() {
22709
22790
  restoreDeadline = window.setTimeout(function () {
22710
22791
  if (useRTVStore$1.getState().glContextLost) useRTVStore$1.getState().tripContextLossBreaker('no restore within 12s');
22711
22792
  }, 12000);
22712
- }, false);
22713
- canvas.addEventListener('webglcontextrestored', function () {
22793
+ };
22794
+ var onContextRestored = function onContextRestored() {
22714
22795
  window.clearTimeout(restoreDeadline);
22715
22796
  CONTEXT_LOG.warn('WebGL context restored — rebuilding render-target-backed resources');
22716
22797
  useRTVStore$1.getState().logGLContextEvent('context restored — rebuilding');
@@ -22728,13 +22809,18 @@ function SceneRenderer() {
22728
22809
  evictEquirectMasters();
22729
22810
  evictGLTFCache();
22730
22811
  evictEnvironmentGLBs();
22812
+ // Tear this canvas down before the swap: R3F force-loses its context ~500 ms after the unmount,
22813
+ // and the successor's onCreated (which also runs this) waits for a debounced size measurement first.
22814
+ cleanup();
22731
22815
  useRTVStore$1.getState().bumpGLGeneration();
22732
22816
  // Shrink the outgoing canvas once React swapped it out.
22733
22817
  window.setTimeout(function () {
22734
22818
  canvas.width = 1;
22735
22819
  canvas.height = 1;
22736
22820
  }, 0);
22737
- }, false);
22821
+ };
22822
+ canvas.addEventListener('webglcontextlost', onContextLost, false);
22823
+ canvas.addEventListener('webglcontextrestored', onContextRestored, false);
22738
22824
  }, []);
22739
22825
  var _useRTVAnalyzerCompon = useRTVAnalyzerComponents(),
22740
22826
  DebugPanel = _useRTVAnalyzerCompon.DebugPanel,
@@ -23178,17 +23264,22 @@ function SceneLoadingDivaBridge() {
23178
23264
 
23179
23265
  /**
23180
23266
  * Hides the whole viewer (the canvas + everything it renders) while the scene
23181
- * loads, so the user never sees a half-built scene pop in. Uses `visibility`
23182
- * (not unmount / `display:none`) so the canvas keeps rendering and the in-flight
23183
- * GLB finishes decoding + fires `endSceneLoad`, which reveals it in place.
23267
+ * loads, so the user never sees a half-built scene pop in, and while the host
23268
+ * has stopped rendering (`stopRender` → `renderStopped`). Uses `visibility`
23269
+ * (not unmount / `display:none`) so the canvas keeps its size and its context:
23270
+ * an in-flight GLB finishes decoding + fires `endSceneLoad`, which reveals it
23271
+ * in place, and a paused viewer resumes without a reboot.
23184
23272
  */
23185
23273
  function SceneVisibilityGate(_ref) {
23186
23274
  var children = _ref.children;
23187
23275
  var isSceneLoading = useRTVStore$1(function (s) {
23188
23276
  return s.isSceneLoading;
23189
23277
  });
23278
+ var renderStopped = useRTVStore$1(function (s) {
23279
+ return s.renderStopped;
23280
+ });
23190
23281
  return /*#__PURE__*/jsx("div", {
23191
- className: isSceneLoading ? "".concat(gate, " ").concat(hidden) : gate,
23282
+ className: isSceneLoading || renderStopped ? "".concat(gate, " ").concat(hidden) : gate,
23192
23283
  children: children
23193
23284
  });
23194
23285
  }
@@ -23380,21 +23471,43 @@ function useAssembledScene(_ref) {
23380
23471
  return null;
23381
23472
  }
23382
23473
  }, [hqRenderScriptProp]);
23474
+
23475
+ // Scenes are compared by content, not by script string: the host re-emits the script for every
23476
+ // render request (a different `_id`, render size or camera per request), and a new `ParsedScene`
23477
+ // object alone re-runs every scene consumer — article-version bump, BVH, shadow rebuild, deck
23478
+ // re-measure and collar rebuild — for a scene that did not change.
23479
+ var lastParsedRef = useRef(null);
23383
23480
  var parsed = useMemo(function () {
23481
+ var _lastParsedRef$curren;
23384
23482
  if (!externalHqs) return null;
23385
23483
  var parseStartedAt = bootClockMs();
23484
+ var scene;
23485
+ // The parse alone is guarded: nothing below may turn a scene that parsed fine into a blank viewer.
23386
23486
  try {
23387
- var scene = parseRenderScript(externalHqs, config.archiveBaseUrl);
23388
- bootPhase('parse done', {
23389
- ms: Math.round(bootClockMs() - parseStartedAt),
23390
- geoAssets: scene.geoAssets.length,
23391
- matAssets: scene.matAssets.length
23392
- });
23393
- return scene;
23487
+ scene = parseRenderScript(externalHqs, config.archiveBaseUrl);
23394
23488
  } catch (err) {
23395
23489
  LOG$2.error(err instanceof Error ? err : new Error(String(err)));
23396
23490
  return null;
23397
23491
  }
23492
+ // A digest, not the serialized scene: the full text of a pool scene would sit in this ref for
23493
+ // the page's lifetime.
23494
+ var signature = hashString(JSON.stringify(scene));
23495
+ if (((_lastParsedRef$curren = lastParsedRef.current) === null || _lastParsedRef$curren === void 0 ? void 0 : _lastParsedRef$curren.signature) === signature) {
23496
+ bootPhase('parse done (scene unchanged)', {
23497
+ ms: Math.round(bootClockMs() - parseStartedAt)
23498
+ });
23499
+ return lastParsedRef.current.scene;
23500
+ }
23501
+ lastParsedRef.current = {
23502
+ scene: scene,
23503
+ signature: signature
23504
+ };
23505
+ bootPhase('parse done', {
23506
+ ms: Math.round(bootClockMs() - parseStartedAt),
23507
+ geoAssets: scene.geoAssets.length,
23508
+ matAssets: scene.matAssets.length
23509
+ });
23510
+ return scene;
23398
23511
  }, [externalHqs, config.archiveBaseUrl]);
23399
23512
 
23400
23513
  // The first-article hold (see the id's doc). Begun before any reveal can happen (the seeded
@@ -23879,7 +23992,8 @@ function useHostPropsSync(_ref2) {
23879
23992
  elementsInfo = _ref2.elementsInfo,
23880
23993
  selectedElementId = _ref2.selectedElementId,
23881
23994
  selectedAccessoryKey = _ref2.selectedAccessoryKey,
23882
- callbacks = _ref2.callbacks;
23995
+ callbacks = _ref2.callbacks,
23996
+ stopRender = _ref2.stopRender;
23883
23997
  var setViewMode = useRTVStore$1(function (s) {
23884
23998
  return s.setViewMode;
23885
23999
  });
@@ -23916,6 +24030,9 @@ function useHostPropsSync(_ref2) {
23916
24030
  var setShowDimensionLines = useRTVStore$1(function (s) {
23917
24031
  return s.setShowDimensionLines;
23918
24032
  });
24033
+ var setRenderStopped = useRTVStore$1(function (s) {
24034
+ return s.setRenderStopped;
24035
+ });
23919
24036
  var sceneElementCount = useRTVStore$1(function (s) {
23920
24037
  return s.elements.length;
23921
24038
  });
@@ -23972,6 +24089,9 @@ function useHostPropsSync(_ref2) {
23972
24089
  useEffect(function () {
23973
24090
  setShowDimensionLines(!!showDimensionLines);
23974
24091
  }, [setShowDimensionLines, showDimensionLines]);
24092
+ useEffect(function () {
24093
+ setRenderStopped(!!stopRender);
24094
+ }, [setRenderStopped, stopRender]);
23975
24095
  useEffect(function () {
23976
24096
  if (focusElementId != null) {
23977
24097
  focusElementById(focusElementId);
@@ -24001,7 +24121,6 @@ function useHostPropsSync(_ref2) {
24001
24121
  var RealTimeRendererNext = /*#__PURE__*/memo(function (props) {
24002
24122
  var scene2d = props.scene2d,
24003
24123
  viewMode = props.viewMode,
24004
- stopRender = props.stopRender,
24005
24124
  analyzerComponents = props.analyzerComponents;
24006
24125
  var unsupported = useRTVStore$1(function (s) {
24007
24126
  return s.unsupported;
@@ -24024,7 +24143,7 @@ var RealTimeRendererNext = /*#__PURE__*/memo(function (props) {
24024
24143
  // must not unmount (killing the WebGL context) meanwhile — the visibility gate keeps it hidden
24025
24144
  // until the boot loads resolve.
24026
24145
  var hasAnything = hasStoreScene || parsed || scene2d || viewMode === '2d';
24027
- if (stopRender || !hasAnything) return null;
24146
+ if (!hasAnything) return null;
24028
24147
 
24029
24148
  // Tier-0 fallback: render nothing rather than a broken WebGL context.
24030
24149
  if (unsupported) return null;
@@ -25154,4 +25273,4 @@ var internals = /*#__PURE__*/Object.freeze({
25154
25273
  mergePresetBundle: mergePresetBundle
25155
25274
  });
25156
25275
 
25157
- export { DECK_COLLAR_LOAD_ID as D, ENVIRONMENT_CONFIG as E, FRAME_ORDER as F, RealTimeRendererNext as R, WHITE_ROOM_PRESET as W, useRTVStore$1 as a, bootPhase as b, waterUniforms as c, disableWaterUniforms as d, sunAnglesToDirection as e, flushPendingBVH as f, collectCollisionTargets as g, emitConfig as h, internals as i, lightingSunTowardDirection as l, parseElementKey as p, setCausticReceiversSuspended as s, useStableEnvSlice as u, withRenderTarget as w };
25276
+ export { DECK_COLLAR_LOAD_ID as D, ENVIRONMENT_CONFIG as E, FRAME_ORDER as F, RealTimeRendererNext as R, WHITE_ROOM_PRESET as W, useRTVStore$1 as a, bootPhase as b, adoptPristineFadeState as c, waterUniforms as d, disableWaterUniforms as e, sunAnglesToDirection as f, flushPendingBVH as g, collectCollisionTargets as h, internals as i, emitConfig as j, lightingSunTowardDirection as l, parseElementKey as p, setCausticReceiversSuspended as s, useStableEnvSlice as u, withRenderTarget as w };
@@ -1,4 +1,4 @@
1
- export { E as ENVIRONMENT_CONFIG, W as WHITE_ROOM_PRESET, R as default, h as emitConfig, i as internals, a as useRTVStore } from './index-cc012549.js';
1
+ export { E as ENVIRONMENT_CONFIG, W as WHITE_ROOM_PRESET, R as default, j as emitConfig, i as internals, a as useRTVStore } from './index-8c31a811.js';
2
2
  import 'react';
3
3
  import '@react-three/fiber';
4
4
  import 'three';
@@ -19,6 +19,11 @@ export interface RTVNextProps {
19
19
  scene2d?: RTVScene2D;
20
20
  viewMode?: '3d' | '2d';
21
21
  config: RTVConfig;
22
+ /**
23
+ * Pauses the viewer instead of unmounting it: the frameloop stops and the stage is hidden, while
24
+ * the assembled scene, every cache and the WebGL context stay alive for an instant return. A host
25
+ * that does not want the viewer to boot at all simply does not mount it.
26
+ */
22
27
  stopRender?: boolean;
23
28
  callbacks?: RTVCallbacks;
24
29
  analyzerComponents?: RTVAnalyzerComponentMap;
@@ -1 +1 @@
1
- {"version":3,"file":"RealTimeRendererNext.d.ts","sourceRoot":"","sources":["../../../../src/RealTimeRendererNext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AAGpC,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AACzE,OAAO,EAAuB,KAAK,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAIzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,mGAAmG;IACnG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,SAAS,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,6FAA6F;IAC7F,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,2FAA2F;IAC3F,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC/C,4FAA4F;IAC5F,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gGAAgG;IAChG,iBAAiB,CAAC,EAAE,OAAO,wBAAwB,EAAE,iBAAiB,EAAE,CAAC;IACzE,6FAA6F;IAC7F,oBAAoB,CAAC,EAAE,OAAO,wBAAwB,EAAE,oBAAoB,EAAE,CAAC;IAC/E;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+FAA+F;IAC/F,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qGAAqG;IACrG,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,QAAA,MAAM,oBAAoB,oCAAgB,YAAY,8BAoCpD,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"RealTimeRendererNext.d.ts","sourceRoot":"","sources":["../../../../src/RealTimeRendererNext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AAGpC,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AACzE,OAAO,EAAuB,KAAK,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAIzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,mGAAmG;IACnG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,SAAS,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,6FAA6F;IAC7F,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,2FAA2F;IAC3F,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC/C,4FAA4F;IAC5F,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gGAAgG;IAChG,iBAAiB,CAAC,EAAE,OAAO,wBAAwB,EAAE,iBAAiB,EAAE,CAAC;IACzE,6FAA6F;IAC7F,oBAAoB,CAAC,EAAE,OAAO,wBAAwB,EAAE,oBAAoB,EAAE,CAAC;IAC/E;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+FAA+F;IAC/F,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qGAAqG;IACrG,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,QAAA,MAAM,oBAAoB,oCAAgB,YAAY,8BAoCpD,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../../../../src/core/SceneRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6E,MAAM,OAAO,CAAC;AA4ClG,wBAAgB,aAAa,sBAiQ5B"}
1
+ {"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../../../../src/core/SceneRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6E,MAAM,OAAO,CAAC;AA4ClG,wBAAgB,aAAa,sBAmQ5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"useMaterialFade.d.ts","sourceRoot":"","sources":["../../../../../../src/core/camera/useMaterialFade.ts"],"names":[],"mappings":"AAUA,wBAAgB,eAAe;kCAKjB,MAAM;;EAoCnB"}
1
+ {"version":3,"file":"useMaterialFade.d.ts","sourceRoot":"","sources":["../../../../../../src/core/camera/useMaterialFade.ts"],"names":[],"mappings":"AAMA,wBAAgB,eAAe;kCAKjB,MAAM;;EA0CnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"collectDeckTriangles.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/environment/deckCutout/collectDeckTriangles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmD,KAAK,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjH,OAAO,EAA2D,KAAK,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAI/I;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAOzC,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,aAAa,EAAE,MAAM,CAAC;IACtB,0HAA0H;IAC1H,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,yGAAyG;IACzG,SAAS,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,aAAa,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACxC,iGAAiG;IACjG,aAAa,EAAE,MAAM,CAAC;IACtB,2FAA2F;IAC3F,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC3E;AAED,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAOzE;AAkBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,MAAM,CAI5E;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,IAAI,EAAE,EACd,SAAS,EAAE,IAAI,EACf,aAAa,EAAE,MAAM,GACpB;IACD,SAAS,EAAE,YAAY,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACpE,GAAG,IAAI,CAoEP;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG;IAAE,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAyB9H;AAED,oIAAoI;AACpI,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,MAAM,CAElE;AAED,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,OAAO,GAAG,MAAM,EAAE,CAoB3F"}
1
+ {"version":3,"file":"collectDeckTriangles.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/environment/deckCutout/collectDeckTriangles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmD,KAAK,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjH,OAAO,EAA2D,KAAK,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAK/I;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAOzC,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,aAAa,EAAE,MAAM,CAAC;IACtB,0HAA0H;IAC1H,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,yGAAyG;IACzG,SAAS,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,aAAa,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACxC,iGAAiG;IACjG,aAAa,EAAE,MAAM,CAAC;IACtB,2FAA2F;IAC3F,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC3E;AAED,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAOzE;AAoBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,MAAM,CAI5E;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,IAAI,EAAE,EACd,SAAS,EAAE,IAAI,EACf,aAAa,EAAE,MAAM,GACpB;IACD,SAAS,EAAE,YAAY,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACpE,GAAG,IAAI,CAoEP;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG;IAAE,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAyB9H;AAED,oIAAoI;AACpI,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,MAAM,CAElE;AAED,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,OAAO,GAAG,MAAM,EAAE,CAoB3F"}
@@ -0,0 +1,23 @@
1
+ import type { Material } from 'three';
2
+ /** A material's state before a fade started driving it. */
3
+ export interface MaterialFadeOriginal {
4
+ opacity: number;
5
+ transparent: boolean;
6
+ }
7
+ /**
8
+ * The 2D↔3D fade (`useMaterialFade`) drives `opacity` / `transparent` on every mesh material in
9
+ * the scene, so while it runs a material's live values are transient. The pre-fade state travels
10
+ * ON the material: `Material.clone()` copies `userData`, so a clone taken mid-fade — the deck
11
+ * collar sampling the deck, a caustic-receiver patch of a shared article material — carries the
12
+ * state it has to return to instead of freezing the faded values as its own.
13
+ */
14
+ export declare function stampFadeOriginal(mat: Material, original: MaterialFadeOriginal): void;
15
+ export declare function readFadeOriginal(mat: Material): MaterialFadeOriginal | null;
16
+ export declare function clearFadeOriginal(mat: Material): void;
17
+ /**
18
+ * Put a fresh clone back into its source's pre-fade state. A clone that keeps the faded values is
19
+ * restored TO them when the fade ends (they read as its original) — or, mounted after the fade
20
+ * already ended, is never restored at all.
21
+ */
22
+ export declare function adoptPristineFadeState(mat: Material): void;
23
+ //# sourceMappingURL=fadeState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fadeState.d.ts","sourceRoot":"","sources":["../../../../../../src/core/materials/fadeState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAID;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAErF;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,oBAAoB,GAAG,IAAI,CAI3E;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAErD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAM1D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fadeState.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fadeState.test.d.ts","sourceRoot":"","sources":["../../../../../../src/core/materials/fadeState.test.ts"],"names":[],"mappings":""}
@@ -3,7 +3,8 @@
3
3
  * visibility (pause when invisible) and the profile's `maxFps` cap.
4
4
  *
5
5
  * Visibility toggles `frameloop` between the env-resolved value and `'never'`
6
- * on tab hide / window blur.
6
+ * on tab hide / window blur, and while the host's `stopRender` holds
7
+ * (`renderStopped`).
7
8
  *
8
9
  * The FPS cap is gated at the renderer level, not via `frameloop` — we wrap
9
10
  * `gl.render` and throttle only the final present:
@@ -1 +1 @@
1
- {"version":3,"file":"useFrameloopVisibility.d.ts","sourceRoot":"","sources":["../../../../../src/core/useFrameloopVisibility.tsx"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,sBAAsB,SA0LrC;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,SAG5C"}
1
+ {"version":3,"file":"useFrameloopVisibility.d.ts","sourceRoot":"","sources":["../../../../../src/core/useFrameloopVisibility.tsx"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sBAAsB,SAmMrC;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,SAG5C"}
@@ -1 +1 @@
1
- {"version":3,"file":"CausticReceiver.d.ts","sourceRoot":"","sources":["../../../../../../src/core/water/CausticReceiver.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,OAAO,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIrD;;;;;GAKG;AACH,QAAA,MAAM,qBAAqB,gCAAgC,CAAC;AAE5D;;;;GAIG;AACH,KAAK,uBAAuB,GAAG,oBAAoB,GAAG;IACpD,CAAC,qBAAqB,CAAC,CAAC,EAAE,IAAI,CAAC;CAChC,CAAC;AAUF;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAErE;AAsKD;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,aAAa,GAAG,uBAAuB,CAoE7H;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAwC5G;AAwBD,mGAAmG;AACnG,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,aAAa,GAAG,oBAAoB,CAQlH;AAED,gGAAgG;AAChG,wBAAgB,yBAAyB,IAAI,IAAI,CAGhD;AAED,kGAAkG;AAClG,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAQzE"}
1
+ {"version":3,"file":"CausticReceiver.d.ts","sourceRoot":"","sources":["../../../../../../src/core/water/CausticReceiver.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,OAAO,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKrD;;;;;GAKG;AACH,QAAA,MAAM,qBAAqB,gCAAgC,CAAC;AAE5D;;;;GAIG;AACH,KAAK,uBAAuB,GAAG,oBAAoB,GAAG;IACpD,CAAC,qBAAqB,CAAC,CAAC,EAAE,IAAI,CAAC;CAChC,CAAC;AAUF;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAErE;AAsKD;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,aAAa,GAAG,uBAAuB,CAsE7H;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAwC5G;AAwBD,mGAAmG;AACnG,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,aAAa,GAAG,oBAAoB,CAQlH;AAED,gGAAgG;AAChG,wBAAgB,yBAAyB,IAAI,IAAI,CAGhD;AAED,kGAAkG;AAClG,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAQzE"}
@@ -1 +1 @@
1
- {"version":3,"file":"useAssembledScene.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/useAssembledScene.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAKlF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAsB5D,KAAK,mBAAmB,GAAG,IAAI,CAAC,YAAY,EAAE,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC,CAAC;AAEpG;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,mBAAmB,GAAG,WAAW,GAAG,IAAI,CAwF5I"}
1
+ {"version":3,"file":"useAssembledScene.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/useAssembledScene.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAMlF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAsB5D,KAAK,mBAAmB,GAAG,IAAI,CAAC,YAAY,EAAE,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC,CAAC;AAEpG;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,mBAAmB,GAAG,WAAW,GAAG,IAAI,CAuG5I"}
@@ -1,6 +1,6 @@
1
1
  import type { RTVNextProps } from '../RealTimeRendererNext';
2
2
  type HostConfigProps = Pick<RTVNextProps, 'config' | 'callbacks'>;
3
- type HostUiProps = Pick<RTVNextProps, 'viewMode' | 'plannerMode' | 'freePlacesOnGrids' | 'plannedPlacesOnGrids' | 'catalogInfo' | 'armedAccessoryCodex' | 'draggedAccessoryCodex' | 'showDimensionLines' | 'focusElementId' | 'elementsInfo' | 'selectedElementId' | 'selectedAccessoryKey' | 'callbacks'>;
3
+ type HostUiProps = Pick<RTVNextProps, 'viewMode' | 'plannerMode' | 'freePlacesOnGrids' | 'plannedPlacesOnGrids' | 'catalogInfo' | 'armedAccessoryCodex' | 'draggedAccessoryCodex' | 'showDimensionLines' | 'focusElementId' | 'elementsInfo' | 'selectedElementId' | 'selectedAccessoryKey' | 'callbacks' | 'stopRender'>;
4
4
  /**
5
5
  * Installs the host's config + callbacks and kicks off device-tier detection.
6
6
  *
@@ -15,6 +15,6 @@ export declare function useHostConfigSync({ config, callbacks }: HostConfigProps
15
15
  * controlled-selection setters act on the scene that landed in the store
16
16
  * earlier in the same flush.
17
17
  */
18
- export declare function useHostPropsSync({ viewMode, plannerMode, freePlacesOnGrids, plannedPlacesOnGrids, catalogInfo, armedAccessoryCodex, draggedAccessoryCodex, showDimensionLines, focusElementId, elementsInfo, selectedElementId, selectedAccessoryKey, callbacks, }: HostUiProps): void;
18
+ export declare function useHostPropsSync({ viewMode, plannerMode, freePlacesOnGrids, plannedPlacesOnGrids, catalogInfo, armedAccessoryCodex, draggedAccessoryCodex, showDimensionLines, focusElementId, elementsInfo, selectedElementId, selectedAccessoryKey, callbacks, stopRender, }: HostUiProps): void;
19
19
  export {};
20
20
  //# sourceMappingURL=useHostPropsSync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useHostPropsSync.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/useHostPropsSync.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAK5D,KAAK,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,WAAW,CAAC,CAAC;AA2BlE,KAAK,WAAW,GAAG,IAAI,CACrB,YAAY,EACV,UAAU,GACV,aAAa,GACb,mBAAmB,GACnB,sBAAsB,GACtB,aAAa,GACb,qBAAqB,GACrB,uBAAuB,GACvB,oBAAoB,GACpB,gBAAgB,GAChB,cAAc,GACd,mBAAmB,GACnB,sBAAsB,GACtB,WAAW,CACd,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,eAAe,QAsCvE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,GACV,EAAE,WAAW,QA4Fb"}
1
+ {"version":3,"file":"useHostPropsSync.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/useHostPropsSync.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAK5D,KAAK,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,WAAW,CAAC,CAAC;AA2BlE,KAAK,WAAW,GAAG,IAAI,CACrB,YAAY,EACV,UAAU,GACV,aAAa,GACb,mBAAmB,GACnB,sBAAsB,GACtB,aAAa,GACb,qBAAqB,GACrB,uBAAuB,GACvB,oBAAoB,GACpB,gBAAgB,GAChB,cAAc,GACd,mBAAmB,GACnB,sBAAsB,GACtB,WAAW,GACX,YAAY,CACf,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,eAAe,QAsCvE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,UAAU,GACX,EAAE,WAAW,QAiGb"}
@@ -8,9 +8,11 @@ import React, { type ReactNode } from 'react';
8
8
  export declare function SceneLoadingDivaBridge(): null;
9
9
  /**
10
10
  * Hides the whole viewer (the canvas + everything it renders) while the scene
11
- * loads, so the user never sees a half-built scene pop in. Uses `visibility`
12
- * (not unmount / `display:none`) so the canvas keeps rendering and the in-flight
13
- * GLB finishes decoding + fires `endSceneLoad`, which reveals it in place.
11
+ * loads, so the user never sees a half-built scene pop in, and while the host
12
+ * has stopped rendering (`stopRender` → `renderStopped`). Uses `visibility`
13
+ * (not unmount / `display:none`) so the canvas keeps its size and its context:
14
+ * an in-flight GLB finishes decoding + fires `endSceneLoad`, which reveals it
15
+ * in place, and a paused viewer resumes without a reboot.
14
16
  */
15
17
  export declare function SceneVisibilityGate({ children }: {
16
18
  children: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"loadingGates.d.ts","sourceRoot":"","sources":["../../../../src/loadingGates.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAOjE;;;;;GAKG;AACH,wBAAgB,sBAAsB,SA6BrC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,qBAGxE;AAED;gFACgF;AAChF,qBAAa,oBAAqB,SAAQ,KAAK,CAAC,SAAS,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;IACrG,KAAK;;MAAqB;IAC1B,MAAM,CAAC,wBAAwB;;;IAG/B,iBAAiB;IAGjB,MAAM;CAGP"}
1
+ {"version":3,"file":"loadingGates.d.ts","sourceRoot":"","sources":["../../../../src/loadingGates.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAOjE;;;;;GAKG;AACH,wBAAgB,sBAAsB,SA6BrC;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,qBAIxE;AAED;gFACgF;AAChF,qBAAa,oBAAqB,SAAQ,KAAK,CAAC,SAAS,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;IACrG,KAAK;;MAAqB;IAC1B,MAAM,CAAC,wBAAwB;;;IAG/B,iBAAiB;IAGjB,MAAM;CAGP"}
@@ -16,6 +16,13 @@ export interface GLGenerationSlice {
16
16
  /** True between context lost and restored — the frameloop controller pauses while set. */
17
17
  glContextLost: boolean;
18
18
  setGLContextLost: (lost: boolean) => void;
19
+ /**
20
+ * Host `stopRender`: the viewer stays mounted with its scene and WebGL context, the frameloop
21
+ * pauses (`useFrameloopVisibility`) and `SceneVisibilityGate` hides the stage. A capture or a
22
+ * scene load in flight still gets frames — both need them to finish, and someone waits on them.
23
+ */
24
+ renderStopped: boolean;
25
+ setRenderStopped: (stopped: boolean) => void;
19
26
  /** Breaker: repeated losses in a short window swap the viewer to the error page instead of remount-thrash. */
20
27
  glContextLossLoop: boolean;
21
28
  /** Called from the `webglcontextrestored` handler; trips {@link glContextLossLoop} on repeats. */