@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,55 @@
1
+ import {describe, expect, test} from 'bun:test'
2
+ import {ServerContract} from '../contracts'
3
+ import {rollupGatherer, rollupCrafter, rollupLoaders} from './rollups'
4
+
5
+ const gLane = (slot: number, y: number, d: number, depth: number) =>
6
+ ServerContract.Types.gatherer_lane.from({
7
+ slot_index: slot,
8
+ yield: y,
9
+ drain: d,
10
+ depth,
11
+ output_pct: 100,
12
+ })
13
+ const cLane = (slot: number, s: number, d: number) =>
14
+ ServerContract.Types.crafter_lane.from({slot_index: slot, speed: s, drain: d, output_pct: 100})
15
+ const lLane = (slot: number, m: number, t: number) =>
16
+ ServerContract.Types.loader_lane.from({slot_index: slot, mass: m, thrust: t, output_pct: 100})
17
+
18
+ describe('rollupGatherer', () => {
19
+ test('empty → undefined', () => {
20
+ expect(rollupGatherer([])).toBeUndefined()
21
+ })
22
+ test('sums yield/drain, takes MAX depth', () => {
23
+ const r = rollupGatherer([gLane(2, 300, 1250, 500), gLane(3, 250, 1000, 5495)])!
24
+ expect(r.yield.toNumber()).toBe(550)
25
+ expect(r.drain.toNumber()).toBe(2250)
26
+ expect(r.depth.toNumber()).toBe(5495)
27
+ })
28
+ test('clamps summed yield to uint16', () => {
29
+ const r = rollupGatherer([gLane(2, 60000, 0, 1), gLane(3, 60000, 0, 1)])!
30
+ expect(r.yield.toNumber()).toBe(65535)
31
+ })
32
+ })
33
+
34
+ describe('rollupCrafter', () => {
35
+ test('empty → undefined', () => {
36
+ expect(rollupCrafter([])).toBeUndefined()
37
+ })
38
+ test('sums speed/drain', () => {
39
+ const r = rollupCrafter([cLane(2, 100, 30), cLane(3, 140, 25)])!
40
+ expect(r.speed.toNumber()).toBe(240)
41
+ expect(r.drain.toNumber()).toBe(55)
42
+ })
43
+ })
44
+
45
+ describe('rollupLoaders', () => {
46
+ test('empty → undefined', () => {
47
+ expect(rollupLoaders([])).toBeUndefined()
48
+ })
49
+ test('integer-averages mass, sums thrust, counts quantity', () => {
50
+ const r = rollupLoaders([lLane(2, 200, 5), lLane(3, 201, 7)])!
51
+ expect(r.mass.toNumber()).toBe(200) // floor(401/2)
52
+ expect(r.thrust.toNumber()).toBe(12)
53
+ expect(r.quantity.toNumber()).toBe(2)
54
+ })
55
+ })
@@ -0,0 +1,72 @@
1
+ import {UInt8, UInt16, UInt32} from '@wharfkit/antelope'
2
+ import type {ServerContract} from '../contracts'
3
+
4
+ export function rollupGatherer(
5
+ lanes: ServerContract.Types.gatherer_lane[]
6
+ ): {yield: UInt16; drain: UInt32; depth: UInt16} | undefined {
7
+ if (lanes.length === 0) return undefined
8
+ let totalYield = 0
9
+ let totalDrain = 0
10
+ let maxDepth = 0
11
+ for (const l of lanes) {
12
+ totalYield += Number(l.yield)
13
+ totalDrain += Number(l.drain)
14
+ const d = Number(l.depth)
15
+ if (d > maxDepth) maxDepth = d
16
+ }
17
+ return {
18
+ yield: UInt16.from(Math.min(totalYield, 65535)),
19
+ drain: UInt32.from(totalDrain),
20
+ depth: UInt16.from(maxDepth),
21
+ }
22
+ }
23
+
24
+ export function rollupCrafter(
25
+ lanes: ServerContract.Types.crafter_lane[]
26
+ ): {speed: UInt16; drain: UInt32} | undefined {
27
+ if (lanes.length === 0) return undefined
28
+ let totalSpeed = 0
29
+ let totalDrain = 0
30
+ for (const l of lanes) {
31
+ totalSpeed += Number(l.speed)
32
+ totalDrain += Number(l.drain)
33
+ }
34
+ return {
35
+ speed: UInt16.from(Math.min(totalSpeed, 65535)),
36
+ drain: UInt32.from(totalDrain),
37
+ }
38
+ }
39
+
40
+ export function rollupBuilder(
41
+ lanes: ServerContract.Types.builder_lane[]
42
+ ): {speed: UInt16; drain: UInt32} | undefined {
43
+ if (lanes.length === 0) return undefined
44
+ let totalSpeed = 0
45
+ let totalDrain = 0
46
+ for (const l of lanes) {
47
+ totalSpeed += Number(l.speed)
48
+ totalDrain += Number(l.drain)
49
+ }
50
+ return {
51
+ speed: UInt16.from(Math.min(totalSpeed, 65535)),
52
+ drain: UInt32.from(totalDrain),
53
+ }
54
+ }
55
+
56
+ export function rollupLoaders(
57
+ lanes: ServerContract.Types.loader_lane[]
58
+ ): {mass: UInt32; thrust: UInt16; quantity: UInt8} | undefined {
59
+ if (lanes.length === 0) return undefined
60
+ const count = lanes.length
61
+ let totalMass = 0
62
+ let totalThrust = 0
63
+ for (const l of lanes) {
64
+ totalMass += Number(l.mass)
65
+ totalThrust += Number(l.thrust)
66
+ }
67
+ return {
68
+ mass: UInt32.from(Math.floor(totalMass / count)),
69
+ thrust: UInt16.from(Math.min(totalThrust, 65535)),
70
+ quantity: UInt8.from(count),
71
+ }
72
+ }
@@ -0,0 +1,67 @@
1
+ import {expect, test} from 'bun:test'
2
+ import {
3
+ compareByStars,
4
+ MAX_STARS_PER_STAT,
5
+ MAX_STAR_RATING,
6
+ starRating,
7
+ starsForStat,
8
+ statMagnitude,
9
+ STAR_STEP,
10
+ } from './stars'
11
+
12
+ test('starsForStat bands at the 250 boundaries', () => {
13
+ expect(starsForStat(0)).toBe(0)
14
+ expect(starsForStat(1)).toBe(0)
15
+ expect(starsForStat(249)).toBe(0)
16
+ expect(starsForStat(250)).toBe(1)
17
+ expect(starsForStat(499)).toBe(1)
18
+ expect(starsForStat(500)).toBe(2)
19
+ expect(starsForStat(749)).toBe(2)
20
+ expect(starsForStat(750)).toBe(3)
21
+ })
22
+
23
+ test('starsForStat clamps to MAX_STARS_PER_STAT', () => {
24
+ expect(starsForStat(999)).toBe(MAX_STARS_PER_STAT)
25
+ expect(starsForStat(1000)).toBe(MAX_STARS_PER_STAT)
26
+ expect(starsForStat(10_000)).toBe(MAX_STARS_PER_STAT)
27
+ })
28
+
29
+ test('starsForStat never goes negative', () => {
30
+ expect(starsForStat(-50)).toBe(0)
31
+ })
32
+
33
+ test('starRating sums per-stat stars to a 0-9 grade', () => {
34
+ expect(starRating(0, 0, 0)).toBe(0)
35
+ expect(starRating(250, 0, 0)).toBe(1)
36
+ expect(starRating(750, 750, 750)).toBe(MAX_STAR_RATING)
37
+ expect(starRating(999, 999, 999)).toBe(MAX_STAR_RATING)
38
+ expect(starRating(599, 599, 599)).toBe(6)
39
+ expect(starRating(251, 251, 251)).toBe(3)
40
+ })
41
+
42
+ test('statMagnitude sums raw values for tiebreaking', () => {
43
+ expect(statMagnitude(599, 599, 599)).toBe(1797)
44
+ expect(statMagnitude(251, 251, 251)).toBe(753)
45
+ expect(statMagnitude(599, 599, 599)).toBeGreaterThan(statMagnitude(251, 251, 251))
46
+ })
47
+
48
+ test('compareByStars orders by rating desc, then magnitude desc', () => {
49
+ const rows = [
50
+ {key: 'low-rating', rating: 3, magnitude: 9000},
51
+ {key: 'high-rating', rating: 6, magnitude: 300},
52
+ {key: 'tie-small', rating: 6, magnitude: 600},
53
+ {key: 'tie-big', rating: 6, magnitude: 900},
54
+ ]
55
+ const ordered = [...rows].sort(compareByStars).map((r) => r.key)
56
+ expect(ordered).toEqual(['tie-big', 'tie-small', 'high-rating', 'low-rating'])
57
+ })
58
+
59
+ test('compareByStars returns 0 when rating and magnitude are equal', () => {
60
+ expect(compareByStars({rating: 4, magnitude: 500}, {rating: 4, magnitude: 500})).toBe(0)
61
+ })
62
+
63
+ test('constants hold their documented values', () => {
64
+ expect(STAR_STEP).toBe(250)
65
+ expect(MAX_STARS_PER_STAT).toBe(3)
66
+ expect(MAX_STAR_RATING).toBe(9)
67
+ })
@@ -0,0 +1,25 @@
1
+ export const STAR_STEP = 250
2
+ export const MAX_STARS_PER_STAT = 3
3
+ export const MAX_STAR_RATING = MAX_STARS_PER_STAT * 3
4
+
5
+ export function starsForStat(value: number): number {
6
+ return Math.max(0, Math.min(MAX_STARS_PER_STAT, Math.floor(value / STAR_STEP)))
7
+ }
8
+
9
+ export function starRating(stat1: number, stat2: number, stat3: number): number {
10
+ return starsForStat(stat1) + starsForStat(stat2) + starsForStat(stat3)
11
+ }
12
+
13
+ export function statMagnitude(stat1: number, stat2: number, stat3: number): number {
14
+ return stat1 + stat2 + stat3
15
+ }
16
+
17
+ export interface StarSortable {
18
+ rating: number
19
+ magnitude: number
20
+ }
21
+
22
+ // Star-quality ordering shared by every "sort by stars" surface: rating desc, then total sum of stats desc.
23
+ export function compareByStars(a: StarSortable, b: StarSortable): number {
24
+ return b.rating - a.rating || b.magnitude - a.magnitude
25
+ }
@@ -0,0 +1,12 @@
1
+ export const MODULE_STAT_SCALING_ANCHOR = 213
2
+ export const MODULE_STAT_SCALING_POST_ANCHOR_PERCENT = 40
3
+
4
+ export function computeEffectiveModuleStat(stat: number): number {
5
+ if (stat <= MODULE_STAT_SCALING_ANCHOR) return stat
6
+ return (
7
+ MODULE_STAT_SCALING_ANCHOR +
8
+ Math.floor(
9
+ ((stat - MODULE_STAT_SCALING_ANCHOR) * MODULE_STAT_SCALING_POST_ANCHOR_PERCENT) / 100
10
+ )
11
+ )
12
+ }
@@ -25,8 +25,7 @@ const ORE_STATS: StatDefinition[] = [
25
25
  key: 'density',
26
26
  label: 'Density',
27
27
  abbreviation: 'DEN',
28
- purpose: 'Mass per unit',
29
- inverted: true,
28
+ purpose: 'Structural integrity — higher rolls produce lighter hulls',
30
29
  },
31
30
  ]
32
31
 
@@ -74,10 +73,11 @@ const GAS_STATS: StatDefinition[] = [
74
73
 
75
74
  const REGOLITH_STATS: StatDefinition[] = [
76
75
  {
77
- key: 'composition',
78
- label: 'Composition',
79
- abbreviation: 'COM',
80
- purpose: 'Mineral/metal mix — drives sensor, chip, and optic crafting quality',
76
+ key: 'cohesion',
77
+ label: 'Cohesion',
78
+ abbreviation: 'COH',
79
+ purpose:
80
+ 'Binding strength of the loose aggregate; higher cohesion yields more rigid frames and hulls',
81
81
  },
82
82
  {
83
83
  key: 'hardness',
@@ -1,14 +1,22 @@
1
1
  import {Bytes, Checksum256, type Checksum256Type} from '@wharfkit/antelope'
2
2
  import {hash512} from '../utils/hash'
3
3
  import {Coordinates, type CoordinatesType} from '../types'
4
- import {getEligibleResources, getResourceWeight, YIELD_THRESHOLD} from './resources'
5
- import {RESERVE_TIERS, rollTier, rollWithinTier} from './tiers'
4
+ import {getItem} from '../data/catalog'
5
+ import {getEligibleResources, getResourceWeight, yieldThresholdAt} from './resources'
6
+ import {
7
+ RESERVE_TIERS,
8
+ rollTier,
9
+ rollWithinTier,
10
+ applyResourceTierMultiplier,
11
+ type ReserveTier,
12
+ } from './tiers'
6
13
 
7
14
  export interface StratumInfo {
8
15
  itemId: number
9
16
  seed: bigint
10
17
  richness: number
11
18
  reserve: number
19
+ tier: ReserveTier
12
20
  }
13
21
 
14
22
  export interface ResourceStats {
@@ -33,18 +41,11 @@ export function deriveStratum(
33
41
 
34
42
  const rawReserve = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0
35
43
 
36
- let reserve = 0
37
- if (rawReserve <= YIELD_THRESHOLD) {
38
- const tierRoll = ((bytes[18] << 8) | bytes[19]) >>> 0
39
- const withinRoll = ((bytes[20] << 8) | bytes[21]) >>> 0
40
- const tier = rollTier(tierRoll, stratum)
41
- reserve = rollWithinTier(withinRoll, RESERVE_TIERS[tier])
42
- }
43
-
44
- if (reserve === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
44
+ if (rawReserve > yieldThresholdAt(stratum))
45
+ return {itemId: 0, seed: 0n, richness: 0, reserve: 0, tier: 'small'}
45
46
 
46
47
  const eligible = getEligibleResources(locationType, subtype, stratum)
47
- if (eligible.length === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
48
+ if (eligible.length === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0, tier: 'small'}
48
49
 
49
50
  const resourceRoll = ((bytes[4] << 24) | (bytes[5] << 16) | (bytes[6] << 8) | bytes[7]) >>> 0
50
51
 
@@ -66,6 +67,13 @@ export function deriveStratum(
66
67
  }
67
68
  }
68
69
 
70
+ const tierRoll = ((bytes[18] << 8) | bytes[19]) >>> 0
71
+ const withinRoll = ((bytes[20] << 8) | bytes[21]) >>> 0
72
+ const tier = rollTier(tierRoll, stratum)
73
+ const selected = getItem(selectedItemId)
74
+ const baseReserve = rollWithinTier(withinRoll, RESERVE_TIERS[tier], selected.mass)
75
+ const reserve = applyResourceTierMultiplier(baseReserve, selected.tier)
76
+
69
77
  const seedBigInt =
70
78
  (BigInt(bytes[8]) << 56n) |
71
79
  (BigInt(bytes[9]) << 48n) |
@@ -76,17 +84,13 @@ export function deriveStratum(
76
84
  (BigInt(bytes[14]) << 8n) |
77
85
  BigInt(bytes[15])
78
86
 
79
- const rawRichness = (bytes[16] << 8) | bytes[17]
80
- const normalized = rawRichness / 65535
81
- const baseRichness = Math.floor(normalized * normalized * 999) + 1
82
-
83
- let depthBonus = 0
84
- if (stratum > 1) {
85
- depthBonus = (50 * Math.log(stratum)) / Math.log(65535)
86
- }
87
- const richness = Math.min(Math.floor(baseRichness + depthBonus), 1000)
87
+ let byteSum = 0
88
+ for (let i = 22; i <= 33; i++) byteSum += bytes[i]
89
+ const z = (byteSum - 1530) / 256
90
+ const roll = 500 + 100 * z
91
+ const richness = Math.max(1, Math.min(999, Math.round(roll)))
88
92
 
89
- return {itemId: selectedItemId, seed: seedBigInt, richness, reserve}
93
+ return {itemId: selectedItemId, seed: seedBigInt, richness, reserve, tier}
90
94
  }
91
95
 
92
96
  /**
@@ -1,3 +1,5 @@
1
+ import {getItem} from '../data/catalog'
2
+
1
3
  export type ReserveTier = 'small' | 'medium' | 'large' | 'massive' | 'motherlode'
2
4
 
3
5
  export interface TierRange {
@@ -6,11 +8,11 @@ export interface TierRange {
6
8
  }
7
9
 
8
10
  export const RESERVE_TIERS: Record<ReserveTier, TierRange> = {
9
- small: {min: 15, max: 60},
10
- medium: {min: 100, max: 200},
11
- large: {min: 400, max: 700},
12
- massive: {min: 1000, max: 2500},
13
- motherlode: {min: 4000, max: 10000},
11
+ small: {min: 3_600_000, max: 14_400_000},
12
+ medium: {min: 24_000_000, max: 48_000_000},
13
+ large: {min: 96_000_000, max: 168_000_000},
14
+ massive: {min: 240_000_000, max: 600_000_000},
15
+ motherlode: {min: 960_000_000, max: 2_400_000_000},
14
16
  }
15
17
 
16
18
  const SHALLOW_THRESHOLDS = {
@@ -47,8 +49,39 @@ export function rollTier(tierRoll: number, stratum: number): ReserveTier {
47
49
  return 'motherlode'
48
50
  }
49
51
 
50
- export function rollWithinTier(withinRoll: number, range: TierRange): number {
52
+ export function rollWithinTier(
53
+ withinRoll: number,
54
+ range: TierRange,
55
+ resourceUnitMass: number
56
+ ): number {
51
57
  const u = withinRoll / 65535
52
58
  const skewed = u * u
53
- return Math.floor(range.min + skewed * (range.max - range.min))
59
+ const depositMass = range.min + skewed * (range.max - range.min)
60
+ return Math.max(1, Math.floor(depositMass / resourceUnitMass))
61
+ }
62
+
63
+ // Must mirror the contract tier-multiplier table in tiers.hpp byte-for-byte; values are in tenths (T1..T10).
64
+ export const RESOURCE_TIER_MULT_TENTHS = [200, 154, 118, 91, 70, 54, 41, 32, 24, 19] as const
65
+
66
+ export function applyResourceTierMultiplier(units: number, resourceTier: number): number {
67
+ const idx = resourceTier < 1 ? 0 : resourceTier > 10 ? 9 : resourceTier - 1
68
+ const scaled = Math.floor((units * RESOURCE_TIER_MULT_TENTHS[idx]) / 10)
69
+ return scaled > 0 ? scaled : 1
70
+ }
71
+
72
+ const RESERVE_TIER_ENTRIES = Object.entries(RESERVE_TIERS) as Array<[ReserveTier, TierRange]>
73
+
74
+ export function tierOfReserve(reserve: number, itemId: number): ReserveTier | null {
75
+ if (reserve <= 0) return null
76
+ const item = getItem(itemId)
77
+ if (item.mass <= 0) return null
78
+ // Reverse the resource-tier multiplier so bands read relative to the resource tier.
79
+ const idx = item.tier < 1 ? 0 : item.tier > 10 ? 9 : item.tier - 1
80
+ const baseReserve = (reserve * 10) / RESOURCE_TIER_MULT_TENTHS[idx]
81
+ const impliedMassLow = baseReserve * item.mass
82
+ const impliedMassHigh = impliedMassLow + item.mass
83
+ for (const [tier, range] of RESERVE_TIER_ENTRIES) {
84
+ if (impliedMassHigh > range.min && impliedMassLow <= range.max) return tier
85
+ }
86
+ return null
54
87
  }
@@ -0,0 +1,14 @@
1
+ import recipes from '../data/recipes.json'
2
+ import type {Recipe} from '../data/recipes-runtime'
3
+
4
+ const bySource = new Map<number, Recipe[]>()
5
+ for (const r of recipes as Recipe[]) {
6
+ if (r.sourceSubclass === undefined) continue
7
+ const list = bySource.get(r.sourceSubclass) ?? []
8
+ list.push(r)
9
+ bySource.set(r.sourceSubclass, list)
10
+ }
11
+
12
+ export function eligibleUpgrades(entityItemId: number): Recipe[] {
13
+ return bySource.get(entityItemId) ?? []
14
+ }
@@ -0,0 +1,154 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {hash512} from '../utils/hash'
3
+ import {COORD_MAX, COORD_MIN} from '../coordinates/constants'
4
+
5
+ export const WH = {
6
+ RSIZE: 75,
7
+ ZONE: 16384,
8
+ THRESHOLD: 8192,
9
+ MIN_REACH: 50000,
10
+ TRANSIT_SPEED: 500,
11
+ } as const
12
+
13
+ const HALF = Math.round(Math.log2(WH.ZONE))
14
+ const MASK = WH.ZONE - 1
15
+
16
+ function roll16(seed: Checksum256Type, str: string): number {
17
+ const h = hash512(seed, str).array
18
+ return (h[0] << 8) | h[1]
19
+ }
20
+ function feistelF(seed: Checksum256Type, x: number, round: number, key: string): number {
21
+ return roll16(seed, `feistel-${key}-${round}-${x}`) & MASK
22
+ }
23
+ export function feistel(seed: Checksum256Type, idx: number, key: string): number {
24
+ let L = (idx >>> HALF) & MASK
25
+ let R = idx & MASK
26
+ for (let r = 0; r < 4; r++) {
27
+ const nR = L ^ feistelF(seed, R, r, key)
28
+ L = R
29
+ R = nR
30
+ }
31
+ return (L << HALF) | R
32
+ }
33
+ export function feistelInv(seed: Checksum256Type, idx: number, key: string): number {
34
+ let L = (idx >>> HALF) & MASK
35
+ let R = idx & MASK
36
+ for (let r = 3; r >= 0; r--) {
37
+ const nL = R ^ feistelF(seed, L, r, key)
38
+ R = L
39
+ L = nL
40
+ }
41
+ return (L << HALF) | R
42
+ }
43
+
44
+ type Region = {rx: number; ry: number}
45
+
46
+ export function regionOf(x: number, y: number): Region {
47
+ return {rx: Math.floor(x / WH.RSIZE), ry: Math.floor(y / WH.RSIZE)}
48
+ }
49
+ export function partnerRegion(seed: Checksum256Type, R: Region): Region {
50
+ const qx = Math.floor(R.rx / WH.ZONE)
51
+ const qy = Math.floor(R.ry / WH.ZONE)
52
+ const zx = qx * WH.ZONE
53
+ const zy = qy * WH.ZONE
54
+ const key = `${qx}:${qy}`
55
+ const idx = (R.ry - zy) * WH.ZONE + (R.rx - zx)
56
+ const p = feistelInv(seed, feistel(seed, idx, key) ^ 1, key)
57
+ return {rx: zx + (p % WH.ZONE), ry: zy + Math.floor(p / WH.ZONE)}
58
+ }
59
+ function regKey(R: Region): string {
60
+ return `${R.rx}:${R.ry}`
61
+ }
62
+ function pairKey(a: Region, b: Region): string {
63
+ const ka = regKey(a)
64
+ const kb = regKey(b)
65
+ return ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`
66
+ }
67
+ function endpointInRegion(seed: Checksum256Type, R: Region, key: string): {x: number; y: number} {
68
+ const h = hash512(seed, `wh-endpoint-${key}-${regKey(R)}`).array
69
+ const ox = ((h[0] << 24) | (h[1] << 16) | (h[2] << 8) | h[3]) >>> 0
70
+ const oy = ((h[4] << 24) | (h[5] << 16) | (h[6] << 8) | h[7]) >>> 0
71
+ return {x: R.rx * WH.RSIZE + (ox % WH.RSIZE), y: R.ry * WH.RSIZE + (oy % WH.RSIZE)}
72
+ }
73
+ function dist(a: {x: number; y: number}, b: {x: number; y: number}): number {
74
+ return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2)
75
+ }
76
+ function inBounds(c: {x: number; y: number}): boolean {
77
+ return c.x >= COORD_MIN && c.x <= COORD_MAX && c.y >= COORD_MIN && c.y <= COORD_MAX
78
+ }
79
+ type RegionWormhole = {A: {x: number; y: number}; B: {x: number; y: number}} | null
80
+
81
+ // ~11 sha512 rolls per region; pure in (seed, region), so memoized for A* neighbor probes.
82
+ const regionCache = new Map<string, RegionWormhole>()
83
+ const REGION_CACHE_MAX = 16384
84
+
85
+ function wormholeOfRegion(seed: Checksum256Type, R: Region): RegionWormhole {
86
+ const cacheKey = `${seed}:${R.rx}:${R.ry}`
87
+ const cached = regionCache.get(cacheKey)
88
+ if (cached !== undefined) return cached
89
+ const result = deriveWormholeOfRegion(seed, R)
90
+ if (regionCache.size >= REGION_CACHE_MAX) regionCache.clear()
91
+ regionCache.set(cacheKey, result)
92
+ return result
93
+ }
94
+
95
+ function deriveWormholeOfRegion(seed: Checksum256Type, R: Region): RegionWormhole {
96
+ const P = partnerRegion(seed, R)
97
+ if (P.rx === R.rx && P.ry === R.ry) return null
98
+ const key = pairKey(R, P)
99
+ if (roll16(seed, `wh-exists-${key}`) >= WH.THRESHOLD) return null
100
+ const A = endpointInRegion(seed, R, key)
101
+ const B = endpointInRegion(seed, P, key)
102
+ if (!inBounds(A) || !inBounds(B)) return null
103
+ if (dist(A, B) < WH.MIN_REACH) return null
104
+ return {A, B}
105
+ }
106
+ export function wormholeAtRegionEndpoint(
107
+ seed: Checksum256Type,
108
+ rx: number,
109
+ ry: number
110
+ ): {from: {x: number; y: number}; to: {x: number; y: number}} | null {
111
+ const w = wormholeOfRegion(seed, {rx, ry})
112
+ if (!w) return null
113
+ return {from: w.A, to: w.B}
114
+ }
115
+ export function wormholeAt(
116
+ seed: Checksum256Type,
117
+ x: number,
118
+ y: number
119
+ ): {x: number; y: number} | null {
120
+ const w = wormholeOfRegion(seed, regionOf(x, y))
121
+ if (!w || w.A.x !== x || w.A.y !== y) return null
122
+ return w.B
123
+ }
124
+
125
+ // Wormhole mouths (the local A endpoint) within reachTiles of (x,y); regions are RSIZE-wide so only a few overlap.
126
+ export function nearbyWormholes(
127
+ seed: Checksum256Type,
128
+ x: number,
129
+ y: number,
130
+ reachTiles: number
131
+ ): {x: number; y: number}[] {
132
+ const min = regionOf(x - reachTiles, y - reachTiles)
133
+ const max = regionOf(x + reachTiles, y + reachTiles)
134
+ const out: {x: number; y: number}[] = []
135
+ for (let rx = min.rx; rx <= max.rx; rx++) {
136
+ for (let ry = min.ry; ry <= max.ry; ry++) {
137
+ const w = wormholeOfRegion(seed, {rx, ry})
138
+ if (!w) continue
139
+ if (w.A.x === x && w.A.y === y) continue
140
+ if (dist({x, y}, w.A) <= reachTiles) out.push(w.A)
141
+ }
142
+ }
143
+ return out
144
+ }
145
+ export function isValidWormholePair(
146
+ seed: Checksum256Type,
147
+ ax: number,
148
+ ay: number,
149
+ bx: number,
150
+ by: number
151
+ ): boolean {
152
+ const to = wormholeAt(seed, ax, ay)
153
+ return to !== null && to.x === bx && to.y === by
154
+ }
@@ -0,0 +1,102 @@
1
+ import {UInt64} from '@wharfkit/antelope'
2
+ import {ServerContract} from '../contracts'
3
+ import {
4
+ CAP_DEMOLISH,
5
+ CAP_MODULES,
6
+ CAP_UNDEPLOY,
7
+ CAP_WRAP,
8
+ type EntityClass,
9
+ getEntityClass,
10
+ kindCan,
11
+ } from '../data/kind-registry'
12
+ import {InventoryAccessor} from './inventory-accessor'
13
+ import {Location} from './location'
14
+ import {ScheduleAccessor} from '../scheduling/accessor'
15
+ import * as schedule from '../scheduling/schedule'
16
+ import type {EntityInventory} from './entity-inventory'
17
+
18
+ export class Entity extends ServerContract.Types.entity_info {
19
+ private _sched?: ScheduleAccessor
20
+ private _inv?: InventoryAccessor
21
+
22
+ get name(): string {
23
+ return this.entity_name
24
+ }
25
+
26
+ get location(): Location {
27
+ return Location.from(this.coordinates)
28
+ }
29
+
30
+ get isIdle(): boolean {
31
+ return schedule.isIdle(this)
32
+ }
33
+
34
+ get hasPendingCapper(): boolean {
35
+ return schedule.hasPendingCapper(this)
36
+ }
37
+
38
+ get sched(): ScheduleAccessor {
39
+ this._sched ??= new ScheduleAccessor(this)
40
+ return this._sched
41
+ }
42
+
43
+ get inv(): InventoryAccessor {
44
+ this._inv ??= new InventoryAccessor(this)
45
+ return this._inv
46
+ }
47
+
48
+ get inventory(): EntityInventory[] {
49
+ return this.inv.items
50
+ }
51
+
52
+ get totalCargoMass(): UInt64 {
53
+ return this.inv.totalMass
54
+ }
55
+
56
+ get maxCapacity(): UInt64 {
57
+ return UInt64.from(this.capacity ?? 0)
58
+ }
59
+
60
+ get availableCapacity(): UInt64 {
61
+ const cargo = this.totalCargoMass
62
+ const max = this.maxCapacity
63
+ return cargo.gte(max) ? UInt64.from(0) : max.subtracting(cargo)
64
+ }
65
+
66
+ get isFull(): boolean {
67
+ return this.totalCargoMass.gte(this.maxCapacity)
68
+ }
69
+
70
+ get totalMass(): UInt64 {
71
+ const hull = this.hullmass ? UInt64.from(this.hullmass) : UInt64.from(0)
72
+ return hull.adding(this.totalCargoMass)
73
+ }
74
+
75
+ get entityClass(): EntityClass {
76
+ return getEntityClass(this.type)
77
+ }
78
+
79
+ get canWrap(): boolean {
80
+ return kindCan(this.type, CAP_WRAP)
81
+ }
82
+
83
+ get canUndeploy(): boolean {
84
+ return kindCan(this.type, CAP_UNDEPLOY)
85
+ }
86
+
87
+ get canDemolish(): boolean {
88
+ return kindCan(this.type, CAP_DEMOLISH)
89
+ }
90
+
91
+ get canUseModules(): boolean {
92
+ return kindCan(this.type, CAP_MODULES)
93
+ }
94
+
95
+ isLoading(now: Date): boolean {
96
+ return schedule.isLoading(this, now)
97
+ }
98
+
99
+ isUnloading(now: Date): boolean {
100
+ return schedule.isUnloading(this, now)
101
+ }
102
+ }