@operato/scene-storage 10.0.0-beta.28 → 10.0.0-beta.30

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 (54) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/asrs-crane-3d.d.ts +10 -0
  3. package/dist/asrs-crane-3d.js +17 -0
  4. package/dist/asrs-crane-3d.js.map +1 -1
  5. package/dist/asrs-crane.d.ts +49 -13
  6. package/dist/asrs-crane.js +120 -16
  7. package/dist/asrs-crane.js.map +1 -1
  8. package/dist/asrs-rack.d.ts +49 -19
  9. package/dist/asrs-rack.js +108 -20
  10. package/dist/asrs-rack.js.map +1 -1
  11. package/dist/box.d.ts +3 -3
  12. package/dist/box.js +1 -2
  13. package/dist/box.js.map +1 -1
  14. package/dist/generic-container.d.ts +2 -2
  15. package/dist/generic-container.js +1 -2
  16. package/dist/generic-container.js.map +1 -1
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.js +2 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/pallet.d.ts +2 -2
  21. package/dist/pallet.js +1 -2
  22. package/dist/pallet.js.map +1 -1
  23. package/dist/parcel.d.ts +3 -3
  24. package/dist/parcel.js +1 -2
  25. package/dist/parcel.js.map +1 -1
  26. package/dist/rack-cell-3d.d.ts +25 -0
  27. package/dist/rack-cell-3d.js +88 -0
  28. package/dist/rack-cell-3d.js.map +1 -0
  29. package/dist/rack-cell.d.ts +56 -0
  30. package/dist/rack-cell.js +200 -0
  31. package/dist/rack-cell.js.map +1 -0
  32. package/dist/spot.d.ts +4 -11
  33. package/dist/spot.js +2 -3
  34. package/dist/spot.js.map +1 -1
  35. package/dist/templates/index.d.ts +42 -0
  36. package/dist/templates/index.js +43 -1
  37. package/dist/templates/index.js.map +1 -1
  38. package/package.json +9 -4
  39. package/src/asrs-crane-3d.ts +20 -0
  40. package/src/asrs-crane.ts +137 -16
  41. package/src/asrs-rack.ts +119 -20
  42. package/src/box.ts +2 -4
  43. package/src/generic-container.ts +1 -3
  44. package/src/index.ts +3 -0
  45. package/src/pallet.ts +1 -3
  46. package/src/parcel.ts +2 -4
  47. package/src/rack-cell-3d.ts +101 -0
  48. package/src/rack-cell.ts +228 -0
  49. package/src/spot.ts +4 -5
  50. package/src/templates/index.ts +43 -1
  51. package/test/setup.js +279 -0
  52. package/test/test-asrs-crane.ts +319 -0
  53. package/tsconfig.json +2 -1
  54. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,319 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * AsrsCrane integration tests — pick/place data-flow for the ASRS crane.
5
+ *
6
+ * Uses fake/minimal containers (no DOM, no Three.js, no RAF) so they run in
7
+ * Node with tsx.
8
+ *
9
+ * Test infrastructure mirrors test-transfer-scenarios.ts in scene-base:
10
+ * - FakeBase: minimal child-tracking container with setState support
11
+ * - FakeCrane: Mover(ContainerCapacity(FakeBase)) — instant moveTo, AsrsCrane
12
+ * engage() semantics (status + carriageHeight), slots = [{id:'forks', maxCount:1}]
13
+ * - FakeRackCell: mimics RackCell.receive / dispatch / canReceive protocol
14
+ */
15
+
16
+ import 'should'
17
+ import { ContainerCapacity, TRANSFER_SLOT_KEY } from '@hatiolab/things-scene'
18
+ import Mover from '../../scene-base/src/mover.js'
19
+
20
+ // ── Shared fake infrastructure ─────────────────────────────────────────────────
21
+
22
+ class FakeBase {
23
+ _components: any[] = []
24
+ state: Record<string, any>
25
+ parent: any = null
26
+ root: any = null
27
+
28
+ constructor(state: Record<string, any> = {}) {
29
+ this.state = state
30
+ }
31
+
32
+ get components() {
33
+ return this._components
34
+ }
35
+
36
+ setState(keyOrObj: string | Record<string, any>, value?: any) {
37
+ if (typeof keyOrObj === 'object') {
38
+ Object.assign(this.state, keyOrObj)
39
+ } else {
40
+ this.state[keyOrObj as string] = value
41
+ }
42
+ }
43
+
44
+ getState(key: string) {
45
+ return this.state[key]
46
+ }
47
+
48
+ addComponent(child: any) {
49
+ if (child.parent && child.parent !== this) {
50
+ const idx = child.parent._components?.indexOf(child) ?? -1
51
+ if (idx >= 0) child.parent._components.splice(idx, 1)
52
+ child.parent = null
53
+ }
54
+ if (!this._components.includes(child)) {
55
+ this._components.push(child)
56
+ }
57
+ child.parent = this
58
+ }
59
+
60
+ removeComponent(child: any) {
61
+ const idx = this._components.indexOf(child)
62
+ if (idx >= 0) this._components.splice(idx, 1)
63
+ if (child.parent === this) child.parent = null
64
+ }
65
+
66
+ reparent(child: any, _options?: any) {
67
+ this.addComponent(child)
68
+ }
69
+
70
+ trigger(_name: string, ..._args: any[]) {}
71
+ }
72
+
73
+ function makeCarrier(zPos?: number) {
74
+ return {
75
+ parent: null as any,
76
+ state: { type: 'pallet', ...(zPos !== undefined ? { zPos } : {}) },
77
+ type: 'pallet',
78
+ [TRANSFER_SLOT_KEY]: undefined as any,
79
+ setState(_s: any) {}
80
+ }
81
+ }
82
+
83
+ // FakeCrane: Mover(ContainerCapacity(FakeBase)) with instant moveTo +
84
+ // AsrsCrane engage() semantics (status + carriageHeight snap from carrier.state.zPos).
85
+ const FakeCraneBase = Mover(ContainerCapacity(FakeBase as any))
86
+
87
+ class FakeCrane extends (FakeCraneBase as any) {
88
+ constructor(state: Record<string, any> = {}) {
89
+ super({ status: 'idle', carriageHeight: 0, ...state })
90
+ }
91
+
92
+ get slots() {
93
+ return [{ id: 'forks', maxCount: 1 }]
94
+ }
95
+
96
+ moveTo(_target: any, _options: any = {}): Promise<void> {
97
+ return Promise.resolve()
98
+ }
99
+
100
+ async engage(target: any, kind: 'pick' | 'place'): Promise<void> {
101
+ if (kind === 'pick') {
102
+ this.state.status = 'loading'
103
+ const carrierY = target?.state?.zPos ?? null
104
+ if (carrierY !== null) this.state.carriageHeight = carrierY
105
+ } else {
106
+ this.state.status = 'unloading'
107
+ }
108
+ }
109
+
110
+ /** Semantic alias for pick (matches AsrsCrane.fetch). */
111
+ fetch(carrier: any, options?: any): Promise<void> {
112
+ return (this as any).pick(carrier, options)
113
+ }
114
+
115
+ /** Semantic alias for place (matches AsrsCrane.deposit). */
116
+ deposit(carrier: any, cell: any, options?: any): Promise<void> {
117
+ return (this as any).place(carrier, cell, options)
118
+ }
119
+ }
120
+
121
+ // FakeRackCell: mimics RackCell.receive / dispatch / canReceive protocol.
122
+ // capacity=1 (single-slot cell).
123
+ class FakeRackCell extends FakeBase {
124
+ cellId: string
125
+
126
+ constructor(state: Record<string, any> = {}) {
127
+ super(state)
128
+ this.cellId = (state.cellId as string) || 'cell-0-0-0'
129
+ }
130
+
131
+ canReceive(_component?: any): boolean {
132
+ return this._components.length < 1
133
+ }
134
+
135
+ async receive(carrier: any, options: any = {}): Promise<void> {
136
+ if (!this.canReceive(carrier)) return
137
+ carrier[TRANSFER_SLOT_KEY] = this.cellId
138
+ this.reparent(carrier, options)
139
+ }
140
+
141
+ async dispatch(carrier: any, target: any, options: any = {}): Promise<void> {
142
+ this.removeComponent(carrier)
143
+ if (typeof target?.receive === 'function') {
144
+ await target.receive(carrier, options)
145
+ } else {
146
+ target?.reparent?.(carrier, options)
147
+ }
148
+ }
149
+ }
150
+
151
+ // ── Scenario 1: fetch (pick) ──────────────────────────────────────────────────
152
+
153
+ describe('AsrsCrane: fetch (pick)', () => {
154
+ it('fetch 후 carrier가 crane의 child', async () => {
155
+ const crane = new FakeCrane()
156
+ const carrier = makeCarrier()
157
+
158
+ await crane.fetch(carrier)
159
+
160
+ crane._components.should.containEql(carrier)
161
+ })
162
+
163
+ it('fetch 후 TRANSFER_SLOT_KEY = forks', async () => {
164
+ const crane = new FakeCrane()
165
+ const carrier = makeCarrier()
166
+
167
+ await crane.fetch(carrier)
168
+
169
+ carrier[TRANSFER_SLOT_KEY].should.equal('forks')
170
+ })
171
+
172
+ it('fetch engage → status loading', async () => {
173
+ const crane = new FakeCrane()
174
+ const carrier = makeCarrier()
175
+
176
+ await crane.fetch(carrier)
177
+
178
+ crane.state.status.should.equal('loading')
179
+ })
180
+
181
+ it('carrier에 zPos 있으면 carriageHeight 스냅', async () => {
182
+ const crane = new FakeCrane()
183
+ const carrier = makeCarrier(1500)
184
+
185
+ await crane.fetch(carrier)
186
+
187
+ crane.state.carriageHeight.should.equal(1500)
188
+ })
189
+
190
+ it('zPos 없으면 carriageHeight 변경 없음', async () => {
191
+ const crane = new FakeCrane({ carriageHeight: 500 })
192
+ const carrier = makeCarrier() // no zPos
193
+
194
+ await crane.fetch(carrier)
195
+
196
+ crane.state.carriageHeight.should.equal(500)
197
+ })
198
+
199
+ it('fetch 전에 carrier가 rack cell에 있었으면 거기서 제거', async () => {
200
+ const crane = new FakeCrane()
201
+ const cell = new FakeRackCell({ cellId: 'cell-0-0-0' })
202
+ const carrier = makeCarrier()
203
+ await cell.receive(carrier)
204
+
205
+ await crane.fetch(carrier)
206
+
207
+ crane._components.should.containEql(carrier)
208
+ cell._components.should.not.containEql(carrier)
209
+ })
210
+
211
+ it('maxCount(1) 가득 차면 canReceive false', async () => {
212
+ const crane = new FakeCrane()
213
+ const c1 = makeCarrier()
214
+ const c2 = makeCarrier()
215
+
216
+ await crane.fetch(c1)
217
+ ;(crane as any).canReceive(c2).should.be.false()
218
+ })
219
+ })
220
+
221
+ // ── Scenario 2: deposit (place) ───────────────────────────────────────────────
222
+
223
+ describe('AsrsCrane: deposit (place)', () => {
224
+ it('deposit 후 carrier가 rackCell의 child', async () => {
225
+ const crane = new FakeCrane()
226
+ const cell = new FakeRackCell({ cellId: 'cell-1-0-2' })
227
+ const carrier = makeCarrier()
228
+
229
+ crane.addComponent(carrier)
230
+ carrier[TRANSFER_SLOT_KEY] = 'forks'
231
+
232
+ await crane.deposit(carrier, cell)
233
+
234
+ cell._components.should.containEql(carrier)
235
+ crane._components.should.not.containEql(carrier)
236
+ })
237
+
238
+ it('deposit engage → status unloading', async () => {
239
+ const crane = new FakeCrane()
240
+ const cell = new FakeRackCell()
241
+ const carrier = makeCarrier()
242
+
243
+ crane.addComponent(carrier)
244
+ carrier[TRANSFER_SLOT_KEY] = 'forks'
245
+
246
+ await crane.deposit(carrier, cell)
247
+
248
+ crane.state.status.should.equal('unloading')
249
+ })
250
+
251
+ it('deposit 후 TRANSFER_SLOT_KEY = cellId', async () => {
252
+ const crane = new FakeCrane()
253
+ const cell = new FakeRackCell({ cellId: 'cell-3-0-1' })
254
+ const carrier = makeCarrier()
255
+
256
+ crane.addComponent(carrier)
257
+ carrier[TRANSFER_SLOT_KEY] = 'forks'
258
+
259
+ await crane.deposit(carrier, cell)
260
+
261
+ carrier[TRANSFER_SLOT_KEY].should.equal('cell-3-0-1')
262
+ })
263
+ })
264
+
265
+ // ── Scenario 3: fetch → deposit 전체 사이클 ──────────────────────────────────
266
+
267
+ describe('AsrsCrane: fetch → deposit 전체 사이클', () => {
268
+ it('cell-A → crane → cell-B', async () => {
269
+ const crane = new FakeCrane()
270
+ const cellA = new FakeRackCell({ cellId: 'cell-0-0-0' })
271
+ const cellB = new FakeRackCell({ cellId: 'cell-5-0-3' })
272
+ const carrier = makeCarrier()
273
+
274
+ // Pre-place carrier in cellA
275
+ await cellA.receive(carrier)
276
+ cellA._components.should.containEql(carrier)
277
+ carrier[TRANSFER_SLOT_KEY].should.equal('cell-0-0-0')
278
+
279
+ // Fetch from cellA
280
+ await crane.fetch(carrier)
281
+ crane._components.should.containEql(carrier)
282
+ cellA._components.should.not.containEql(carrier)
283
+ carrier[TRANSFER_SLOT_KEY].should.equal('forks')
284
+
285
+ // Deposit into cellB
286
+ await crane.deposit(carrier, cellB)
287
+ cellB._components.should.containEql(carrier)
288
+ crane._components.should.not.containEql(carrier)
289
+ carrier[TRANSFER_SLOT_KEY].should.equal('cell-5-0-3')
290
+ })
291
+
292
+ it('fetch → deposit 후 status 순서: loading → unloading', async () => {
293
+ const crane = new FakeCrane()
294
+ const cell = new FakeRackCell({ cellId: 'cell-0-0-0' })
295
+ const carrier = makeCarrier()
296
+
297
+ await crane.fetch(carrier)
298
+ crane.state.status.should.equal('loading')
299
+
300
+ await crane.deposit(carrier, cell)
301
+ crane.state.status.should.equal('unloading')
302
+ })
303
+
304
+ it('pickAndPlace: carrier를 cellA에서 cellB로 한 번에', async () => {
305
+ const crane = new FakeCrane()
306
+ const cellA = new FakeRackCell({ cellId: 'cell-2-0-1' })
307
+ const cellB = new FakeRackCell({ cellId: 'cell-7-0-3' })
308
+ const carrier = makeCarrier()
309
+
310
+ await cellA.receive(carrier)
311
+
312
+ await (crane as any).pickAndPlace(carrier, cellB)
313
+
314
+ cellB._components.should.containEql(carrier)
315
+ cellA._components.should.not.containEql(carrier)
316
+ crane._components.should.not.containEql(carrier)
317
+ carrier[TRANSFER_SLOT_KEY].should.equal('cell-7-0-3')
318
+ })
319
+ })
package/tsconfig.json CHANGED
@@ -19,5 +19,6 @@
19
19
  "incremental": true,
20
20
  "skipLibCheck": true
21
21
  },
22
- "include": ["**/*.ts", "*.d.ts"]
22
+ "include": ["src/**/*.ts"],
23
+ "exclude": ["dist/**", "test/**", "node_modules"]
23
24
  }