@operato/scene-storage 10.0.0-beta.40 → 10.0.0-beta.42

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 (102) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/MIGRATION-plan-a-slot-api.md +266 -0
  3. package/PLAN-A-rack-as-slot-holder.md +164 -0
  4. package/dist/box.js +18 -0
  5. package/dist/box.js.map +1 -1
  6. package/dist/crane-3d.d.ts +47 -2
  7. package/dist/crane-3d.js +246 -89
  8. package/dist/crane-3d.js.map +1 -1
  9. package/dist/crane.d.ts +96 -12
  10. package/dist/crane.js +395 -100
  11. package/dist/crane.js.map +1 -1
  12. package/dist/index.d.ts +3 -4
  13. package/dist/index.js +1 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/pallet.d.ts +15 -0
  16. package/dist/pallet.js +38 -2
  17. package/dist/pallet.js.map +1 -1
  18. package/dist/parcel-3d.js +22 -18
  19. package/dist/parcel-3d.js.map +1 -1
  20. package/dist/parcel.d.ts +4 -3
  21. package/dist/parcel.js +24 -5
  22. package/dist/parcel.js.map +1 -1
  23. package/dist/rack-grid-3d.d.ts +18 -7
  24. package/dist/rack-grid-3d.js +372 -69
  25. package/dist/rack-grid-3d.js.map +1 -1
  26. package/dist/rack-grid-cell.d.ts +21 -72
  27. package/dist/rack-grid-cell.js +147 -243
  28. package/dist/rack-grid-cell.js.map +1 -1
  29. package/dist/rack-grid.d.ts +277 -56
  30. package/dist/rack-grid.js +1230 -695
  31. package/dist/rack-grid.js.map +1 -1
  32. package/dist/rack-materials.d.ts +9 -0
  33. package/dist/rack-materials.js +55 -0
  34. package/dist/rack-materials.js.map +1 -0
  35. package/dist/storage-rack-3d.d.ts +15 -0
  36. package/dist/storage-rack-3d.js +165 -29
  37. package/dist/storage-rack-3d.js.map +1 -1
  38. package/dist/storage-rack.d.ts +253 -32
  39. package/dist/storage-rack.js +726 -66
  40. package/dist/storage-rack.js.map +1 -1
  41. package/package.json +3 -3
  42. package/src/box.ts +18 -0
  43. package/src/crane-3d.ts +258 -93
  44. package/src/crane.ts +445 -110
  45. package/src/index.ts +3 -4
  46. package/src/pallet.ts +50 -1
  47. package/src/parcel-3d.ts +23 -18
  48. package/src/parcel.ts +24 -5
  49. package/src/rack-grid-3d.ts +383 -80
  50. package/src/rack-grid-cell.ts +161 -305
  51. package/src/rack-grid.ts +1263 -762
  52. package/src/rack-materials.ts +61 -0
  53. package/src/storage-rack-3d.ts +182 -29
  54. package/src/storage-rack.ts +819 -67
  55. package/test/test-carrier-lifecycle.ts +361 -0
  56. package/test/test-coord-alignment.ts +201 -0
  57. package/test/test-crane-geometry.ts +167 -0
  58. package/test/test-external-to-rack.ts +461 -0
  59. package/test/test-mover-concurrent-bug.ts +304 -0
  60. package/test/test-mover-rollback.ts +290 -0
  61. package/test/test-phase-h-carrier-pickable.ts +4 -3
  62. package/test/test-r19-place-absorb.ts +174 -0
  63. package/test/test-rack-3d-attach-real.ts +301 -0
  64. package/test/test-rack-concurrent.ts +254 -0
  65. package/test/test-rack-edge-cases.ts +323 -0
  66. package/test/test-rack-grid-cell.ts +318 -0
  67. package/test/test-rack-grid-location.ts +657 -0
  68. package/test/test-real-3d-positioning.ts +158 -0
  69. package/test/test-slot-center-convention.ts +116 -0
  70. package/test/test-slot-target.ts +189 -0
  71. package/test/test-storage-rack-batched.ts +606 -0
  72. package/test/test-storage-rack-click.ts +329 -0
  73. package/test/test-storage-rack-slot-api.ts +357 -0
  74. package/test/test-toscene-convention.ts +162 -0
  75. package/test/test-user-scenario-sequential.ts +334 -0
  76. package/translations/en.json +7 -1
  77. package/translations/ja.json +7 -1
  78. package/translations/ko.json +7 -1
  79. package/translations/ms.json +7 -1
  80. package/translations/zh.json +7 -1
  81. package/tsconfig.tsbuildinfo +1 -1
  82. package/dist/rack-column.d.ts +0 -35
  83. package/dist/rack-column.js +0 -258
  84. package/dist/rack-column.js.map +0 -1
  85. package/dist/rack-grid-helpers.d.ts +0 -28
  86. package/dist/rack-grid-helpers.js +0 -71
  87. package/dist/rack-grid-helpers.js.map +0 -1
  88. package/dist/rack-grid-location.d.ts +0 -37
  89. package/dist/rack-grid-location.js +0 -227
  90. package/dist/rack-grid-location.js.map +0 -1
  91. package/dist/storage-cell-3d.d.ts +0 -25
  92. package/dist/storage-cell-3d.js +0 -88
  93. package/dist/storage-cell-3d.js.map +0 -1
  94. package/dist/storage-cell.d.ts +0 -70
  95. package/dist/storage-cell.js +0 -197
  96. package/dist/storage-cell.js.map +0 -1
  97. package/src/rack-column.ts +0 -340
  98. package/src/rack-grid-helpers.ts +0 -77
  99. package/src/rack-grid-location.ts +0 -286
  100. package/src/storage-cell-3d.ts +0 -101
  101. package/src/storage-cell.ts +0 -247
  102. package/test/test-rack-grid.ts +0 -77
@@ -1,10 +1,53 @@
1
1
  import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
2
2
  import type { State, Material3D } from '@hatiolab/things-scene';
3
- import { CellMap, type AttachFrame, type Alignment, type Heights, type PlacementArchetype } from '@operato/scene-base';
3
+ import * as THREE from 'three';
4
+ import { CellMap, SlotTarget, type AttachFrame, type Alignment, type Heights, type PlacementArchetype, type SlottedHolder } from '@operato/scene-base';
4
5
  /** Rack 컴포넌트 state */
5
6
  export interface StorageRackState extends State {
6
7
  bays?: number;
7
8
  levels?: number;
9
+ /**
10
+ * Level 1 (첫 shelf) 의 *시작 높이* (mm, rack 의 3D Y 축, 바닥부터). 미명시 0
11
+ * (바닥 = 첫 shelf). 양수 시 그만큼 위로 올라가 stocker port / conveyor 같은
12
+ * 컴포넌트가 들어갈 *빈 공간* 확보. Frame uprights 는 바닥 ~ 천장 그대로.
13
+ */
14
+ shelfBaseHeight?: number;
15
+ /**
16
+ * 적재 시각화 데이터. 배열의 각 record 는 최소 `cellId` 필드가 필요. cellMap 에
17
+ * 존재하는 cellId 만 InstancedMesh 의 instance 로 렌더링됨 (한 박스 = 한 cell).
18
+ * Plan A: 이 record 들이 *"매트릭스 안"* 의 carrier — obtainCarrier 가 materialize
19
+ * 시 record 빠짐, receiveAt 시 record push. pickAndPlace 와 양방향 atomic sync.
20
+ */
21
+ data?: Array<{
22
+ cellId: string;
23
+ [key: string]: any;
24
+ }>;
25
+ /**
26
+ * RackCell 컴포넌트의 eager 생성 여부.
27
+ * - `undefined` (default): state.data 있으면 false (batched, 메모리 절약),
28
+ * 없으면 true (legacy non-batched, RackCell-based Mover 호환).
29
+ * - `true`: 명시적 eager — 모든 cell 을 RackCell 로 생성.
30
+ * - `false`: 명시적 skip — Plan A 의 slot API 만 사용 (obtainCarrier / slotTargetAt).
31
+ */
32
+ eagerCells?: boolean;
33
+ /**
34
+ * Legend 컴포넌트의 id. legend 의 `state.status = {field, ranges:[{value|min,max, color}], defaultColor}`
35
+ * 를 사용해 각 record 의 `field` 값을 색상으로 매핑. 미명시 시 scene-wide auto-discovery
36
+ * (type='legend' 인 첫 번째 컴포넌트).
37
+ */
38
+ legendTarget?: string;
39
+ /**
40
+ * Cell 클릭 시 invoke 할 things-scene `Popup` 컴포넌트의 id. Popup 컴포넌트는 scene
41
+ * 어딘가에 일반 컴포넌트로 배치되어 있고, 그 자체의 state (board / modal / closable /
42
+ * draggable / tether / billboard 등) 가 popup 동작을 정의. rack 은 *anchor 만 동적으로*
43
+ * override 해 호출 — 클릭된 cell 위에 popup 이 뜸.
44
+ *
45
+ * 미명시 시 popup 비활성 (rack-cell-click 이벤트는 여전히 발사 — 외부 consumer 가
46
+ * 직접 처리 가능).
47
+ */
48
+ popupRef?: string;
49
+ /** 가로 frame (beam) 만 숨김 — uprights 는 유지. */
50
+ hideHorizontalFrame?: boolean;
8
51
  debugCells?: boolean;
9
52
  material3d?: Material3D;
10
53
  }
@@ -15,16 +58,12 @@ declare const Rack_base: any;
15
58
  *
16
59
  * `levels` × `bays` cells form a vertical grid. Each cell holds one logistics
17
60
  * package (Pallet / Box / Parcel). A picker (Crane / Forklift / robot arm)
18
- * accesses individual cells via Phase G/H Pickable contract — the picker is
19
- * Rack-agnostic, knowing only how to interact with a cell.
61
+ * accesses individual cells via the Plan A slot API — the picker interacts
62
+ * with `SlotTarget` (no explicit cell-component required).
20
63
  *
21
- * **Monitoring mode** (default): carriers are direct children of the rack,
22
- * placed by external data binding. No RackCell children are created.
23
- *
24
- * **Simulation mode**: call `rack._buildCells()` after placing the rack on the
25
- * scene. This creates RackCell children at the correct 3D positions. A picker
26
- * (Crane / Forklift / ...) then navigates to individual RackCells for
27
- * pick-and-place.
64
+ * **Plan A**: carriers are direct children of the rack, addressed by
65
+ * `state.cellId`. Stock visualization uses InstancedMesh (batched). Slot
66
+ * lookup / pick / place via `obtainCarrier` / `receiveAt` / `slotTargetAt`.
28
67
  *
29
68
  * **Placement**: `floor` archetype, full ceiling depth by default.
30
69
  *
@@ -32,13 +71,18 @@ declare const Rack_base: any;
32
71
  * extension can be added later for AGV-mounted or cart-mounted variants —
33
72
  * the cell topology and pickable contract stay the same.
34
73
  */
35
- export default class Rack extends Rack_base {
74
+ export default class Rack extends Rack_base implements SlottedHolder {
36
75
  state: StorageRackState;
37
76
  static placement: PlacementArchetype;
38
77
  static align: Alignment;
39
78
  static defaultDepth: (h: Heights) => number;
40
79
  get nature(): ComponentNature;
41
80
  get anchors(): never[];
81
+ /**
82
+ * Runtime — bays / levels 변경 시 anchor 캐시 무효화. cell 위치가 바뀌므로 다음
83
+ * `_ensureCellAttachObject3d` 호출이 새 좌표로 갱신.
84
+ */
85
+ onchange(after: Record<string, unknown>, _before: Record<string, unknown>): void;
42
86
  /**
43
87
  * Derive the cell topology from the rack's current dimensions and bay/level
44
88
  * counts. The CellMap is rebuilt fresh each time (state changes trigger
@@ -50,41 +94,218 @@ export default class Rack extends Rack_base {
50
94
  * Z = row axis (front → back, the rack's `height` state property)
51
95
  */
52
96
  get cellMap(): CellMap;
53
- /**
54
- * Create RackCell child components for each cell in the CellMap.
55
- *
56
- * Called explicitly to enter simulation mode — monitoring-mode racks
57
- * never call this (carriers are direct children, no explicit cells).
58
- *
59
- * Idempotent: removes existing rack-cell children first.
60
- */
61
- _buildCells(): void;
62
97
  /**
63
98
  * Allow:
64
- * - Carriable components (pallets, boxes, parcels) — direct children in monitoring mode.
65
- * - RackCell — created by _buildCells() in simulation mode.
99
+ * - Carriable components (pallets, boxes, parcels) — direct children, operation archetype.
66
100
  *
67
101
  * Block:
68
102
  * - Everything else (sensors, labels, etc. can be siblings of the rack, not children).
69
103
  */
70
104
  containable(component: Component): boolean;
71
105
  /**
72
- * Attach frame for carriers that are DIRECT children of the rack
73
- * (monitoring mode, where carriers go directly into the rack without
74
- * explicit RackCell components).
106
+ * Attach frame for direct-child carriers Plan A 모든 carrier rack
107
+ * 직접 자식이므로 매번 호출됨. carrier state.cellId 해당하는 *cell-local
108
+ * anchor object3d* 를 반환 → carrier 의 object3d 가 자동으로 셀 위치에 정렬됨.
109
+ */
110
+ attachPointFor(carrier: Component): AttachFrame | null;
111
+ /** state.data 의 record 목록 (읽기 전용 뷰). */
112
+ get records(): ReadonlyArray<{
113
+ cellId: string;
114
+ [key: string]: any;
115
+ }>;
116
+ /**
117
+ * 1-based (bay, row, level) → 0-based cellId 문자열.
118
+ *
119
+ * rack.cellIdOf(1, 1, 6) → '0-0-5'
120
+ * rack.cellIdOf(3, 1, 4) → '2-0-3'
121
+ */
122
+ cellIdOf(bay: number, row?: number, level?: number): string;
123
+ /** cellId 에 carrier 가 있는가 — child carrier 또는 state.data record 어느 쪽이든. */
124
+ hasCarrierAt(cellId: string): boolean;
125
+ /** cellId 매칭되는 rack 의 직접 자식 carrier (operation archetype). */
126
+ private _carrierChildAt;
127
+ /**
128
+ * carrier 를 obtain — 이미 child 면 그대로, 아니면 state.data record 로 transient
129
+ * materialize 후 rack 의 직접 자식으로 add 하고 state.data 에서 그 record 제거.
130
+ * record 도 child 도 없으면 null.
131
+ *
132
+ * Signature overloads:
133
+ * obtainCarrier('0-0-5') — string cellId 직접
134
+ * obtainCarrier(1, 1, 6) — 1-based (bay, row, level)
135
+ * obtainCarrier(1) ≡ obtainCarrier(1,1,1)
136
+ */
137
+ obtainCarrier(idOrBay: string | number, row?: number, level?: number): Component | null;
138
+ /**
139
+ * State.data 의 *internal* 갱신 — Plan A 의 obtainCarrier / receiveAt 가 사용.
140
+ * `setState` 와 달리 *'change' 이벤트 / onchangeData / mapping cascade 를 우회*.
141
+ *
142
+ * 이유: mapping 시스템이 state.data 변경 시 *자동으로 script fire*. Plan A 의
143
+ * setState 가 그 cascade 를 트리거하면 사용자 script 가 *재귀적으로 자기 자신을 호출*
144
+ * 하는 회귀 (board 의 의도된 binding 일 수도, 우연일 수도) 발생.
145
+ *
146
+ * 대신 *직접 _state 갱신 + rebuildStockMesh 직접 호출* — 시각화는 갱신되지만 외부
147
+ * mapping 은 fire 안 됨. External (WMS / application setState) 호출은 그대로 setState
148
+ * 거치므로 그쪽 mapping 은 정상 동작.
149
+ */
150
+ private _setDataSilently;
151
+ /**
152
+ * cell 이 carrier 를 받을 수 있는가.
153
+ *
154
+ * 규칙:
155
+ * - state.data 에 record 가 있으면 점유 → false
156
+ * - carrier-child 가 있고 *그 child 가 들여오려는 carrier 자기 자신이 아니면* → false
157
+ * - 들여오려는 carrier 가 *바로 그 cell 의 child 자기 자신* 이면 → true (idempotent —
158
+ * obtain('A') 직후 receive('A', sameCarrier) 가 *자기 자리 복귀* 로 동작)
159
+ */
160
+ canReceiveAt(cellId: string, carrier?: Component): boolean;
161
+ /**
162
+ * Carrier 가 rack 의 slot 으로 들어옴 — "매트릭스 진입": 즉시 dispose + state.data 에
163
+ * record 로 환원. 결과: InstancedMesh 가 다시 그 자리에 instance 표시, rack 의 자식
164
+ * 컴포넌트 트리는 깨끗.
165
+ */
166
+ receiveAt(cellId: string, carrier: Component, _options?: any): Promise<void>;
167
+ /**
168
+ * Carrier 의 state 를 state.data record 로 추출. application 이 carrier subclass 별
169
+ * 추가 필드 인코딩 원하면 override. transform/position 관련은 record 와 무관해 skip.
170
+ */
171
+ recordFromCarrier(carrier: Component, cellId: string): {
172
+ cellId: string;
173
+ [key: string]: any;
174
+ };
175
+ /**
176
+ * SlottedHolder 컨트랙 — slot 의 attach object3d 반환. SlotTarget 이 자기
177
+ * `_realObject.object3d` proxy 로 사용하고, Carriable.applyHolderAttachPoint 도
178
+ * 이걸 attach frame 으로 사용 (transit 중 carrier 가 slot 위치에 정렬).
179
+ */
180
+ getSlotAttachObject3d(cellId: string): THREE.Object3D | undefined;
181
+ /**
182
+ * SlottedHolder 컨트랙 — slot 의 *expected carrier* 의 3D 크기 (slot 자체의 기하 크기가
183
+ * 아님). Crane 의 `resolveCarrierBottomY = centerY - depth/2` 에서 *carrier 가 놓일 때
184
+ * 예상되는 carrier depth* 를 써야 fork 가 *carrier 바닥 = shelf* 에 정확히 진입.
185
+ *
186
+ * 즉:
187
+ * depth = stockD (= levelHeight * 0.7) — *carrier 의 vertical extent*. 전체 셀 높이
188
+ * (levelHeight) 가 아닌 *실제 stock 박스 깊이*. anchor 가 stock 시각 중심 (
189
+ * shelf + stockD/2) 에 위치하므로 depth = stockD 여야 bottom 계산이 shelf.
190
+ * width = bayWidth — 그대로
191
+ * height = rowDepth — 그대로 (2D 의 Z 축 폭)
192
+ */
193
+ getSlotSize(cellId: string): {
194
+ width: number;
195
+ height: number;
196
+ depth: number;
197
+ } | undefined;
198
+ /**
199
+ * SlottedHolder 컨트랙 — cellId 에 대한 SlotTarget. Mover.pickAndPlace 의 dest 로 넘김.
75
200
  *
76
- * In simulation mode, carriers become children of their RackCell,
77
- * and each RackCell provides its own attachPointFor(). So this method
78
- * is only invoked on direct-child carriers in monitoring mode — it
79
- * returns the rack's own object3d as the attach frame (default behavior).
201
+ * Signature overloads:
202
+ * slotTargetAt('0-0-5') string cellId 직접
203
+ * slotTargetAt(1, 1, 6) — 1-based (bay, row, level)
204
+ */
205
+ slotTargetAt(idOrBay: string | number, row?: number, level?: number): SlotTarget;
206
+ /**
207
+ * SlotTarget 의 2D center 위임 — Mover.moveTo 의 2D path 계산에 사용.
208
+ *
209
+ * 반환값은 *rack 자체의 local frame* (rack 의 left/top 미포함) — 즉 cell 의 위치를
210
+ * rack 의 *내부 좌표계로* 표현. SlotTarget.toScene 이 rack.toScene 을 위임해 *rack 의
211
+ * rotation / 부모 chain 변환 포함* 한 절대 좌표로 변환.
212
+ *
213
+ * 이전 결함: rack.left/top 을 포함해 model-layer 프레임 좌표 반환 + toScene 미구현 →
214
+ * rack 이 rotated 또는 nested 일 때 X 가 어긋났음.
215
+ */
216
+ cellCenter2D(cellId: string): {
217
+ x: number;
218
+ y: number;
219
+ } | null;
220
+ /**
221
+ * SlotTarget 의 toScene 위임 — *rack-local* 좌표를 *scene-absolute* 로 변환.
222
+ * rack.toScene 이 rack 의 rotation / translation / parent chain 모두 처리.
223
+ */
224
+ cellToScene(localX: number, localY: number): {
225
+ x: number;
226
+ y: number;
227
+ };
228
+ /** cellId 별 attach anchor object3d cache (rack.object3d 의 자식). */
229
+ private _attachAnchorByCell;
230
+ /**
231
+ * cellId 위치에 lightweight anchor object3d 를 *singleton 으로* 보장 + 갱신.
232
+ * 이 anchor 가:
233
+ * - Carriable.applyHolderAttachPoint 가 attach 하는 frame
234
+ * - SlotTarget._realObject.object3d 의 proxy
235
+ * - 두 용도가 *같은 object3d* 를 공유해 carrier 가 transient 동안 SlotTarget 의
236
+ * pose 와 정확히 동기화.
80
237
  */
81
- attachPointFor(_carrier: Component): AttachFrame | null;
238
+ private _ensureCellAttachObject3d;
82
239
  /**
83
- * 2D — top-down rectangle showing the rack footprint, with subdivisions
84
- * suggesting the bay layout.
240
+ * 2D — top-down rectangle showing the rack footprint with bay subdivisions.
241
+ * 편집/배치 가능하도록 *명시 fill + stroke* — pipeline 분기 무관하게 항상
242
+ * 보임. fill 은 반투명 (carrier / cell 위 overlay).
85
243
  */
86
244
  render(ctx: CanvasRenderingContext2D): void;
87
245
  get fillStyle(): string;
246
+ onchangeData(): void;
247
+ private _legendTarget?;
248
+ /**
249
+ * Legend 컴포넌트 lookup. 우선순위:
250
+ * 1) state.legendTarget id 명시
251
+ * 2) scene 전체에서 `type='legend'` 첫 번째 컴포넌트 (자동 발견)
252
+ */
253
+ get legendTarget(): Component | undefined;
254
+ private _onLegendChanged;
255
+ /**
256
+ * record 의 legend.field 값을 ranges 와 매칭해 색상 해석.
257
+ * - `range.value === recordValue` (카테고리 일치)
258
+ * - `range.min ≤ Number(v) < range.max` (수치 범위)
259
+ * - 매칭 없으면 `defaultColor` 또는 undefined
260
+ */
261
+ resolveLegendColor(record: any): string | undefined;
262
+ /**
263
+ * things-scene EventManager3D 가 raycast → object3d.userData.context.component 의
264
+ * `trigger("click", mouseEvent)` 을 호출 → eventMap 으로 receive.
265
+ * `(self).(self).click` 으로 등록해 *우리 rack 의 어떤 mesh 든 클릭됐을 때* 발사.
266
+ */
267
+ get eventMap(): {
268
+ '(self)': {
269
+ '(self)': {
270
+ click: (mouseEvent: MouseEvent) => void;
271
+ };
272
+ };
273
+ };
274
+ private _onRackClick;
275
+ /**
276
+ * state.popupRef 가 가리키는 Popup 컴포넌트를 invoke. anchor 를 SlotTarget 으로
277
+ * 지정 — SlotTarget._realObject.object3d 가 cellId 위치의 anchor object3d 를
278
+ * 가리켜 tether / projectToScreen 정확.
279
+ *
280
+ * - popupRef 미설정 → no-op (event 만 발사된 상태로 남음)
281
+ * - 다른 cell 클릭 시 popup 이 새 anchor 로 "이동" (Popup 의 board 등 설정 유지)
282
+ * - frame/empty 영역 클릭 시 호출 안 됨 → popup 그대로 유지
283
+ * - 명시적 close 버튼은 popup 자체의 closable 옵션이 처리
284
+ */
285
+ private _invokePopup;
286
+ /**
287
+ * 클릭 시 framework 의 mouse NDC (이미 InteractionManager 가 set 한 상태) 를 재사용해
288
+ * raycast → *우리 rack* 의 어떤 mesh 가 closest hit 인지 반환. 다른 object 가 더 가까우면
289
+ * undefined (다른 rack 또는 무관 mesh 의 hit).
290
+ *
291
+ * 접근 경로:
292
+ * 1. ThreeCapability — ModelLayer 는 `_threeCapability`, ThreeContainer 는 `_capability`.
293
+ * capability 의 `getObjectsByRaycast()` 가 *동일한* mouse NDC 로 framework 가 click
294
+ * 처리 직전에 쓴 그 raycaster 를 재사용 (가장 정확).
295
+ * 2. capability 가 없는 컨테이너 — public scene3d / renderer3d / camera + mouseEvent
296
+ * 좌표로 자체 ndc 변환 후 fresh raycaster.
297
+ */
298
+ private _raycastRackHit;
299
+ /**
300
+ * world point → cellId 역산.
301
+ *
302
+ * 1. rack 의 `matrixWorld.invert()` 로 world → rack-local 변환 (rack 의 회전·이동
303
+ * 반영)
304
+ * 2. rack-local point 의 `(x, y)` 를 (bay, level) 격자에 매핑
305
+ *
306
+ * 범위 밖이면 `"out-of-bounds(...)"` 문자열 반환 (caller 가 무시).
307
+ */
308
+ private _cellIdFromWorldPoint;
88
309
  buildRealObject(): RealObject | undefined;
89
310
  }
90
311
  export {};