@operato/scene-storage 10.0.0-beta.41 → 10.0.0-beta.43

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 (85) hide show
  1. package/CHANGELOG.md +20 -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/crane.js +1 -1
  5. package/dist/crane.js.map +1 -1
  6. package/dist/index.d.ts +3 -4
  7. package/dist/index.js +1 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/parcel-3d.js +42 -9
  10. package/dist/parcel-3d.js.map +1 -1
  11. package/dist/rack-grid-3d.d.ts +18 -7
  12. package/dist/rack-grid-3d.js +372 -69
  13. package/dist/rack-grid-3d.js.map +1 -1
  14. package/dist/rack-grid-cell.d.ts +21 -72
  15. package/dist/rack-grid-cell.js +147 -243
  16. package/dist/rack-grid-cell.js.map +1 -1
  17. package/dist/rack-grid.d.ts +277 -56
  18. package/dist/rack-grid.js +1230 -695
  19. package/dist/rack-grid.js.map +1 -1
  20. package/dist/rack-materials.d.ts +9 -0
  21. package/dist/rack-materials.js +55 -0
  22. package/dist/rack-materials.js.map +1 -0
  23. package/dist/storage-rack-3d.d.ts +15 -0
  24. package/dist/storage-rack-3d.js +131 -30
  25. package/dist/storage-rack-3d.js.map +1 -1
  26. package/dist/storage-rack.d.ts +242 -45
  27. package/dist/storage-rack.js +684 -106
  28. package/dist/storage-rack.js.map +1 -1
  29. package/package.json +3 -3
  30. package/src/crane.ts +1 -1
  31. package/src/index.ts +3 -4
  32. package/src/parcel-3d.ts +41 -9
  33. package/src/rack-grid-3d.ts +383 -80
  34. package/src/rack-grid-cell.ts +161 -305
  35. package/src/rack-grid.ts +1263 -762
  36. package/src/rack-materials.ts +61 -0
  37. package/src/storage-rack-3d.ts +144 -30
  38. package/src/storage-rack.ts +763 -111
  39. package/test/test-carrier-lifecycle.ts +361 -0
  40. package/test/test-coord-alignment.ts +201 -0
  41. package/test/test-external-to-rack.ts +461 -0
  42. package/test/test-mover-concurrent-bug.ts +304 -0
  43. package/test/test-mover-rollback.ts +290 -0
  44. package/test/test-r19-place-absorb.ts +174 -0
  45. package/test/test-rack-3d-attach-real.ts +301 -0
  46. package/test/test-rack-concurrent.ts +254 -0
  47. package/test/test-rack-edge-cases.ts +323 -0
  48. package/test/test-rack-grid-cell.ts +318 -0
  49. package/test/test-rack-grid-location.ts +657 -0
  50. package/test/test-real-3d-positioning.ts +158 -0
  51. package/test/test-slot-center-convention.ts +116 -0
  52. package/test/test-slot-target.ts +189 -0
  53. package/test/test-storage-rack-batched.ts +606 -0
  54. package/test/test-storage-rack-click.ts +329 -0
  55. package/test/test-storage-rack-slot-api.ts +357 -0
  56. package/test/test-toscene-convention.ts +162 -0
  57. package/test/test-user-scenario-sequential.ts +334 -0
  58. package/translations/en.json +2 -0
  59. package/translations/ja.json +2 -0
  60. package/translations/ko.json +2 -0
  61. package/translations/ms.json +2 -0
  62. package/translations/zh.json +2 -0
  63. package/tsconfig.tsbuildinfo +1 -1
  64. package/dist/rack-column.d.ts +0 -35
  65. package/dist/rack-column.js +0 -258
  66. package/dist/rack-column.js.map +0 -1
  67. package/dist/rack-grid-helpers.d.ts +0 -28
  68. package/dist/rack-grid-helpers.js +0 -71
  69. package/dist/rack-grid-helpers.js.map +0 -1
  70. package/dist/rack-grid-location.d.ts +0 -37
  71. package/dist/rack-grid-location.js +0 -227
  72. package/dist/rack-grid-location.js.map +0 -1
  73. package/dist/storage-cell-3d.d.ts +0 -25
  74. package/dist/storage-cell-3d.js +0 -88
  75. package/dist/storage-cell-3d.js.map +0 -1
  76. package/dist/storage-cell.d.ts +0 -73
  77. package/dist/storage-cell.js +0 -215
  78. package/dist/storage-cell.js.map +0 -1
  79. package/src/rack-column.ts +0 -340
  80. package/src/rack-grid-helpers.ts +0 -77
  81. package/src/rack-grid-location.ts +0 -286
  82. package/src/storage-cell-3d.ts +0 -101
  83. package/src/storage-cell.ts +0 -267
  84. package/test/test-cell-position.ts +0 -105
  85. package/test/test-rack-grid.ts +0 -77
@@ -0,0 +1,318 @@
1
+ /*
2
+ * RackGridCell + RackGrid hybrid integration tests.
3
+ *
4
+ * 검증 대상:
5
+ * - cell-component 가 있을 때 locationOf / cellIdOfLocation 이 *cell.state* lookup
6
+ * - cellOverrides fallback 도 여전히 동작 (cell-component 없는 경우)
7
+ * - setIsEmpty 가 cell-component 우선 mutate
8
+ * - increaseLocation 이 cell-component 우선 mutate + cellOverrides 비손상
9
+ * - cell.state.shelfLocations 가 RackGrid 의 shelfLocations 보다 우선
10
+ *
11
+ * 진짜 RackGrid/RackGridCell 인스턴스화는 things-scene 의존성으로 비현실적. 따라서
12
+ * MiniGridWithCells — RackGrid 의 알고리즘 + cell-component 들의 state 시뮬.
13
+ */
14
+
15
+ import 'should'
16
+
17
+ interface FakeCellState {
18
+ type: string
19
+ cellId?: string
20
+ section?: string
21
+ unit?: string
22
+ shelfLocations?: string
23
+ isEmpty?: boolean
24
+ border?: any
25
+ }
26
+
27
+ class FakeCell {
28
+ state: FakeCellState
29
+ constructor(initial: FakeCellState) {
30
+ this.state = { type: 'rack-grid-cell', ...initial }
31
+ }
32
+ set(key: any, value?: any) {
33
+ if (typeof key === 'string') {
34
+ if (value === null) delete (this.state as any)[key]
35
+ else (this.state as any)[key] = value
36
+ } else {
37
+ Object.assign(this.state, key)
38
+ }
39
+ }
40
+ getState(key: string) { return (this.state as any)[key] }
41
+ }
42
+
43
+ class MiniGridWithCells {
44
+ state: any
45
+ components: FakeCell[] = []
46
+ private _locationIndexCache?: Map<string, string>
47
+
48
+ constructor(initialState: any = {}, opts: { withCells?: boolean } = {}) {
49
+ this.state = {
50
+ columns: 4,
51
+ rows: 1,
52
+ shelves: 4,
53
+ sectionDigits: 2,
54
+ unitDigits: 2,
55
+ locPattern: '{z}{s}-{u}-{sh}',
56
+ zone: 'Z',
57
+ cellOverrides: {},
58
+ ...initialState
59
+ }
60
+ if (opts.withCells !== false) this._buildCells()
61
+ }
62
+
63
+ setState(partial: any) {
64
+ this.state = { ...this.state, ...partial }
65
+ this._locationIndexCache = undefined
66
+ }
67
+
68
+ invalidateLocationIndex() { this._locationIndexCache = undefined }
69
+
70
+ get columns(): number { return this.state.columns ?? 4 }
71
+ get rackRows(): number { return this.state.rows ?? 1 }
72
+ get shelves(): number { return this.state.shelves ?? 4 }
73
+ get cellOverrides() { return this.state.cellOverrides ?? {} }
74
+
75
+ private _buildCells() {
76
+ const cols = this.columns
77
+ const rows = this.rackRows
78
+ for (let r = 0; r < rows; r++) {
79
+ for (let c = 0; c < cols; c++) {
80
+ this.components.push(new FakeCell({ type: 'rack-grid-cell', cellId: `${c}-${r}` }))
81
+ }
82
+ }
83
+ }
84
+
85
+ cellAt(col: number, row: number = 0): FakeCell | null {
86
+ const idx = row * this.columns + col
87
+ return this.components[idx] ?? null
88
+ }
89
+
90
+ cellAtBayKey(bayKey: string): FakeCell | null {
91
+ const parts = bayKey.split('-').map(Number)
92
+ if (parts.length !== 2) return null
93
+ return this.cellAt(parts[0], parts[1])
94
+ }
95
+
96
+ parseCellId(cellId: string) {
97
+ const parts = cellId.split('-').map(Number)
98
+ if (parts.length !== 3 || parts.some(n => !Number.isFinite(n))) return null
99
+ return { col: parts[0], row: parts[1], shelf: parts[2] }
100
+ }
101
+
102
+ parsePosKey(posKey: string) {
103
+ const parts = posKey.split('-').map(Number)
104
+ if (parts.length !== 2 || parts.some(n => !Number.isFinite(n))) return null
105
+ return { col: parts[0], row: parts[1] }
106
+ }
107
+
108
+ private _shelfLabelsFromCell(cellShelfLocations: string | undefined): string[] {
109
+ const input = cellShelfLocations ?? this.state.shelfLocations
110
+ const levels = this.shelves
111
+ const parts = (input || '').split(',')
112
+ const out: string[] = []
113
+ for (let i = 0; i < levels; i++) {
114
+ const p = parts[i]
115
+ out[i] = p && p.trim().length > 0 ? p.trim() : String(i + 1)
116
+ }
117
+ return out
118
+ }
119
+
120
+ locationOf(cellId: string): string | null {
121
+ const parsed = this.parseCellId(cellId)
122
+ if (!parsed) return null
123
+ const posKey = `${parsed.col}-${parsed.row}`
124
+
125
+ let section, unit, isEmpty, cellShelfLocations: string | undefined
126
+ const cell = this.cellAt(parsed.col, parsed.row)
127
+ if (cell) {
128
+ section = cell.state.section
129
+ unit = cell.state.unit
130
+ isEmpty = cell.state.isEmpty
131
+ cellShelfLocations = cell.state.shelfLocations
132
+ } else {
133
+ const o = this.cellOverrides[posKey]
134
+ section = o?.section; unit = o?.unit; isEmpty = o?.isEmpty
135
+ }
136
+
137
+ if (!section || !unit || isEmpty) return null
138
+ const pattern = this.state.locPattern ?? '{z}{s}-{u}-{sh}'
139
+ const zone = this.state.zone ?? ''
140
+ const shelfLabel = this._shelfLabelsFromCell(cellShelfLocations)[parsed.shelf] ?? String(parsed.shelf + 1)
141
+ return pattern
142
+ .replace(/\{z\}/g, zone)
143
+ .replace(/\{s\}/g, section)
144
+ .replace(/\{u\}/g, unit)
145
+ .replace(/\{sh\}/g, shelfLabel)
146
+ }
147
+
148
+ cellIdOfLocation(location: string): string | null {
149
+ return this._locationIndex.get(location) ?? null
150
+ }
151
+
152
+ private get _locationIndex(): Map<string, string> {
153
+ if (this._locationIndexCache) return this._locationIndexCache
154
+ const map = new Map<string, string>()
155
+ const shelves = this.shelves
156
+
157
+ // cells first
158
+ for (const cell of this.components) {
159
+ if (cell.state.type !== 'rack-grid-cell') continue
160
+ const bayKey = cell.state.cellId
161
+ if (!bayKey) continue
162
+ const parsed = this.parsePosKey(bayKey)
163
+ if (!parsed) continue
164
+ for (let s = 0; s < shelves; s++) {
165
+ const cellId = `${parsed.col}-${parsed.row}-${s}`
166
+ const loc = this.locationOf(cellId)
167
+ if (loc) map.set(loc, cellId)
168
+ }
169
+ }
170
+ // overrides fallback
171
+ for (const posKey of Object.keys(this.cellOverrides)) {
172
+ const o = this.cellOverrides[posKey]
173
+ if (!o.section || !o.unit || o.isEmpty) continue
174
+ const parsed = this.parsePosKey(posKey)
175
+ if (!parsed) continue
176
+ for (let s = 0; s < shelves; s++) {
177
+ const cellId = `${parsed.col}-${parsed.row}-${s}`
178
+ const loc = this.locationOf(cellId)
179
+ if (loc && !map.has(loc)) map.set(loc, cellId)
180
+ }
181
+ }
182
+
183
+ this._locationIndexCache = map
184
+ return map
185
+ }
186
+
187
+ setIsEmpty(posKeys: string[], isEmpty: boolean): void {
188
+ const hasCells = this.components.some(c => c.state.type === 'rack-grid-cell')
189
+ if (hasCells) {
190
+ for (const k of posKeys) {
191
+ const cell = this.cellAtBayKey(k)
192
+ cell?.set('isEmpty', isEmpty)
193
+ }
194
+ this.invalidateLocationIndex()
195
+ return
196
+ }
197
+ // fallback
198
+ const current = this.cellOverrides
199
+ const next = { ...current }
200
+ for (const k of posKeys) {
201
+ const existing = next[k] ?? {}
202
+ const merged: any = { ...existing, isEmpty }
203
+ if (!isEmpty && !existing.section && !existing.unit) delete next[k]
204
+ else next[k] = merged
205
+ }
206
+ this.setState({ cellOverrides: next })
207
+ }
208
+ }
209
+
210
+ // ── 1: cell-component 가 있을 때 locationOf cell.state 직접 lookup ──────────
211
+
212
+ describe('RackGridCell: cell.state 가 source of truth', () => {
213
+ it('cell.state.section/unit 명시 → location 부여', () => {
214
+ const g = new MiniGridWithCells()
215
+ g.cellAt(0, 0)!.set({ section: '01', unit: '01' })
216
+ g.locationOf('0-0-0').should.equal('Z01-01-1')
217
+ g.locationOf('0-0-3').should.equal('Z01-01-4')
218
+ })
219
+
220
+ it('section 만 / unit 만 → null', () => {
221
+ const g = new MiniGridWithCells()
222
+ g.cellAt(0, 0)!.set({ section: '01' })
223
+ ;(g.locationOf('0-0-0') === null).should.be.true()
224
+ g.cellAt(1, 0)!.set({ unit: '02' })
225
+ ;(g.locationOf('1-0-0') === null).should.be.true()
226
+ })
227
+
228
+ it('cell.state.isEmpty=true → location null', () => {
229
+ const g = new MiniGridWithCells()
230
+ g.cellAt(0, 0)!.set({ section: '01', unit: '01', isEmpty: true })
231
+ ;(g.locationOf('0-0-0') === null).should.be.true()
232
+ })
233
+
234
+ it('cell.state.shelfLocations 가 RackGrid.shelfLocations 보다 우선', () => {
235
+ const g = new MiniGridWithCells({ shelfLocations: 'A,B,C,D' })
236
+ g.cellAt(0, 0)!.set({ section: '01', unit: '01' })
237
+ g.locationOf('0-0-0').should.equal('Z01-01-A')
238
+ // cell-specific override
239
+ g.cellAt(0, 0)!.set('shelfLocations', 'X,Y,Z,W')
240
+ g.locationOf('0-0-0').should.equal('Z01-01-X')
241
+ g.locationOf('0-0-3').should.equal('Z01-01-W')
242
+ })
243
+
244
+ it('cell.state 변경 → location 역인덱스 무효화 (invalidate 후 재구축)', () => {
245
+ const g = new MiniGridWithCells()
246
+ g.cellAt(0, 0)!.set({ section: '01', unit: '01' })
247
+ g.cellIdOfLocation('Z01-01-1').should.equal('0-0-0')
248
+ // cell 의 section 변경
249
+ g.cellAt(0, 0)!.set('section', '02')
250
+ g.invalidateLocationIndex() // cell.set 이 parent.invalidate 호출 시뮬
251
+ g.cellIdOfLocation('Z02-01-1').should.equal('0-0-0')
252
+ ;(g.cellIdOfLocation('Z01-01-1') === null).should.be.true()
253
+ })
254
+ })
255
+
256
+ // ── 2: setIsEmpty 가 cell-component 우선 mutate ─────────────────────────────
257
+
258
+ describe('RackGridCell: setIsEmpty 는 cell.set() 우선', () => {
259
+ it('cell-component 있으면 cell.state.isEmpty 갱신 (cellOverrides 무영향)', () => {
260
+ const g = new MiniGridWithCells()
261
+ g.setIsEmpty(['1-0', '2-0'], true)
262
+ g.cellAt(1, 0)!.state.isEmpty!.should.be.true()
263
+ g.cellAt(2, 0)!.state.isEmpty!.should.be.true()
264
+ Object.keys(g.cellOverrides).length.should.equal(0) // fallback 안 침범
265
+ })
266
+
267
+ it('cell-component 없으면 cellOverrides 갱신 (fallback)', () => {
268
+ const g = new MiniGridWithCells({}, { withCells: false })
269
+ g.setIsEmpty(['1-0'], true)
270
+ g.cellOverrides['1-0'].isEmpty!.should.be.true()
271
+ })
272
+ })
273
+
274
+ // ── 3: cellOverrides fallback 도 여전히 동작 (cell-component 없을 때) ──────
275
+
276
+ describe('RackGridCell: cellOverrides fallback 호환', () => {
277
+ it('cell-component 없으면 cellOverrides 가 source of truth', () => {
278
+ const g = new MiniGridWithCells({
279
+ cellOverrides: { '0-0': { section: '01', unit: '01' } }
280
+ }, { withCells: false })
281
+ g.locationOf('0-0-0').should.equal('Z01-01-1')
282
+ g.cellIdOfLocation('Z01-01-1').should.equal('0-0-0')
283
+ })
284
+
285
+ it('cell-component 있으면 cellOverrides 무시 (cell.state 가 single source of truth)', () => {
286
+ // cell-component 가 있으면 *그 cell.state 만* lookup. cellOverrides 의 동일 bayKey
287
+ // 데이터는 무시됨 → cell-component 의 데이터로 일관. (transitional load 시 별도
288
+ // migrate 로직이 cellOverrides → cell.state 옮김)
289
+ const g = new MiniGridWithCells({
290
+ cellOverrides: { '3-0': { section: '02', unit: '03' } }
291
+ })
292
+ ;(g.locationOf('3-0-0') === null).should.be.true() // cell.state 비어있음 → null
293
+ })
294
+ })
295
+
296
+ // ── 4: cell-component 의 cellId 동기화 (순서 기반 bayKey) ──────────────────
297
+
298
+ describe('RackGridCell: cellId 가 components 순서 기반', () => {
299
+ it('4×1 grid 에서 cellId 가 0-0, 1-0, 2-0, 3-0', () => {
300
+ const g = new MiniGridWithCells({ columns: 4, rows: 1 })
301
+ g.components[0].state.cellId!.should.equal('0-0')
302
+ g.components[1].state.cellId!.should.equal('1-0')
303
+ g.components[2].state.cellId!.should.equal('2-0')
304
+ g.components[3].state.cellId!.should.equal('3-0')
305
+ })
306
+
307
+ it('3×2 grid 에서 row-major 순서', () => {
308
+ const g = new MiniGridWithCells({ columns: 3, rows: 2 })
309
+ // row 0: 0-0, 1-0, 2-0
310
+ g.components[0].state.cellId!.should.equal('0-0')
311
+ g.components[1].state.cellId!.should.equal('1-0')
312
+ g.components[2].state.cellId!.should.equal('2-0')
313
+ // row 1: 0-1, 1-1, 2-1
314
+ g.components[3].state.cellId!.should.equal('0-1')
315
+ g.components[4].state.cellId!.should.equal('1-1')
316
+ g.components[5].state.cellId!.should.equal('2-1')
317
+ })
318
+ })