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

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 (156) 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 +3601 -1364
  7. package/lib/shipload.js +15671 -5261
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +15392 -5209
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1173 -0
  12. package/lib/testing.js +4971 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4965 -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 +27 -30
  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 +7 -8
  42. package/src/data/colors.ts +14 -48
  43. package/src/data/entities.json +380 -15
  44. package/src/data/item-ids.ts +51 -13
  45. package/src/data/items.json +318 -77
  46. package/src/data/kind-registry.json +195 -0
  47. package/src/data/kind-registry.ts +182 -0
  48. package/src/data/metadata.ts +291 -51
  49. package/src/data/recipes-runtime.ts +6 -23
  50. package/src/data/recipes.json +2260 -173
  51. package/src/data/tiers.ts +9 -6
  52. package/src/derivation/build-methods.test.ts +13 -0
  53. package/src/derivation/build-methods.ts +48 -0
  54. package/src/derivation/capabilities.test.ts +348 -0
  55. package/src/derivation/capabilities.ts +693 -0
  56. package/src/derivation/capability-mappings.ts +60 -16
  57. package/src/derivation/crafting.test.ts +17 -0
  58. package/src/derivation/crafting.ts +57 -34
  59. package/src/derivation/index.ts +27 -2
  60. package/src/derivation/recipe-usage.test.ts +90 -0
  61. package/src/derivation/recipe-usage.ts +141 -0
  62. package/src/derivation/reserve-regen.ts +34 -0
  63. package/src/derivation/resources.ts +9 -1
  64. package/src/derivation/rollups.test.ts +55 -0
  65. package/src/derivation/rollups.ts +72 -0
  66. package/src/derivation/stars.test.ts +67 -0
  67. package/src/derivation/stars.ts +25 -0
  68. package/src/derivation/stat-scaling.ts +12 -0
  69. package/src/derivation/stats.ts +6 -6
  70. package/src/derivation/stratum.ts +26 -22
  71. package/src/derivation/tiers.ts +40 -7
  72. package/src/derivation/upgrades.ts +14 -0
  73. package/src/derivation/wormhole.ts +154 -0
  74. package/src/entities/entity.ts +102 -0
  75. package/src/entities/gamestate.ts +3 -28
  76. package/src/entities/makers.ts +141 -135
  77. package/src/entities/slot-multiplier.ts +43 -0
  78. package/src/errors.ts +12 -16
  79. package/src/format.ts +26 -4
  80. package/src/index-module.ts +333 -35
  81. package/src/managers/actions.ts +668 -103
  82. package/src/managers/base.ts +6 -2
  83. package/src/managers/cluster.test.ts +39 -0
  84. package/src/managers/cluster.ts +53 -0
  85. package/src/managers/construction-types.ts +80 -0
  86. package/src/managers/construction.ts +443 -0
  87. package/src/managers/context.ts +29 -1
  88. package/src/managers/coordinates.ts +14 -0
  89. package/src/managers/entities.ts +27 -66
  90. package/src/managers/epochs.ts +40 -0
  91. package/src/managers/index.ts +20 -1
  92. package/src/managers/locations.ts +25 -29
  93. package/src/managers/nft.test.ts +14 -0
  94. package/src/managers/nft.ts +70 -0
  95. package/src/managers/players.ts +25 -0
  96. package/src/managers/plot.ts +122 -0
  97. package/src/nft/atomicassets.abi.json +1342 -0
  98. package/src/nft/atomicassets.ts +237 -0
  99. package/src/nft/atomicdata.ts +130 -0
  100. package/src/nft/buildImmutableData.ts +388 -0
  101. package/src/nft/description.ts +215 -99
  102. package/src/nft/index.ts +3 -0
  103. package/src/planner/index.ts +242 -0
  104. package/src/planner/planner.test.ts +334 -0
  105. package/src/resolution/describe-module.ts +43 -25
  106. package/src/resolution/display-name.ts +38 -10
  107. package/src/resolution/resolve-item.test.ts +48 -0
  108. package/src/resolution/resolve-item.ts +112 -58
  109. package/src/scan/index.ts +244 -0
  110. package/src/scan/scan-wasm.base64.ts +2 -0
  111. package/src/scheduling/accessor.ts +65 -23
  112. package/src/scheduling/availability.test.ts +204 -0
  113. package/src/scheduling/availability.ts +211 -0
  114. package/src/scheduling/cancel.test.ts +542 -0
  115. package/src/scheduling/cancel.ts +254 -0
  116. package/src/scheduling/cluster-stock.test.ts +31 -0
  117. package/src/scheduling/cluster-stock.ts +15 -0
  118. package/src/scheduling/energy.ts +47 -0
  119. package/src/scheduling/idle-resolve.ts +45 -0
  120. package/src/scheduling/jobs.ts +71 -0
  121. package/src/scheduling/lane-core.ts +128 -0
  122. package/src/scheduling/lanes.test.ts +250 -0
  123. package/src/scheduling/lanes.ts +227 -0
  124. package/src/scheduling/projection.ts +257 -133
  125. package/src/scheduling/schedule.test.ts +119 -0
  126. package/src/scheduling/schedule.ts +257 -117
  127. package/src/scheduling/task-cargo.test.ts +44 -0
  128. package/src/scheduling/task-cargo.ts +46 -0
  129. package/src/scheduling/unwrap.test.ts +86 -0
  130. package/src/scheduling/unwrap.ts +190 -0
  131. package/src/shipload.ts +26 -1
  132. package/src/subscriptions/manager.cluster.test.ts +51 -0
  133. package/src/subscriptions/manager.ts +245 -172
  134. package/src/subscriptions/mappers.ts +5 -8
  135. package/src/subscriptions/types.ts +28 -3
  136. package/src/testing/catalog-hash.ts +19 -0
  137. package/src/testing/index.ts +2 -0
  138. package/src/testing/projection-parity.ts +167 -0
  139. package/src/travel/reach.ts +21 -0
  140. package/src/travel/route-planner.ts +288 -0
  141. package/src/travel/route-simulator.ts +170 -0
  142. package/src/travel/travel.ts +188 -122
  143. package/src/types/capabilities.ts +37 -8
  144. package/src/types/entity.ts +3 -3
  145. package/src/types/index.ts +0 -1
  146. package/src/types.ts +51 -16
  147. package/src/utils/cargo.test.ts +14 -0
  148. package/src/utils/cargo.ts +29 -0
  149. package/src/utils/display-name.ts +70 -0
  150. package/src/utils/system.ts +15 -16
  151. package/src/capabilities/loading.ts +0 -8
  152. package/src/entities/container.ts +0 -123
  153. package/src/entities/ship-deploy.ts +0 -295
  154. package/src/entities/ship.ts +0 -221
  155. package/src/entities/warehouse.ts +0 -127
  156. package/src/types/entity-traits.ts +0 -69
@@ -0,0 +1,86 @@
1
+ import {describe, expect, test} from 'bun:test'
2
+ import {
3
+ derivedLoaders,
4
+ unwrapTransitDuration,
5
+ unwrapLoadDuration,
6
+ estimateUnwrapDuration,
7
+ incomingHoldMass,
8
+ projectedPeakCargomass,
9
+ } from './unwrap'
10
+
11
+ describe('unwrap duration mirror', () => {
12
+ test('derivedLoaders aggregates lanes like derived_loaders()', () => {
13
+ expect(derivedLoaders([])).toBeNull()
14
+ expect(
15
+ derivedLoaders([
16
+ {mass: 1000, thrust: 10},
17
+ {mass: 1400, thrust: 20},
18
+ ])
19
+ ).toEqual({mass: 1200, thrust: 30, quantity: 2}) // floor(2400/2)=1200, sum thrust, count
20
+ })
21
+
22
+ test('transit floors distance then flight time', () => {
23
+ // distance = floor(sqrt(3^2+4^2)*10000)=50000; accel=400/mass*10000; flight=floor(2*sqrt(d/accel))
24
+ const mass = 1000
25
+ const accel = (400 / mass) * 10000
26
+ const expected = Math.floor(2 * Math.sqrt(50000 / accel))
27
+ expect(unwrapTransitDuration(mass, {x: 0, y: 0}, {x: 3, y: 4})).toBe(expected)
28
+ })
29
+
30
+ test('load uses altitude z, adds loader mass, divides by quantity', () => {
31
+ const loaders = {mass: 1200, thrust: 30, quantity: 2}
32
+ const itemMass = 800
33
+ const accel = (30 / (itemMass + 1200)) * 10000
34
+ const flight = Math.floor(2 * Math.sqrt(3000 / accel))
35
+ expect(unwrapLoadDuration(loaders, itemMass, 3000)).toBe(Math.floor(flight / 2))
36
+ })
37
+
38
+ test('zero item mass and no loaders are safe', () => {
39
+ expect(unwrapTransitDuration(0, {x: 0, y: 0}, {x: 9, y: 9})).toBe(0)
40
+ expect(unwrapLoadDuration(null, 500, 3000)).toBe(0)
41
+ })
42
+
43
+ test('load altitude is floored so ground-level (z=0) destinations are not instant', () => {
44
+ const loaders = {mass: 500, thrust: 200, quantity: 1}
45
+ const atZero = unwrapLoadDuration(loaders, 1000, 0)
46
+ const at800 = unwrapLoadDuration(loaders, 1000, 800)
47
+ expect(atZero).toBeGreaterThan(0)
48
+ expect(atZero).toBe(at800)
49
+ })
50
+
51
+ test('estimateUnwrapDuration uses worst-loader baseline when dest has no loaders', () => {
52
+ // IRON (101); same coords so transit is 0 and the estimate is baseline load alone
53
+ const item = {itemId: 101, quantity: 10, modules: [], originX: 0, originY: 0}
54
+ const bareDest = {loader_lanes: [], coordinates: {x: 0, y: 0, z: 800}}
55
+
56
+ const bare = estimateUnwrapDuration(bareDest, item)
57
+ expect(bare).toBeGreaterThan(0)
58
+
59
+ const loadedDest = {
60
+ loader_lanes: [{mass: 500, thrust: 200}],
61
+ coordinates: {x: 0, y: 0, z: 800},
62
+ }
63
+ const loaded = estimateUnwrapDuration(loadedDest, item)
64
+ expect(loaded).toBeLessThanOrEqual(bare)
65
+
66
+ expect(unwrapLoadDuration(null, 500, 3000)).toBe(0)
67
+ })
68
+ })
69
+
70
+ test('incomingHoldMass sums incoming-kind hold mass', () => {
71
+ expect(incomingHoldMass([])).toBe(0)
72
+ // PUSH(2) + FLIGHT(5) count; BUILD(4) does not
73
+ expect(
74
+ incomingHoldMass([
75
+ {kind: 2, incoming_mass: 100},
76
+ {kind: 4, incoming_mass: 999},
77
+ {kind: 5, incoming_mass: 50},
78
+ ])
79
+ ).toBe(150)
80
+ })
81
+
82
+ test('projectedPeakCargomass tracks the running peak from cargomass', () => {
83
+ const entity = {cargomass: 1000, lanes: [], cargo: [], schedule: undefined} as never
84
+ // No pending tasks: peak = base + candidate add.
85
+ expect(projectedPeakCargomass(entity, new Date(0), 500)).toBe(1500)
86
+ })
@@ -0,0 +1,190 @@
1
+ import type {UInt16Type, UInt32Type} from '@wharfkit/antelope'
2
+ import {calcCargoItemMass} from '../capabilities/storage'
3
+ import type {ServerContract} from '../contracts'
4
+ import {PRECISION} from '../types'
5
+ import * as sched from './schedule'
6
+ import {taskCargoEffect} from './availability'
7
+ import {candidateLaneCompletesAt} from './lanes'
8
+
9
+ const NFT_TRANSIT_THRUST = 400
10
+ const BASELINE_LOADER: DerivedLoaders = {mass: 2000, thrust: 1, quantity: 1}
11
+ // ground-level entities (warehouses, z=0) still incur a base orbital climb of load effort
12
+ const MIN_LOAD_Z = 800
13
+
14
+ export interface DerivedLoaders {
15
+ mass: number
16
+ thrust: number
17
+ quantity: number
18
+ }
19
+
20
+ export function derivedLoaders(
21
+ lanes: {mass: UInt32Type | number; thrust: UInt16Type | number}[] | undefined
22
+ ): DerivedLoaders | null {
23
+ if (!lanes || lanes.length === 0) return null
24
+ let totalMass = 0
25
+ let totalThrust = 0
26
+ for (const l of lanes) {
27
+ totalMass += Number(l.mass)
28
+ totalThrust += Number(l.thrust)
29
+ }
30
+ const count = lanes.length
31
+ return {
32
+ mass: Math.floor(totalMass / count),
33
+ thrust: Math.min(totalThrust, 65_535),
34
+ quantity: count,
35
+ }
36
+ }
37
+
38
+ function acceleration(thrust: number, mass: number): number {
39
+ if (mass <= 0) return 0
40
+ return (thrust / mass) * PRECISION
41
+ }
42
+
43
+ function flightTime(distance: number, accel: number): number {
44
+ if (accel <= 0 || distance <= 0) return 0
45
+ return Math.floor(2 * Math.sqrt(distance / accel))
46
+ }
47
+
48
+ function distance2d(ax: number, ay: number, bx: number, by: number): number {
49
+ const dx = ax - bx
50
+ const dy = ay - by
51
+ return Math.floor(Math.sqrt(dx * dx + dy * dy) * PRECISION)
52
+ }
53
+
54
+ export function unwrapTransitDuration(
55
+ itemMass: number,
56
+ origin: {x: number; y: number},
57
+ dest: {x: number; y: number}
58
+ ): number {
59
+ if (itemMass <= 0) return 0
60
+ return flightTime(
61
+ distance2d(origin.x, origin.y, dest.x, dest.y),
62
+ acceleration(NFT_TRANSIT_THRUST, itemMass)
63
+ )
64
+ }
65
+
66
+ export function unwrapLoadDuration(
67
+ loaders: DerivedLoaders | null,
68
+ itemMass: number,
69
+ destZ: number
70
+ ): number {
71
+ if (!loaders || itemMass <= 0) return 0
72
+ const total = itemMass + loaders.mass
73
+ const flight = flightTime(Math.max(destZ, MIN_LOAD_Z), acceleration(loaders.thrust, total))
74
+ return Math.floor(flight / loaders.quantity)
75
+ }
76
+
77
+ export interface UnwrapItem {
78
+ itemId: number
79
+ quantity: number
80
+ modules: ServerContract.Types.module_entry[]
81
+ originX: number
82
+ originY: number
83
+ }
84
+
85
+ export interface UnwrapDestination {
86
+ loader_lanes?: {mass: UInt32Type | number; thrust: UInt16Type | number}[]
87
+ coordinates: {x: UInt32Type | number; y: UInt32Type | number; z?: UInt32Type | number}
88
+ }
89
+
90
+ export function estimateUnwrapDuration(dest: UnwrapDestination, item: UnwrapItem): number {
91
+ const itemMass = Number(
92
+ calcCargoItemMass({
93
+ item_id: item.itemId as never,
94
+ quantity: item.quantity as never,
95
+ modules: item.modules,
96
+ })
97
+ )
98
+ const loaders = derivedLoaders(dest.loader_lanes) ?? BASELINE_LOADER
99
+ const dz = Number(dest.coordinates.z ?? 0)
100
+ const load = unwrapLoadDuration(loaders, itemMass, dz)
101
+ const transit = unwrapTransitDuration(
102
+ itemMass,
103
+ {x: item.originX, y: item.originY},
104
+ {
105
+ x: Number(dest.coordinates.x),
106
+ y: Number(dest.coordinates.y),
107
+ }
108
+ )
109
+ return load + transit
110
+ }
111
+
112
+ // Hold kinds that count as incoming (mirror is_incoming_hold_kind in holds.hpp).
113
+ const INCOMING_HOLD_KINDS = new Set<number>([2, 3, 5])
114
+
115
+ export function incomingHoldMass(
116
+ holds:
117
+ | {kind: number | {toNumber(): number}; incoming_mass: number | {toNumber(): number}}[]
118
+ | undefined
119
+ ): number {
120
+ if (!holds) return 0
121
+ let total = 0
122
+ for (const h of holds) {
123
+ if (INCOMING_HOLD_KINDS.has(Number(h.kind))) total += Number(h.incoming_mass)
124
+ }
125
+ return total
126
+ }
127
+
128
+ type CargoItem = ServerContract.Types.cargo_item
129
+
130
+ function cargoListMass(items: CargoItem[]): number {
131
+ let m = 0
132
+ for (const it of items) m += Number(calcCargoItemMass(it))
133
+ return m
134
+ }
135
+
136
+ export function projectedPeakCargomass(
137
+ entity: sched.ScheduleData & {cargomass: number | {toNumber(): number}},
138
+ at: Date,
139
+ addMass: number,
140
+ removeMass = 0
141
+ ): number {
142
+ const events: {t: number; delta: number}[] = []
143
+ for (const ordered of sched.orderedTasks(entity)) {
144
+ const eff = taskCargoEffect(ordered.task)
145
+ const delta = cargoListMass(eff.added) - cargoListMass(eff.removed)
146
+ events.push({t: ordered.completesAt.getTime(), delta})
147
+ }
148
+ events.push({t: at.getTime(), delta: addMass - removeMass})
149
+ events.sort((a, b) => (a.t !== b.t ? a.t - b.t : b.delta - a.delta))
150
+ let running = Number(entity.cargomass)
151
+ let peak = running
152
+ for (const e of events) {
153
+ running += e.delta
154
+ if (running < 0) running = 0
155
+ if (running > peak) peak = running
156
+ }
157
+ return Math.min(peak, 0xffff_ffff)
158
+ }
159
+
160
+ export function receiveFits(
161
+ dest: UnwrapDestination &
162
+ sched.ScheduleData & {
163
+ cargomass: number | {toNumber(): number}
164
+ capacity?: number | {toNumber(): number}
165
+ holds?: {
166
+ kind: number | {toNumber(): number}
167
+ incoming_mass: number | {toNumber(): number}
168
+ }[]
169
+ },
170
+ item: UnwrapItem,
171
+ now: Date
172
+ ): boolean {
173
+ const capacity = Number(dest.capacity ?? 0)
174
+ if (capacity <= 0) return false
175
+ const itemMass = Number(
176
+ calcCargoItemMass({
177
+ item_id: item.itemId as never,
178
+ quantity: item.quantity as never,
179
+ modules: item.modules,
180
+ })
181
+ )
182
+ const duration = estimateUnwrapDuration(dest, item)
183
+ const candidateCompletes = candidateLaneCompletesAt(dest, sched.LANE_MOBILITY, duration, now)
184
+ const peak = projectedPeakCargomass(
185
+ dest,
186
+ candidateCompletes,
187
+ itemMass + incomingHoldMass(dest.holds)
188
+ )
189
+ return peak <= capacity
190
+ }
package/src/shipload.ts CHANGED
@@ -7,8 +7,11 @@ import {GameContext} from './managers/context'
7
7
  import type {EntitiesManager} from './managers/entities'
8
8
  import type {PlayersManager} from './managers/players'
9
9
  import type {LocationsManager} from './managers/locations'
10
+ import type {CoordinatesManager} from './managers/coordinates'
10
11
  import type {EpochsManager} from './managers/epochs'
11
12
  import type {ActionsManager} from './managers/actions'
13
+ import type {ClusterManager} from './managers/cluster'
14
+ import type {NftManager} from './managers/nft'
12
15
  import type {SubscriptionsManager} from './subscriptions/manager'
13
16
  import type {GameState} from './entities/gamestate'
14
17
 
@@ -17,6 +20,7 @@ interface ShiploadOptions {
17
20
  serverContractName?: string
18
21
  client?: APIClient
19
22
  subscriptionsUrl?: string
23
+ atomicAssetsAccount?: string
20
24
  }
21
25
 
22
26
  interface ShiploadConstructorOptions extends ShiploadOptions {
@@ -39,7 +43,12 @@ export class Shipload {
39
43
  ? serverContract
40
44
  : new ServerContract.Contract({client: apiClient})
41
45
 
42
- this._context = new GameContext(apiClient, server, platform)
46
+ this._context = new GameContext(
47
+ apiClient,
48
+ server,
49
+ platform,
50
+ constructorOptions?.atomicAssetsAccount ?? 'atomicassets'
51
+ )
43
52
 
44
53
  if (constructorOptions?.subscriptionsUrl) {
45
54
  this._context.setSubscriptionsUrl(constructorOptions.subscriptionsUrl)
@@ -79,6 +88,10 @@ export class Shipload {
79
88
  return this._context.client
80
89
  }
81
90
 
91
+ get atomicAssetsAccount(): string {
92
+ return this._context.atomicAssetsAccount
93
+ }
94
+
82
95
  get server(): Contract {
83
96
  return this._context.server
84
97
  }
@@ -99,6 +112,10 @@ export class Shipload {
99
112
  return this._context.locations
100
113
  }
101
114
 
115
+ get coordinates(): CoordinatesManager {
116
+ return this._context.coordinates
117
+ }
118
+
102
119
  get epochs(): EpochsManager {
103
120
  return this._context.epochs
104
121
  }
@@ -107,6 +124,14 @@ export class Shipload {
107
124
  return this._context.actions
108
125
  }
109
126
 
127
+ get clusters(): ClusterManager {
128
+ return this._context.clusters
129
+ }
130
+
131
+ get nft(): NftManager {
132
+ return this._context.nft
133
+ }
134
+
110
135
  get subscriptions(): SubscriptionsManager {
111
136
  return this._context.subscriptions
112
137
  }
@@ -0,0 +1,51 @@
1
+ import {afterEach, describe, expect, test} from 'bun:test'
2
+ import {SubscriptionsManager} from './manager'
3
+ import type {ServerMessage} from './types'
4
+
5
+ function createManager(): SubscriptionsManager {
6
+ return new SubscriptionsManager({url: 'ws://localhost:0'})
7
+ }
8
+
9
+ function deliver(mgr: SubscriptionsManager, msg: ServerMessage) {
10
+ ;(mgr as unknown as {onMessage(m: ServerMessage): void}).onMessage(msg)
11
+ }
12
+
13
+ describe('cluster delta routing', () => {
14
+ let mgr: SubscriptionsManager | undefined
15
+
16
+ afterEach(() => {
17
+ mgr?.close()
18
+ mgr = undefined
19
+ })
20
+
21
+ test('routes a cluster frame to the matching entity subscription onCluster', () => {
22
+ mgr = createManager()
23
+ const received: unknown[] = []
24
+ const handle = mgr.subscribeEntity(42, {
25
+ onCluster: (p) => received.push(p),
26
+ })
27
+ deliver(mgr, {
28
+ type: 'cluster',
29
+ sub_id: handle.subId,
30
+ hub_id: 42,
31
+ seq: 7,
32
+ cells: [{gx: 0, gy: 1, entity: 99, type: 'warehouse'}],
33
+ })
34
+ expect(received).toEqual([
35
+ {
36
+ hubId: 42,
37
+ seq: 7,
38
+ cells: [{gx: 0, gy: 1, entity: 99, type: 'warehouse'}],
39
+ erased: false,
40
+ },
41
+ ])
42
+ })
43
+
44
+ test('an erased cluster frame yields cells:null, erased:true', () => {
45
+ mgr = createManager()
46
+ const received: unknown[] = []
47
+ const handle = mgr.subscribeEntity(42, {onCluster: (p) => received.push(p)})
48
+ deliver(mgr, {type: 'cluster', sub_id: handle.subId, hub_id: 42, seq: 8, erased: true})
49
+ expect(received).toEqual([{hubId: 42, seq: 8, cells: null, erased: true}])
50
+ })
51
+ })