@shipload/sdk 1.0.0-next.6 → 1.0.0-next.60

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 (155) hide show
  1. package/lib/scan.d.ts +52 -0
  2. package/lib/scan.js +162 -0
  3. package/lib/scan.js.map +1 -0
  4. package/lib/scan.m.js +152 -0
  5. package/lib/scan.m.js.map +1 -0
  6. package/lib/shipload.d.ts +3589 -1363
  7. package/lib/shipload.js +15700 -5454
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +15423 -5402
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1173 -0
  12. package/lib/testing.js +4947 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4941 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.test.ts +82 -0
  18. package/src/capabilities/craftable.ts +82 -0
  19. package/src/capabilities/crafting.test.ts +35 -0
  20. package/src/capabilities/crafting.ts +34 -7
  21. package/src/capabilities/gathering.test.ts +29 -0
  22. package/src/capabilities/gathering.ts +37 -19
  23. package/src/capabilities/hauling.ts +0 -5
  24. package/src/capabilities/index.ts +0 -1
  25. package/src/capabilities/modules.ts +42 -0
  26. package/src/capabilities/movement.ts +1 -1
  27. package/src/capabilities/storage.ts +16 -1
  28. package/src/contracts/platform.ts +231 -3
  29. package/src/contracts/server.ts +1518 -501
  30. package/src/coordinates/address.ts +88 -0
  31. package/src/coordinates/constants.test.ts +15 -0
  32. package/src/coordinates/constants.ts +23 -0
  33. package/src/coordinates/index.ts +15 -0
  34. package/src/coordinates/memo.test.ts +47 -0
  35. package/src/coordinates/memo.ts +20 -0
  36. package/src/coordinates/permutation.ts +77 -0
  37. package/src/coordinates/regions.ts +48 -0
  38. package/src/coordinates/sectors.ts +115 -0
  39. package/src/data/capabilities.ts +49 -18
  40. package/src/data/capability-formulas.ts +26 -14
  41. package/src/data/catalog.ts +0 -5
  42. package/src/data/colors.ts +13 -47
  43. package/src/data/entities.json +379 -14
  44. package/src/data/item-ids.ts +48 -12
  45. package/src/data/items.json +301 -74
  46. package/src/data/kind-registry.json +195 -0
  47. package/src/data/kind-registry.ts +182 -0
  48. package/src/data/metadata.ts +277 -48
  49. package/src/data/recipes-runtime.ts +6 -23
  50. package/src/data/recipes.json +2074 -173
  51. package/src/derivation/build-methods.test.ts +13 -0
  52. package/src/derivation/build-methods.ts +48 -0
  53. package/src/derivation/capabilities.test.ts +348 -0
  54. package/src/derivation/capabilities.ts +692 -0
  55. package/src/derivation/capability-mappings.ts +60 -16
  56. package/src/derivation/crafting.test.ts +17 -0
  57. package/src/derivation/crafting.ts +56 -33
  58. package/src/derivation/index.ts +27 -2
  59. package/src/derivation/recipe-usage.test.ts +88 -0
  60. package/src/derivation/recipe-usage.ts +141 -0
  61. package/src/derivation/reserve-regen.ts +34 -0
  62. package/src/derivation/resources.ts +9 -1
  63. package/src/derivation/rollups.test.ts +55 -0
  64. package/src/derivation/rollups.ts +72 -0
  65. package/src/derivation/stars.test.ts +67 -0
  66. package/src/derivation/stars.ts +25 -0
  67. package/src/derivation/stat-scaling.ts +12 -0
  68. package/src/derivation/stats.ts +6 -6
  69. package/src/derivation/stratum.ts +26 -22
  70. package/src/derivation/tiers.ts +40 -7
  71. package/src/derivation/upgrades.ts +14 -0
  72. package/src/derivation/wormhole.ts +154 -0
  73. package/src/entities/entity.ts +102 -0
  74. package/src/entities/gamestate.ts +3 -28
  75. package/src/entities/makers.ts +141 -135
  76. package/src/entities/slot-multiplier.ts +43 -0
  77. package/src/errors.ts +12 -16
  78. package/src/format.ts +26 -4
  79. package/src/index-module.ts +333 -35
  80. package/src/managers/actions.ts +668 -103
  81. package/src/managers/base.ts +6 -2
  82. package/src/managers/cluster.test.ts +39 -0
  83. package/src/managers/cluster.ts +53 -0
  84. package/src/managers/construction-types.ts +80 -0
  85. package/src/managers/construction.ts +443 -0
  86. package/src/managers/context.ts +29 -1
  87. package/src/managers/coordinates.ts +14 -0
  88. package/src/managers/entities.ts +27 -66
  89. package/src/managers/epochs.ts +40 -0
  90. package/src/managers/index.ts +20 -1
  91. package/src/managers/locations.ts +25 -29
  92. package/src/managers/nft.test.ts +14 -0
  93. package/src/managers/nft.ts +70 -0
  94. package/src/managers/players.ts +25 -0
  95. package/src/managers/plot.ts +122 -0
  96. package/src/nft/atomicassets.abi.json +1342 -0
  97. package/src/nft/atomicassets.ts +237 -0
  98. package/src/nft/atomicdata.ts +130 -0
  99. package/src/nft/buildImmutableData.ts +385 -0
  100. package/src/nft/description.ts +225 -52
  101. package/src/nft/index.ts +3 -0
  102. package/src/planner/index.ts +242 -0
  103. package/src/planner/planner.test.ts +334 -0
  104. package/src/resolution/describe-module.ts +43 -25
  105. package/src/resolution/display-name.ts +38 -10
  106. package/src/resolution/resolve-item.test.ts +48 -0
  107. package/src/resolution/resolve-item.ts +112 -58
  108. package/src/scan/index.ts +244 -0
  109. package/src/scan/scan-wasm.base64.ts +2 -0
  110. package/src/scheduling/accessor.ts +65 -23
  111. package/src/scheduling/availability.test.ts +204 -0
  112. package/src/scheduling/availability.ts +211 -0
  113. package/src/scheduling/cancel.test.ts +542 -0
  114. package/src/scheduling/cancel.ts +254 -0
  115. package/src/scheduling/cluster-stock.test.ts +31 -0
  116. package/src/scheduling/cluster-stock.ts +15 -0
  117. package/src/scheduling/energy.ts +47 -0
  118. package/src/scheduling/idle-resolve.ts +45 -0
  119. package/src/scheduling/jobs.ts +71 -0
  120. package/src/scheduling/lane-core.ts +128 -0
  121. package/src/scheduling/lanes.test.ts +250 -0
  122. package/src/scheduling/lanes.ts +227 -0
  123. package/src/scheduling/projection.ts +257 -133
  124. package/src/scheduling/schedule.test.ts +119 -0
  125. package/src/scheduling/schedule.ts +257 -117
  126. package/src/scheduling/task-cargo.test.ts +44 -0
  127. package/src/scheduling/task-cargo.ts +46 -0
  128. package/src/scheduling/unwrap.test.ts +86 -0
  129. package/src/scheduling/unwrap.ts +190 -0
  130. package/src/shipload.ts +26 -1
  131. package/src/subscriptions/manager.cluster.test.ts +51 -0
  132. package/src/subscriptions/manager.ts +245 -172
  133. package/src/subscriptions/mappers.ts +5 -8
  134. package/src/subscriptions/types.ts +28 -3
  135. package/src/testing/catalog-hash.ts +19 -0
  136. package/src/testing/index.ts +2 -0
  137. package/src/testing/projection-parity.ts +167 -0
  138. package/src/travel/reach.ts +21 -0
  139. package/src/travel/route-planner.ts +288 -0
  140. package/src/travel/route-simulator.ts +170 -0
  141. package/src/travel/travel.ts +188 -122
  142. package/src/types/capabilities.ts +37 -8
  143. package/src/types/entity.ts +3 -3
  144. package/src/types/index.ts +0 -1
  145. package/src/types.ts +51 -16
  146. package/src/utils/cargo.test.ts +14 -0
  147. package/src/utils/cargo.ts +29 -0
  148. package/src/utils/display-name.ts +70 -0
  149. package/src/utils/system.ts +15 -16
  150. package/src/capabilities/loading.ts +0 -8
  151. package/src/entities/container.ts +0 -123
  152. package/src/entities/ship-deploy.ts +0 -295
  153. package/src/entities/ship.ts +0 -221
  154. package/src/entities/warehouse.ts +0 -127
  155. package/src/types/entity-traits.ts +0 -69
@@ -3,6 +3,9 @@ import type {
3
3
  BoundingBox,
4
4
  BoundsDeltaMessage,
5
5
  ClientMessage,
6
+ ClusterCellWire,
7
+ ClusterDeltaMessage,
8
+ EntityDeletedMessage,
6
9
  ServerMessage,
7
10
  SnapshotMessage,
8
11
  SubscribeEntityMessage,
@@ -13,12 +16,10 @@ import type {
13
16
  WireEntity,
14
17
  } from './types'
15
18
  import {mapEntity, parseWireEntity} from './mappers'
16
- import type {Ship} from '../entities/ship'
17
- import type {Warehouse} from '../entities/warehouse'
18
- import type {Container} from '../entities/container'
19
+ import type {Entity} from '../entities/entity'
19
20
 
20
- export type SubscriptionEntityType = 'ship' | 'warehouse' | 'container'
21
- export type EntityInstance = Ship | Warehouse | Container
21
+ export type SubscriptionEntityType = 'ship' | 'warehouse' | 'container' | 'nexus'
22
+ export type EntityInstance = Entity
22
23
 
23
24
  export interface SubscriptionsOptions {
24
25
  url: string
@@ -27,50 +28,79 @@ export interface SubscriptionsOptions {
27
28
  pongTimeoutMs?: number
28
29
  }
29
30
 
30
- export interface BoundsSubscriptionHandle {
31
- readonly subId: string
32
- unsubscribe(): void
33
- updateBounds(bounds: BoundingBox): void
34
- current: Map<number, EntityInstance>
31
+ export type ExactEntitySubscriptionFilter = {
32
+ id: string | number
33
+ owner?: never
34
+ bounds?: never
35
+ prioritizeOwner?: never
35
36
  }
36
37
 
37
- export interface OwnerSubscriptionHandle {
38
+ export type BroadEntitySubscriptionFilter = {
39
+ id?: undefined
40
+ owner?: string
41
+ bounds?: BoundingBox
42
+ prioritizeOwner?: string
43
+ }
44
+
45
+ export type EntitySubscriptionFilter = ExactEntitySubscriptionFilter | BroadEntitySubscriptionFilter
46
+
47
+ export interface EntitySubscriptionMeta {
48
+ seq?: number
49
+ truncated?: boolean
50
+ }
51
+
52
+ export interface EntitySubscriptionHandlers {
53
+ onSnapshot?: (entities: EntityInstance[], meta: EntitySubscriptionMeta) => void
54
+ onUpdate?: (entity: EntityInstance, meta: EntitySubscriptionMeta) => void
55
+ onBoundsDelta?: (
56
+ entered: EntityInstance[],
57
+ exited: number[],
58
+ meta: EntitySubscriptionMeta
59
+ ) => void
60
+ onDeleted?: (id: string, meta: EntitySubscriptionMeta) => void
61
+ onError?: (error: Error) => void
62
+ onCluster?: (payload: {
63
+ hubId: number
64
+ seq: number
65
+ cells: ClusterCellWire[] | null
66
+ erased: boolean
67
+ }) => void
68
+ }
69
+
70
+ export interface EntitiesSubscriptionHandle {
38
71
  readonly subId: string
72
+ readonly filter: EntitySubscriptionFilter
39
73
  unsubscribe(): void
40
74
  current: Map<number, EntityInstance>
41
75
  }
42
76
 
43
- export interface EntitySubscriptionHandle {
44
- readonly subId: string
45
- readonly entityType: SubscriptionEntityType
46
- readonly entityId: string
47
- unsubscribe(): void
48
- current: EntityInstance | null
77
+ export type BoundsSubscriptionHandle = EntitiesSubscriptionHandle & {
78
+ readonly filter: BroadEntitySubscriptionFilter & {bounds: BoundingBox}
79
+ updateBounds(bounds: BoundingBox): void
80
+ }
81
+ export type OwnerSubscriptionHandle = EntitiesSubscriptionHandle & {
82
+ readonly filter: BroadEntitySubscriptionFilter
83
+ }
84
+ export type EntitySubscriptionHandle = EntitiesSubscriptionHandle & {
85
+ readonly filter: ExactEntitySubscriptionFilter
86
+ }
87
+
88
+ type EntitiesSubscriptionEntry = {
89
+ filter: InternalEntitySubscriptionFilter
90
+ handlers: EntitySubscriptionHandlers
91
+ handle: EntitiesSubscriptionHandle
92
+ }
93
+
94
+ type InternalEntitySubscriptionFilter = {
95
+ id?: string | number
96
+ owner?: string
97
+ bounds?: BoundingBox
98
+ prioritizeOwner?: string
49
99
  }
50
100
 
51
101
  export class SubscriptionsManager {
52
102
  private readonly conn: WebSocketConnection
53
- private readonly entitySubs = new Map<
54
- string,
55
- {
56
- type: SubscriptionEntityType
57
- id: string
58
- onUpdate: (e: EntityInstance) => void
59
- handle: EntitySubscriptionHandle
60
- }
61
- >()
62
- private readonly boundsSubs = new Map<
63
- string,
64
- {
65
- bounds?: BoundingBox
66
- owner?: string
67
- prioritizeOwner?: string
68
- onSnapshot?: (entities: EntityInstance[]) => void
69
- onUpdate?: (entity: EntityInstance) => void
70
- onBoundsDelta?: (entered: EntityInstance[], exited: number[]) => void
71
- handle: BoundsSubscriptionHandle | OwnerSubscriptionHandle
72
- }
73
- >()
103
+ private readonly entitySubs = new Map<string, EntitiesSubscriptionEntry>()
74
104
  private subCounter = 0
75
105
  private hasConnected = false
76
106
 
@@ -99,113 +129,162 @@ export class SubscriptionsManager {
99
129
  this.conn.send(msg)
100
130
  }
101
131
 
102
- subscribeEntity(
103
- type: SubscriptionEntityType,
104
- id: string,
105
- onUpdate: (e: EntityInstance) => void
106
- ): EntitySubscriptionHandle {
107
- const subId = this.generateSubID('ent')
108
- const msg: SubscribeEntityMessage = {
109
- type: 'subscribe_entity',
110
- sub_id: subId,
111
- entity_type: type,
112
- entity_id: id,
113
- }
114
- const handle: EntitySubscriptionHandle = {
132
+ subscribeEntities(
133
+ filter: BroadEntitySubscriptionFilter & {bounds: BoundingBox},
134
+ handlers?: EntitySubscriptionHandlers
135
+ ): BoundsSubscriptionHandle
136
+ subscribeEntities(
137
+ filter: ExactEntitySubscriptionFilter,
138
+ handlers?: EntitySubscriptionHandlers
139
+ ): EntitySubscriptionHandle
140
+ subscribeEntities(
141
+ filter: BroadEntitySubscriptionFilter,
142
+ handlers?: EntitySubscriptionHandlers
143
+ ): EntitiesSubscriptionHandle
144
+ subscribeEntities(
145
+ filter: EntitySubscriptionFilter,
146
+ handlers?: EntitySubscriptionHandlers
147
+ ): EntitiesSubscriptionHandle
148
+ subscribeEntities(
149
+ filter: EntitySubscriptionFilter,
150
+ handlers: EntitySubscriptionHandlers = {}
151
+ ): EntitiesSubscriptionHandle {
152
+ const storedFilter = this.normalizeFilter(filter)
153
+ const subId = this.generateSubID(this.subscriptionPrefix(storedFilter))
154
+ const handle: EntitiesSubscriptionHandle = {
115
155
  subId,
116
- entityType: type,
117
- entityId: id,
118
- unsubscribe: () => this.unsubscribeEntity(subId),
119
- current: null,
156
+ get filter() {
157
+ return SubscriptionsManager.publicFilter(storedFilter)
158
+ },
159
+ unsubscribe: () => this.unsubscribeEntities(subId),
160
+ current: new Map(),
120
161
  }
121
- this.entitySubs.set(subId, {type, id, onUpdate, handle})
122
- this.sendMessage(msg)
162
+ if (storedFilter.id === undefined && storedFilter.bounds) {
163
+ ;(handle as BoundsSubscriptionHandle).updateBounds = (bounds) =>
164
+ this.updateBounds(subId, bounds)
165
+ }
166
+
167
+ this.entitySubs.set(subId, {filter: storedFilter, handlers, handle})
168
+ this.sendMessage(this.subscribeMessage(subId, storedFilter))
123
169
  return handle
124
170
  }
125
171
 
126
- private unsubscribeEntity(subId: string) {
127
- const entry = this.entitySubs.get(subId)
128
- if (!entry) return
129
- this.entitySubs.delete(subId)
130
- const msg: UnsubscribeEntityMessage = {type: 'unsubscribe_entity', sub_id: subId}
131
- this.sendMessage(msg)
172
+ subscribeEntity(
173
+ id: string | number,
174
+ handlers: EntitySubscriptionHandlers
175
+ ): EntitySubscriptionHandle {
176
+ return this.subscribeEntities({id}, handlers)
177
+ }
178
+
179
+ subscribeOwner(
180
+ owner: string,
181
+ handlers: EntitySubscriptionHandlers = {}
182
+ ): OwnerSubscriptionHandle {
183
+ return this.subscribeEntities({owner}, handlers) as OwnerSubscriptionHandle
132
184
  }
133
185
 
134
186
  subscribeBounds(
135
187
  bounds: BoundingBox,
136
- handlers: {
137
- onSnapshot?: (entities: EntityInstance[]) => void
138
- onUpdate?: (entity: EntityInstance) => void
139
- onBoundsDelta?: (entered: EntityInstance[], exited: number[]) => void
188
+ handlers: EntitySubscriptionHandlers & {
140
189
  owner?: string
141
190
  prioritizeOwner?: string
142
- }
191
+ } = {}
143
192
  ): BoundsSubscriptionHandle {
144
- const subId = this.generateSubID('bnd')
145
- const msg: SubscribeMessage = {
146
- type: 'subscribe',
147
- sub_id: subId,
148
- bounds,
149
- owner: handlers.owner,
150
- prioritize_owner: handlers.prioritizeOwner,
193
+ return this.subscribeEntities(
194
+ {bounds, owner: handlers.owner, prioritizeOwner: handlers.prioritizeOwner},
195
+ handlers
196
+ )
197
+ }
198
+
199
+ subscribeAllEntities(handlers: EntitySubscriptionHandlers = {}): EntitiesSubscriptionHandle {
200
+ return this.subscribeEntities({}, handlers)
201
+ }
202
+
203
+ private normalizeFilter(filter: EntitySubscriptionFilter): InternalEntitySubscriptionFilter {
204
+ const raw = filter as InternalEntitySubscriptionFilter
205
+ if (
206
+ raw.id !== undefined &&
207
+ (raw.owner !== undefined ||
208
+ raw.bounds !== undefined ||
209
+ raw.prioritizeOwner !== undefined)
210
+ ) {
211
+ throw new Error(
212
+ 'Exact entity subscription filters cannot include owner, bounds, or prioritizeOwner'
213
+ )
151
214
  }
152
- const handle: BoundsSubscriptionHandle = {
153
- subId,
154
- unsubscribe: () => this.unsubscribeBounds(subId),
155
- updateBounds: (b) => this.updateBounds(subId, b),
156
- current: new Map(),
215
+ if (raw.id !== undefined) {
216
+ return {id: raw.id}
217
+ }
218
+ return {
219
+ owner: raw.owner,
220
+ bounds: raw.bounds ? this.cloneBounds(raw.bounds) : undefined,
221
+ prioritizeOwner: raw.prioritizeOwner,
157
222
  }
158
- this.boundsSubs.set(subId, {
159
- bounds,
160
- owner: handlers.owner,
161
- prioritizeOwner: handlers.prioritizeOwner,
162
- onSnapshot: handlers.onSnapshot,
163
- onUpdate: handlers.onUpdate,
164
- onBoundsDelta: handlers.onBoundsDelta,
165
- handle,
166
- })
167
- this.sendMessage(msg)
168
- return handle
169
223
  }
170
224
 
171
- subscribeOwner(
172
- owner: string,
173
- handlers: {
174
- onSnapshot?: (entities: EntityInstance[]) => void
175
- onUpdate?: (entity: EntityInstance) => void
176
- } = {}
177
- ): OwnerSubscriptionHandle {
178
- const subId = this.generateSubID('own')
179
- const msg: SubscribeMessage = {
225
+ private static publicFilter(
226
+ filter: InternalEntitySubscriptionFilter
227
+ ): EntitySubscriptionFilter {
228
+ if (filter.id !== undefined) {
229
+ return Object.freeze({id: filter.id}) as ExactEntitySubscriptionFilter
230
+ }
231
+
232
+ return Object.freeze({
233
+ owner: filter.owner,
234
+ bounds: filter.bounds ? (Object.freeze({...filter.bounds}) as BoundingBox) : undefined,
235
+ prioritizeOwner: filter.prioritizeOwner,
236
+ }) as BroadEntitySubscriptionFilter
237
+ }
238
+
239
+ private cloneBounds(bounds: BoundingBox): BoundingBox {
240
+ return {...bounds}
241
+ }
242
+
243
+ private subscriptionPrefix(filter: InternalEntitySubscriptionFilter): string {
244
+ if (filter.id !== undefined) return 'ent'
245
+ if (filter.bounds) return 'bnd'
246
+ if (filter.owner) return 'own'
247
+ return 'all'
248
+ }
249
+
250
+ private subscribeMessage(
251
+ subId: string,
252
+ filter: InternalEntitySubscriptionFilter
253
+ ): SubscribeEntityMessage | SubscribeMessage {
254
+ if (filter.id !== undefined) {
255
+ return {
256
+ type: 'subscribe_entity',
257
+ sub_id: subId,
258
+ entity_id: String(filter.id),
259
+ }
260
+ }
261
+
262
+ return {
180
263
  type: 'subscribe',
181
264
  sub_id: subId,
182
- owner,
265
+ owner: filter.owner,
266
+ bounds: filter.bounds,
267
+ prioritize_owner: filter.prioritizeOwner,
183
268
  }
184
- const handle: OwnerSubscriptionHandle = {
185
- subId,
186
- unsubscribe: () => this.unsubscribeBounds(subId),
187
- current: new Map(),
188
- }
189
- this.boundsSubs.set(subId, {
190
- bounds: undefined,
191
- owner,
192
- prioritizeOwner: undefined,
193
- onSnapshot: handlers.onSnapshot,
194
- onUpdate: handlers.onUpdate,
195
- handle,
196
- })
197
- this.sendMessage(msg)
198
- return handle
199
269
  }
200
270
 
201
- private unsubscribeBounds(subId: string) {
202
- this.boundsSubs.delete(subId)
271
+ private unsubscribeEntities(subId: string) {
272
+ const entry = this.entitySubs.get(subId)
273
+ if (!entry) return
274
+ this.entitySubs.delete(subId)
275
+ if (entry.filter.id !== undefined) {
276
+ const msg: UnsubscribeEntityMessage = {type: 'unsubscribe_entity', sub_id: subId}
277
+ this.sendMessage(msg)
278
+ return
279
+ }
203
280
  this.sendMessage({type: 'unsubscribe', sub_id: subId})
204
281
  }
205
282
 
206
283
  private updateBounds(subId: string, bounds: BoundingBox) {
207
- const entry = this.boundsSubs.get(subId)
208
- if (entry) entry.bounds = bounds
284
+ const entry = this.entitySubs.get(subId)
285
+ if (!entry) return
286
+ if (entry.filter.id !== undefined || !entry.filter.bounds) return
287
+ entry.filter.bounds = this.cloneBounds(bounds)
209
288
  const msg: UpdateBoundsMessage = {type: 'update_bounds', sub_id: subId, bounds}
210
289
  this.sendMessage(msg)
211
290
  }
@@ -217,23 +296,7 @@ export class SubscriptionsManager {
217
296
  return
218
297
  }
219
298
  for (const [subId, entry] of this.entitySubs) {
220
- const msg: SubscribeEntityMessage = {
221
- type: 'subscribe_entity',
222
- sub_id: subId,
223
- entity_type: entry.type,
224
- entity_id: entry.id,
225
- }
226
- this.sendMessage(msg)
227
- }
228
- for (const [subId, entry] of this.boundsSubs) {
229
- const msg: SubscribeMessage = {
230
- type: 'subscribe',
231
- sub_id: subId,
232
- bounds: entry.bounds,
233
- owner: entry.owner,
234
- prioritize_owner: entry.prioritizeOwner,
235
- }
236
- this.sendMessage(msg)
299
+ this.sendMessage(this.subscribeMessage(subId, entry.filter))
237
300
  }
238
301
  }
239
302
 
@@ -248,6 +311,12 @@ export class SubscriptionsManager {
248
311
  case 'bounds_delta':
249
312
  this.handleBoundsDelta(msg)
250
313
  break
314
+ case 'entity_deleted':
315
+ this.handleEntityDeleted(msg)
316
+ break
317
+ case 'cluster':
318
+ this.handleCluster(msg)
319
+ break
251
320
  case 'error':
252
321
  this.handleError(msg)
253
322
  break
@@ -260,60 +329,64 @@ export class SubscriptionsManager {
260
329
  }
261
330
 
262
331
  private handleSnapshot(msg: SnapshotMessage) {
263
- const entSub = this.entitySubs.get(msg.sub_id)
264
- if (entSub) {
265
- if (msg.entities.length > 0) {
266
- const ent = this.parseEntity(msg.entities[0])
267
- entSub.handle.current = ent
268
- entSub.onUpdate(ent)
269
- }
270
- return
271
- }
272
- const boundsSub = this.boundsSubs.get(msg.sub_id)
273
- if (boundsSub) {
274
- const ents = msg.entities.map((e) => this.parseEntity(e))
275
- boundsSub.handle.current.clear()
276
- for (const e of ents) boundsSub.handle.current.set(Number(e.id), e)
277
- boundsSub.onSnapshot?.(ents)
332
+ const sub = this.entitySubs.get(msg.sub_id)
333
+ if (!sub) return
334
+ const meta = {seq: msg.seq, truncated: msg.truncated === true}
335
+ const ents = msg.entities.map((e) => this.parseEntity(e))
336
+ sub.handle.current.clear()
337
+ for (const e of ents) sub.handle.current.set(Number(e.id), e)
338
+ sub.handlers.onSnapshot?.(ents, meta)
339
+ if (sub.filter.id !== undefined && ents[0]) {
340
+ sub.handlers.onUpdate?.(ents[0], {seq: msg.seq})
278
341
  }
279
342
  }
280
343
 
281
344
  private handleUpdate(msg: UpdateMessage) {
282
345
  const ent = this.parseEntity(msg.entity)
283
346
  for (const subId of msg.sub_ids) {
284
- const entSub = this.entitySubs.get(subId)
285
- if (entSub) {
286
- entSub.handle.current = ent
287
- entSub.onUpdate(ent)
288
- continue
289
- }
290
- const boundsSub = this.boundsSubs.get(subId)
291
- if (boundsSub) {
292
- boundsSub.handle.current.set(msg.entity_id, ent)
293
- boundsSub.onUpdate?.(ent)
294
- }
347
+ const sub = this.entitySubs.get(subId)
348
+ if (!sub) continue
349
+ sub.handle.current.set(msg.entity_id, ent)
350
+ sub.handlers.onUpdate?.(ent, {seq: msg.seq})
295
351
  }
296
352
  }
297
353
 
298
354
  private handleBoundsDelta(msg: BoundsDeltaMessage) {
299
- const sub = this.boundsSubs.get(msg.sub_id)
355
+ const sub = this.entitySubs.get(msg.sub_id)
300
356
  if (!sub) return
357
+ const meta = {seq: msg.seq, truncated: msg.truncated === true}
301
358
  const entered = msg.entered.map((e) => this.parseEntity(e))
302
359
  for (const e of entered) sub.handle.current.set(Number(e.id), e)
303
360
  for (const id of msg.exited) sub.handle.current.delete(id)
304
- sub.onBoundsDelta?.(entered, msg.exited)
361
+ sub.handlers.onBoundsDelta?.(entered, msg.exited, meta)
305
362
  }
306
363
 
307
364
  private handleError(msg: {sub_id?: string; error: string}) {
308
365
  if (!msg.sub_id) return
309
- const entSub = this.entitySubs.get(msg.sub_id)
310
- if (entSub) {
366
+ const sub = this.entitySubs.get(msg.sub_id)
367
+ if (!sub) return
368
+ this.entitySubs.delete(msg.sub_id)
369
+ sub.handlers.onError?.(new Error(msg.error))
370
+ }
371
+
372
+ private handleEntityDeleted(msg: EntityDeletedMessage) {
373
+ const sub = this.entitySubs.get(msg.sub_id)
374
+ if (!sub) return
375
+ sub.handle.current.delete(msg.entity_id)
376
+ if (sub.filter.id !== undefined) {
311
377
  this.entitySubs.delete(msg.sub_id)
312
- return
313
- }
314
- const boundsSub = this.boundsSubs.get(msg.sub_id)
315
- if (boundsSub) {
316
- this.boundsSubs.delete(msg.sub_id)
317
378
  }
379
+ sub.handlers.onDeleted?.(String(msg.entity_id), {seq: msg.seq})
380
+ }
381
+
382
+ private handleCluster(msg: ClusterDeltaMessage) {
383
+ const sub = this.entitySubs.get(msg.sub_id)
384
+ if (!sub) return
385
+ sub.handlers.onCluster?.({
386
+ hubId: msg.hub_id,
387
+ seq: msg.seq,
388
+ cells: msg.erased ? null : (msg.cells ?? []),
389
+ erased: msg.erased === true,
390
+ })
318
391
  }
319
392
  }
@@ -1,14 +1,9 @@
1
1
  import {ServerContract} from '../contracts'
2
- import {Ship} from '../entities/ship'
3
- import {Warehouse} from '../entities/warehouse'
4
- import {Container} from '../entities/container'
2
+ import {Entity} from '../entities/entity'
5
3
  import type {WireEntity} from './types'
6
4
 
7
- export function mapEntity(ei: ServerContract.Types.entity_info): Ship | Warehouse | Container {
8
- if (ei.type.equals('ship')) return new Ship(ei)
9
- if (ei.type.equals('warehouse')) return new Warehouse(ei)
10
- if (ei.type.equals('container')) return new Container(ei)
11
- throw new Error(`mapEntity: unknown entity type ${ei.type.toString()}`)
5
+ export function mapEntity(ei: ServerContract.Types.entity_info): Entity {
6
+ return new Entity(ei)
12
7
  }
13
8
 
14
9
  export function parseWireEntity(raw: WireEntity): ServerContract.Types.entity_info {
@@ -24,5 +19,7 @@ export function parseWireEntity(raw: WireEntity): ServerContract.Types.entity_in
24
19
  }
25
20
  delete shaped.name
26
21
 
22
+ if (shaped.holds === undefined) shaped.holds = []
23
+
27
24
  return ServerContract.Types.entity_info.from(shaped)
28
25
  }
@@ -39,7 +39,6 @@ export type UnsubscribeMessage = {
39
39
  export type SubscribeEntityMessage = {
40
40
  type: 'subscribe_entity'
41
41
  sub_id: string
42
- entity_type: 'ship' | 'warehouse' | 'container'
43
42
  entity_id: string
44
43
  }
45
44
 
@@ -79,11 +78,12 @@ export type AckMessage = {
79
78
  }
80
79
 
81
80
  export type WireEntity = Record<string, unknown> & {
82
- type: number
83
- type_name: 'ship' | 'warehouse' | 'container'
81
+ type: number | string
82
+ type_name?: string
84
83
  id: string | number
85
84
  owner: string
86
85
  coordinates: WireCoordinates
86
+ item_id: number
87
87
  }
88
88
 
89
89
  export type SnapshotMessage = {
@@ -111,6 +111,13 @@ export type BoundsDeltaMessage = {
111
111
  truncated?: boolean
112
112
  }
113
113
 
114
+ export type EntityDeletedMessage = {
115
+ type: 'entity_deleted'
116
+ sub_id: string
117
+ entity_id: number
118
+ seq: number
119
+ }
120
+
114
121
  export type EventMessage = {
115
122
  type: 'event'
116
123
  sub_id: string
@@ -132,12 +139,30 @@ export type ErrorMessage = {
132
139
  sub_id?: string
133
140
  }
134
141
 
142
+ export interface ClusterCellWire {
143
+ gx: number
144
+ gy: number
145
+ entity: number
146
+ type?: string
147
+ }
148
+
149
+ export type ClusterDeltaMessage = {
150
+ type: 'cluster'
151
+ sub_id: string
152
+ hub_id: number
153
+ seq: number
154
+ cells?: ClusterCellWire[]
155
+ erased?: boolean
156
+ }
157
+
135
158
  export type ServerMessage =
136
159
  | AckMessage
137
160
  | SnapshotMessage
138
161
  | UpdateMessage
139
162
  | BoundsDeltaMessage
163
+ | EntityDeletedMessage
140
164
  | EventMessage
141
165
  | EventCatchupCompleteMessage
142
166
  | PongMessage
143
167
  | ErrorMessage
168
+ | ClusterDeltaMessage
@@ -0,0 +1,19 @@
1
+ import {createHash} from 'node:crypto'
2
+ import {readFileSync} from 'node:fs'
3
+
4
+ export const CATALOG_FILES_REL = [
5
+ 'items.json',
6
+ 'recipes.json',
7
+ 'entities.json',
8
+ 'kind-registry.json',
9
+ 'item-ids.ts',
10
+ ] as const
11
+
12
+ export function computeCatalogHash(filePaths: ReadonlyArray<string>): string {
13
+ const hash = createHash('sha256')
14
+ for (const p of filePaths) {
15
+ hash.update(readFileSync(p))
16
+ hash.update('\0')
17
+ }
18
+ return hash.digest('hex')
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './catalog-hash'
2
+ export * from './projection-parity'