@crystaldesign/real-time-viewer-next 26.13.0-beta.2 → 26.13.0-beta.20

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 (47) hide show
  1. package/build/esm/{DeckCutoutSubsystem-6453a4f4.js → DeckCutoutSubsystem-8b702df0.js} +208 -11
  2. package/build/esm/{SunLensflare-46fa6ee8.js → SunLensflare-0676ae6e.js} +1 -1
  3. package/build/esm/{WaterSurface-10a28566.js → WaterSurface-f3e878b1.js} +4 -3
  4. package/build/esm/{index-428fd8ca.js → index-6f58b1ed.js} +219 -49
  5. package/build/esm/index.js +1 -1
  6. package/build/types/real-time-viewer-next/src/config/environment.config.d.ts +1 -0
  7. package/build/types/real-time-viewer-next/src/config/environment.config.d.ts.map +1 -1
  8. package/build/types/real-time-viewer-next/src/core/ArticleSettleTracker.d.ts +12 -0
  9. package/build/types/real-time-viewer-next/src/core/ArticleSettleTracker.d.ts.map +1 -0
  10. package/build/types/real-time-viewer-next/src/core/SceneRenderer.d.ts.map +1 -1
  11. package/build/types/real-time-viewer-next/src/core/camera/CameraController.d.ts.map +1 -1
  12. package/build/types/real-time-viewer-next/src/core/camera/useCameraActions.d.ts +2 -1
  13. package/build/types/real-time-viewer-next/src/core/camera/useCameraActions.d.ts.map +1 -1
  14. package/build/types/real-time-viewer-next/src/core/camera/useCameraConstraints.d.ts.map +1 -1
  15. package/build/types/real-time-viewer-next/src/core/dimension-lines/DimensionLines3D.d.ts.map +1 -1
  16. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutArticleSync.d.ts.map +1 -1
  17. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/articleFootprint.d.ts.map +1 -1
  18. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureArticle.d.ts +16 -0
  19. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureArticle.d.ts.map +1 -1
  20. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureDiagnostics.d.ts.map +1 -1
  21. package/build/types/real-time-viewer-next/src/core/environment/scene.config.d.ts +21 -0
  22. package/build/types/real-time-viewer-next/src/core/environment/scene.config.d.ts.map +1 -1
  23. package/build/types/real-time-viewer-next/src/core/shadows/useShadowDirty.d.ts.map +1 -1
  24. package/build/types/real-time-viewer-next/src/core/water/WaterSurfaceRenderer.d.ts.map +1 -1
  25. package/build/types/real-time-viewer-next/src/presets/villa-cliff.d.ts.map +1 -1
  26. package/build/types/real-time-viewer-next/src/store/slices/camera.d.ts +6 -1
  27. package/build/types/real-time-viewer-next/src/store/slices/camera.d.ts.map +1 -1
  28. package/build/types/real-time-viewer-next/src/store/slices/selection.d.ts +5 -2
  29. package/build/types/real-time-viewer-next/src/store/slices/selection.d.ts.map +1 -1
  30. package/build/types/real-time-viewer-next/src/store/slices/versions.d.ts +9 -0
  31. package/build/types/real-time-viewer-next/src/store/slices/versions.d.ts.map +1 -1
  32. package/build/types/real-time-viewer-next/src/types/camera.d.ts +9 -6
  33. package/build/types/real-time-viewer-next/src/types/camera.d.ts.map +1 -1
  34. package/build/types/real-time-viewer-next/src/utils/cameraFraming.d.ts +7 -0
  35. package/build/types/real-time-viewer-next/src/utils/cameraFraming.d.ts.map +1 -1
  36. package/build/types/real-time-viewer-next/src/utils/cameraFraming.test.d.ts +2 -0
  37. package/build/types/real-time-viewer-next/src/utils/cameraFraming.test.d.ts.map +1 -0
  38. package/build/types/real-time-viewer-next/src/utils/geoTargets.d.ts +30 -0
  39. package/build/types/real-time-viewer-next/src/utils/geoTargets.d.ts.map +1 -0
  40. package/build/umd/463.real-time-viewer-next.umd.min.js +1 -1
  41. package/build/umd/99.real-time-viewer-next.umd.min.js +2 -0
  42. package/build/umd/99.real-time-viewer-next.umd.min.js.LICENSE.txt +1 -0
  43. package/build/umd/real-time-viewer-next.umd.min.js +1 -1
  44. package/build/umd/report.html +1 -1
  45. package/package.json +6 -2
  46. package/build/umd/673.real-time-viewer-next.umd.min.js +0 -2
  47. package/build/umd/673.real-time-viewer-next.umd.min.js.LICENSE.txt +0 -1
@@ -1,11 +1,11 @@
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-428fd8ca.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-6f58b1ed.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';
5
5
  import { useThree, useFrame } from '@react-three/fiber';
6
6
  import { getLogger } from '@crystaldesign/diva-core';
7
- import { Box3, Matrix4, Vector3, Matrix3, MeshStandardMaterial, Shape, Path, ExtrudeGeometry, BufferAttribute, TextureLoader, SRGBColorSpace, RepeatWrapping } from 'three';
8
7
  import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
8
+ import { Box3, Matrix4, Vector3, Matrix3, MeshStandardMaterial, Shape, Path, ExtrudeGeometry, BufferAttribute, TextureLoader, SRGBColorSpace, RepeatWrapping } from 'three';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
10
  import '@babel/runtime/helpers/asyncToGenerator';
11
11
  import '@babel/runtime/regenerator';
@@ -34,6 +34,64 @@ import '@react-three/postprocessing';
34
34
  import '@babel/runtime/helpers/construct';
35
35
  import 'detect-gpu';
36
36
 
37
+ /**
38
+ * How a catalog names a geo. Assembled geo nodes are called `` `${nodePath}/geo:${assetId}:${i}` `` —
39
+ * a runtime path no catalog can be authored against — so a selector is translated by the resolver
40
+ * instead (geo name → asset ids → nodes).
41
+ */
42
+
43
+ /**
44
+ * Resolve a {@link GeoSelector} to scene nodes.
45
+ *
46
+ * Assembled geo nodes are named `` `${nodePath}/geo:${assetId}:${i}` `` (see `SceneTree`), i.e. a
47
+ * runtime path carrying an asset id — nothing a catalog can be authored against. A selector names
48
+ * the geo the way a catalog does (`GeoAsset.name`, e.g. `GEO_GRIFF_L`) and this translates:
49
+ * name → asset ids → nodes.
50
+ */
51
+ function resolveGeoNodes(root, geoAssets, selector) {
52
+ if ('nodeName' in selector) {
53
+ var node = root.getObjectByName(selector.nodeName);
54
+ return node ? [node] : [];
55
+ }
56
+ var matchesName = geoNameMatcher(selector);
57
+ if (!matchesName) return [];
58
+ var assetIds = geoAssets.filter(function (a) {
59
+ return matchesName(a.name);
60
+ }).map(function (a) {
61
+ return a.id;
62
+ });
63
+ if (assetIds.length === 0) return [];
64
+ var markers = assetIds.map(function (id) {
65
+ return "/geo:".concat(id, ":");
66
+ });
67
+ var all = [];
68
+ root.traverse(function (obj) {
69
+ if (obj.name && markers.some(function (m) {
70
+ return obj.name.includes(m);
71
+ })) all.push(obj);
72
+ });
73
+ return all;
74
+ }
75
+
76
+ /** The catalog-name test of a name-based selector; null when it names nothing. */
77
+ function geoNameMatcher(selector) {
78
+ if ('geoNamePrefix' in selector) {
79
+ var prefix = selector.geoNamePrefix;
80
+ return prefix ? function (name) {
81
+ return name.startsWith(prefix);
82
+ } : null;
83
+ }
84
+ var geoName = selector.geoName;
85
+ return geoName ? function (name) {
86
+ return name === geoName;
87
+ } : null;
88
+ }
89
+
90
+ function ownKeys$1(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
91
+ function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$1(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$1(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
92
+ function _createForOfIteratorHelper$8(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$8(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; } } }; }
93
+ function _unsupportedIterableToArray$8(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$8(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$8(r, a) : void 0; } }
94
+ function _arrayLikeToArray$8(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; }
37
95
  var ELEMENT_PREFIX = 'Element#';
38
96
  var _elementBox = new Box3();
39
97
  var _unionBox = new Box3();
@@ -136,6 +194,132 @@ function measureArticle(scene, patterns, elementsInfo) {
136
194
  };
137
195
  }
138
196
 
197
+ /**
198
+ * The measurement narrowed to the article's geos whose catalog name starts with one of the
199
+ * comma-separated `geoPrefix`es: the rect, the bottom and the traced outline then describe only those
200
+ * parts — the basin, not the stairs or cover that come with the same article. The elements still move
201
+ * as a whole; only what is measured changes. Falls back to the whole article, with a note in
202
+ * `geoScope`, when no geo matches.
203
+ */
204
+ function restrictToGeos(measurement, geoPrefix, geoAssets) {
205
+ var prefixes = geoPrefix.split(',').map(function (p) {
206
+ return p.trim();
207
+ }).filter(function (p) {
208
+ return p.length > 0;
209
+ });
210
+ if (prefixes.length === 0) return measurement;
211
+ var nodes = new Set();
212
+ var _iterator = _createForOfIteratorHelper$8(topLevelElements(measurement.elementObjects)),
213
+ _step;
214
+ try {
215
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
216
+ var element = _step.value;
217
+ var _iterator3 = _createForOfIteratorHelper$8(prefixes),
218
+ _step3;
219
+ try {
220
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
221
+ var prefix = _step3.value;
222
+ var _iterator4 = _createForOfIteratorHelper$8(resolveGeoNodes(element, geoAssets, {
223
+ geoNamePrefix: prefix
224
+ })),
225
+ _step4;
226
+ try {
227
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
228
+ var node = _step4.value;
229
+ nodes.add(node);
230
+ }
231
+ } catch (err) {
232
+ _iterator4.e(err);
233
+ } finally {
234
+ _iterator4.f();
235
+ }
236
+ }
237
+ } catch (err) {
238
+ _iterator3.e(err);
239
+ } finally {
240
+ _iterator3.f();
241
+ }
242
+ }
243
+ } catch (err) {
244
+ _iterator.e(err);
245
+ } finally {
246
+ _iterator.f();
247
+ }
248
+ _unionBox.makeEmpty();
249
+ var _iterator2 = _createForOfIteratorHelper$8(nodes),
250
+ _step2;
251
+ try {
252
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
253
+ var _node = _step2.value;
254
+ _elementBox.setFromObject(_node);
255
+ if (!_elementBox.isEmpty()) _unionBox.union(_elementBox);
256
+ }
257
+ } catch (err) {
258
+ _iterator2.e(err);
259
+ } finally {
260
+ _iterator2.f();
261
+ }
262
+ if (_unionBox.isEmpty()) {
263
+ return _objectSpread$1(_objectSpread$1({}, measurement), {}, {
264
+ geoScope: "geos ".concat(geoPrefix, ": NONE matched \u2014 ").concat(describeArticleGeos(measurement, prefixes, geoAssets), " \u2014 measured the whole article")
265
+ });
266
+ }
267
+ var worldBottomY = _unionBox.min.y;
268
+ _unionBox.applyMatrix4(measurement.floorLocal);
269
+ return _objectSpread$1(_objectSpread$1({}, measurement), {}, {
270
+ rect: {
271
+ minX: _unionBox.min.x,
272
+ maxX: _unionBox.max.x,
273
+ minY: _unionBox.min.y,
274
+ maxY: _unionBox.max.y,
275
+ minZ: _unionBox.min.z,
276
+ maxZ: _unionBox.max.z
277
+ },
278
+ worldBottomY: worldBottomY,
279
+ shapeNodes: _toConsumableArray(nodes),
280
+ geoScope: "geos ".concat(geoPrefix, ": ").concat(nodes.size, " node(s) [").concat(_toConsumableArray(new Set(_toConsumableArray(nodes).map(function (n) {
281
+ return geoAssetName(n, geoAssets);
282
+ }))).join(', '), "]")
283
+ });
284
+ }
285
+ var GEO_NODE_ID = /\/geo:(.+):\d+$/;
286
+
287
+ /** Catalog geo name of an assembled geo node (`…/geo:<assetId>:<i>`), or the raw node name. */
288
+ function geoAssetName(node, geoAssets) {
289
+ var _GEO_NODE_ID$exec, _geoAssets$find$name, _geoAssets$find;
290
+ var id = (_GEO_NODE_ID$exec = GEO_NODE_ID.exec(node.name)) === null || _GEO_NODE_ID$exec === void 0 ? void 0 : _GEO_NODE_ID$exec[1];
291
+ return (_geoAssets$find$name = (_geoAssets$find = geoAssets.find(function (a) {
292
+ return a.id === id;
293
+ })) === null || _geoAssets$find === void 0 ? void 0 : _geoAssets$find.name) !== null && _geoAssets$find$name !== void 0 ? _geoAssets$find$name : node.name;
294
+ }
295
+
296
+ /**
297
+ * Why a prefix matched nothing: the geo names the article actually carries, and whether a mesh INSIDE
298
+ * one of them carries the prefix instead — a name from the GLB, which the prefix is not matched against.
299
+ */
300
+ function describeArticleGeos(measurement, prefixes, geoAssets) {
301
+ var geoNames = new Set();
302
+ var innerMatches = new Set();
303
+ var _iterator5 = _createForOfIteratorHelper$8(topLevelElements(measurement.elementObjects)),
304
+ _step5;
305
+ try {
306
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
307
+ var element = _step5.value;
308
+ element.traverse(function (obj) {
309
+ if (GEO_NODE_ID.test(obj.name)) geoNames.add(geoAssetName(obj, geoAssets));else if (prefixes.some(function (p) {
310
+ return obj.name.startsWith(p);
311
+ })) innerMatches.add(obj.name);
312
+ });
313
+ }
314
+ } catch (err) {
315
+ _iterator5.e(err);
316
+ } finally {
317
+ _iterator5.f();
318
+ }
319
+ var inner = innerMatches.size > 0 ? " | but GLB-internal nodes match: [".concat(_toConsumableArray(innerMatches).slice(0, 10).join(', '), "]") : '';
320
+ return "article geos: [".concat(_toConsumableArray(geoNames).join(', '), "]").concat(inner);
321
+ }
322
+
139
323
  function _createForOfIteratorHelper$7(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$7(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; } } }; }
140
324
  function _unsupportedIterableToArray$7(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$7(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$7(r, a) : void 0; } }
141
325
  function _arrayLikeToArray$7(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; }
@@ -895,7 +1079,8 @@ function traceArticleFootprint(elements, floorLocal, rect, minLocalY, offset, di
895
1079
  var pos = mesh.geometry.getAttribute('position');
896
1080
  if (!pos) return;
897
1081
  meshes.push(mesh);
898
- sigParts.push("".concat(mesh.name, ":").concat(pos.count));
1082
+ // Identity, not name: names can be empty, and a geoPrefix switch between equal-sized geos must re-trace.
1083
+ sigParts.push("".concat(mesh.uuid, "/").concat(mesh.geometry.uuid, ":").concat(pos.count));
899
1084
  });
900
1085
  }
901
1086
  } catch (err) {
@@ -1013,7 +1198,7 @@ function traceArticleFootprint(elements, floorLocal, rect, minLocalY, offset, di
1013
1198
  * without such a rule the deck's authored hole stays fully open.
1014
1199
  */
1015
1200
  function deriveArticleFootprint(measurement, rule, margin, cache) {
1016
- var _result$points$length, _result$points;
1201
+ var _measurement$shapeNod, _result$points$length, _result$points;
1017
1202
  var pendingLiftY = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
1018
1203
  var result = {
1019
1204
  points: null,
@@ -1031,7 +1216,7 @@ function deriveArticleFootprint(measurement, rule, margin, cache) {
1031
1216
  elementObjects = measurement.elementObjects;
1032
1217
  var rawSizeX = rect.maxX - rect.minX;
1033
1218
  var rawSizeZ = rect.maxZ - rect.minZ;
1034
- var traced = traceArticleFootprint(topLevelElements(elementObjects), floorLocal, rect, floorTopY - rule.footprintSliceDepth, margin, result.trace, cache, pendingLiftY);
1219
+ var traced = traceArticleFootprint((_measurement$shapeNod = measurement.shapeNodes) !== null && _measurement$shapeNod !== void 0 ? _measurement$shapeNod : topLevelElements(elementObjects), floorLocal, rect, floorTopY - rule.footprintSliceDepth, margin, result.trace, cache, pendingLiftY);
1035
1220
  var minX = Infinity;
1036
1221
  var maxX = -Infinity;
1037
1222
  var minZ = Infinity;
@@ -1233,7 +1418,7 @@ function _arrayLikeToArray$2(r, a) { (null == a || a > r.length) && (a = r.lengt
1233
1418
  * `message` is lazy — composed only when the key moved.
1234
1419
  */
1235
1420
  function measureLog(_ref) {
1236
- var _rule$match, _points$length, _fpBounds$map$join;
1421
+ var _rule$match, _measurement$geoScope, _points$length, _fpBounds$map$join;
1237
1422
  var measurement = _ref.measurement,
1238
1423
  rule = _ref.rule,
1239
1424
  primaryCodex = _ref.primaryCodex,
@@ -1266,7 +1451,7 @@ function measureLog(_ref) {
1266
1451
  }
1267
1452
  }
1268
1453
  var fpBounds = points ? [fpMinX, fpMaxX, fpMinZ, fpMaxZ] : null;
1269
- var key = "".concat(primaryCodex !== null && primaryCodex !== void 0 ? primaryCodex : '', "|").concat((_rule$match = rule === null || rule === void 0 ? void 0 : rule.match) !== null && _rule$match !== void 0 ? _rule$match : '', "|").concat(footprint.sampledCount, "|").concat((_points$length = points === null || points === void 0 ? void 0 : points.length) !== null && _points$length !== void 0 ? _points$length : 0, "|").concat((_fpBounds$map$join = fpBounds === null || fpBounds === void 0 ? void 0 : fpBounds.map(function (v) {
1454
+ var key = "".concat(primaryCodex !== null && primaryCodex !== void 0 ? primaryCodex : '', "|").concat((_rule$match = rule === null || rule === void 0 ? void 0 : rule.match) !== null && _rule$match !== void 0 ? _rule$match : '', "|").concat((_measurement$geoScope = measurement.geoScope) !== null && _measurement$geoScope !== void 0 ? _measurement$geoScope : '', "|").concat(footprint.sampledCount, "|").concat((_points$length = points === null || points === void 0 ? void 0 : points.length) !== null && _points$length !== void 0 ? _points$length : 0, "|").concat((_fpBounds$map$join = fpBounds === null || fpBounds === void 0 ? void 0 : fpBounds.map(function (v) {
1270
1455
  return v.toFixed(2);
1271
1456
  }).join(',')) !== null && _fpBounds$map$join !== void 0 ? _fpBounds$map$join : '');
1272
1457
  var message = function message() {
@@ -1290,7 +1475,8 @@ function measureLog(_ref) {
1290
1475
  } else {
1291
1476
  fpInfo = " | ".concat(footprint.source, " produced NO usable outline (").concat(footprint.sampledCount, " pts)").concat(footprint.repairNote, " \u2192 NO COLLAR, the authored hole stays fully open");
1292
1477
  }
1293
- return "[DeckCutout] article codex: ".concat(primaryCodex !== null && primaryCodex !== void 0 ? primaryCodex : '(unknown)', " \u2192 rule: ").concat(rule ? "'".concat(rule.match, "'") : '(none — articleSync globals)').concat(sliceInfo).concat(fpInfo);
1478
+ var scope = measurement.geoScope ? " | ".concat(measurement.geoScope) : '';
1479
+ return "[DeckCutout] article codex: ".concat(primaryCodex !== null && primaryCodex !== void 0 ? primaryCodex : '(unknown)', " \u2192 rule: ").concat(rule ? "'".concat(rule.match, "'") : '(none — articleSync globals)').concat(scope).concat(sliceInfo).concat(fpInfo);
1294
1480
  };
1295
1481
  return {
1296
1482
  key: key,
@@ -1355,6 +1541,8 @@ function DeckCutoutArticleSync() {
1355
1541
  var footprintMarkedRef = useRef(false);
1356
1542
  // One-shot codex/rule log: fires only when the measured codex or matched rule changes.
1357
1543
  var matchLogRef = useRef(null);
1544
+ // One-shot height log: fires only when the measured tops, the deck or the pending lift change.
1545
+ var heightLogRef = useRef(null);
1358
1546
  var geometryCacheRef = useRef(null);
1359
1547
  // Settle tracking: measure every frame while the pipeline is still changing.
1360
1548
  var settlingRef = useRef(false);
@@ -1415,6 +1603,7 @@ function DeckCutoutArticleSync() {
1415
1603
  stableMeasuresRef.current = 0;
1416
1604
  }, [articleVersion, decorVersion, active]);
1417
1605
  useFrame(function () {
1606
+ var _useRTVStore$getState, _useRTVStore$getState2, _measured$geoScope;
1418
1607
  if (!active || patterns.length === 0) return;
1419
1608
  frameRef.current += 1;
1420
1609
  // While unsettled, measure EVERY frame: the pipeline converges by
@@ -1451,21 +1640,29 @@ function DeckCutoutArticleSync() {
1451
1640
  if (stableMeasuresRef.current >= SETTLE_STABLE_MEASURES) settlingRef.current = false;
1452
1641
  };
1453
1642
  measureCountRef.current += 1;
1454
- var measured = measureArticle(scene, patterns, useRTVStore.getState().elementsInfo);
1455
- if (!measured) {
1643
+ var article = measureArticle(scene, patterns, useRTVStore.getState().elementsInfo);
1644
+ if (!article) {
1456
1645
  setWaterOverride(null);
1457
1646
  setFootprintOverride(null);
1458
1647
  finishMeasure(true);
1459
1648
  return;
1460
1649
  }
1461
- var _pickArticleRule = pickArticleRule(deform.articleRules, measured.codexesByArea),
1650
+ var _pickArticleRule = pickArticleRule(deform.articleRules, article.codexesByArea),
1462
1651
  rule = _pickArticleRule.rule,
1463
1652
  primaryCodex = _pickArticleRule.primaryCodex;
1653
+ // A rule naming a `geoPrefix` measures only those geos — the basin, not the stairs or cover the
1654
+ // same article ships with — so outline, rim, anchor and water all follow the basin alone.
1655
+ var measured = rule !== null && rule !== void 0 && rule.geoPrefix ? restrictToGeos(article, rule.geoPrefix, (_useRTVStore$getState = (_useRTVStore$getState2 = useRTVStore.getState().scene) === null || _useRTVStore$getState2 === void 0 ? void 0 : _useRTVStore$getState2.geoAssets) !== null && _useRTVStore$getState !== void 0 ? _useRTVStore$getState : []) : article;
1464
1656
  // The anchor's lift changes what clears the slice plane and used to force a second full trace
1465
1657
  // per article load. It is fully determined here (same formula as `deriveElementAnchor`), so
1466
1658
  // the first trace evaluates the article at its post-lift height directly.
1467
1659
  var anchorCfg = deform.elementAnchor;
1468
1660
  var pendingLiftY = anchorActive ? (anchorCfg.alignTopToDeck ? measured.floorTopY - measured.rect.maxY : 0) + anchorCfg.offsetY : 0;
1661
+ var heightLog = "[DeckCutout:height] top (floor-local): whole article ".concat(article.rect.maxY.toFixed(3), " | measured ").concat(measured.rect.maxY.toFixed(3)) + " (".concat((_measured$geoScope = measured.geoScope) !== null && _measured$geoScope !== void 0 ? _measured$geoScope : 'whole article', ") | rule ").concat(rule ? "'".concat(rule.match, "' geoPrefix '").concat(rule.geoPrefix, "'") : 'NONE matched') + " | deck top ".concat(measured.floorTopY.toFixed(3)) + " | pending lift ".concat(pendingLiftY.toFixed(3), " | anchor ").concat(anchorActive ? 'on' : 'OFF', ", alignTopToDeck ").concat(anchorCfg.alignTopToDeck, ", offsetY ").concat(anchorCfg.offsetY);
1662
+ if (heightLogRef.current !== heightLog) {
1663
+ heightLogRef.current = heightLog;
1664
+ LOG$2.debug(heightLog);
1665
+ }
1469
1666
  var traced = deriveArticleFootprint(measured, rule, deform.articleSync.margin, geometryCacheRef, pendingLiftY);
1470
1667
  var rect = traced.rim ? _objectSpread(_objectSpread({}, measured.rect), traced.rim) : measured.rect;
1471
1668
  var log = measureLog({
@@ -1,5 +1,5 @@
1
1
  import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
2
- import { g as flushPendingBVH, h as collectCollisionTargets } from './index-428fd8ca.js';
2
+ import { g as flushPendingBVH, h as collectCollisionTargets } from './index-6f58b1ed.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, d as waterUniforms, e as disableWaterUniforms, a as useRTVStore, F as FRAME_ORDER, f as sunAnglesToDirection, l as lightingSunTowardDirection } from './index-428fd8ca.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-6f58b1ed.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';
@@ -1931,9 +1931,10 @@ function WaterSurfaceRenderer(_ref) {
1931
1931
  }
1932
1932
 
1933
1933
  // Receiving geometry is article AND deck, so either version invalidates
1934
- // the height map and the refraction capture.
1934
+ // the height map and the refraction capture — and the article's settled
1935
+ // placement, since the deck anchor moves it after its geos mount.
1935
1936
  var store = useRTVStore.getState();
1936
- var geometryVersion = store.articleVersion + store.decorVersion;
1937
+ var geometryVersion = store.articleVersion + store.articleSettledVersion + store.decorVersion;
1937
1938
 
1938
1939
  // Keep the height map current and bind it to the caustics pass. The water
1939
1940
  // group is excluded — none of its meshes are receiving geometry.