@shipload/sdk 1.0.0-next.7 → 1.0.0-next.71

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 (203) 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 +5822 -1484
  7. package/lib/shipload.js +20838 -5419
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +20674 -5669
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1691 -0
  12. package/lib/testing.js +7109 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +7103 -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 +80 -0
  20. package/src/capabilities/crafting.ts +50 -8
  21. package/src/capabilities/gathering.test.ts +29 -0
  22. package/src/capabilities/gathering.ts +38 -20
  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.test.ts +26 -0
  28. package/src/capabilities/storage.ts +22 -7
  29. package/src/contracts/fund.ts +306 -0
  30. package/src/contracts/platform.ts +312 -3
  31. package/src/contracts/server.ts +2793 -478
  32. package/src/contracts/token.ts +170 -0
  33. package/src/contracts.ts +2 -0
  34. package/src/coordinates/address.ts +88 -0
  35. package/src/coordinates/constants.test.ts +15 -0
  36. package/src/coordinates/constants.ts +23 -0
  37. package/src/coordinates/index.ts +15 -0
  38. package/src/coordinates/memo.test.ts +47 -0
  39. package/src/coordinates/memo.ts +20 -0
  40. package/src/coordinates/permutation.ts +77 -0
  41. package/src/coordinates/regions.ts +48 -0
  42. package/src/coordinates/sectors.ts +115 -0
  43. package/src/data/capabilities.ts +49 -18
  44. package/src/data/capability-formulas.ts +26 -14
  45. package/src/data/catalog.test.ts +19 -0
  46. package/src/data/catalog.ts +13 -3
  47. package/src/data/charters.json +396 -0
  48. package/src/data/citizenry-adjectives.json +42 -0
  49. package/src/data/citizenry-nouns.json +50 -0
  50. package/src/data/colors.ts +14 -32
  51. package/src/data/entities.json +421 -15
  52. package/src/data/item-ids.ts +53 -13
  53. package/src/data/items.json +330 -77
  54. package/src/data/kind-registry.json +212 -0
  55. package/src/data/kind-registry.ts +192 -0
  56. package/src/data/metadata.ts +312 -51
  57. package/src/data/recipes-runtime.ts +6 -23
  58. package/src/data/recipes.json +2173 -173
  59. package/src/data/tiers.ts +9 -6
  60. package/src/derivation/build-methods.test.ts +71 -0
  61. package/src/derivation/build-methods.ts +50 -0
  62. package/src/derivation/capabilities.test.ts +348 -0
  63. package/src/derivation/capabilities.ts +707 -0
  64. package/src/derivation/capability-mappings.ts +60 -16
  65. package/src/derivation/crafting.test.ts +17 -0
  66. package/src/derivation/crafting.ts +57 -34
  67. package/src/derivation/index.ts +27 -2
  68. package/src/derivation/recipe-usage.test.ts +88 -0
  69. package/src/derivation/recipe-usage.ts +141 -0
  70. package/src/derivation/reserve-regen.ts +34 -0
  71. package/src/derivation/resources.ts +9 -1
  72. package/src/derivation/rollups.test.ts +55 -0
  73. package/src/derivation/rollups.ts +72 -0
  74. package/src/derivation/stars.test.ts +67 -0
  75. package/src/derivation/stars.ts +25 -0
  76. package/src/derivation/stat-scaling.ts +12 -0
  77. package/src/derivation/stats.ts +6 -6
  78. package/src/derivation/strata.ts +5 -3
  79. package/src/derivation/stratum.ts +26 -22
  80. package/src/derivation/tiers.ts +40 -7
  81. package/src/derivation/upgrades.ts +14 -0
  82. package/src/derivation/wormhole.ts +154 -0
  83. package/src/entities/entity.ts +102 -0
  84. package/src/entities/gamestate.ts +3 -28
  85. package/src/entities/makers.ts +144 -136
  86. package/src/entities/slot-multiplier.ts +43 -0
  87. package/src/entity/hydrate.test.ts +156 -0
  88. package/src/entity/hydrate.ts +86 -0
  89. package/src/errors.ts +21 -16
  90. package/src/format.ts +42 -9
  91. package/src/index-module.ts +425 -32
  92. package/src/influence/ballot.test.ts +103 -0
  93. package/src/influence/ballot.ts +98 -0
  94. package/src/influence/categories.ts +32 -0
  95. package/src/influence/charter-metadata.test.ts +136 -0
  96. package/src/influence/charter-metadata.ts +236 -0
  97. package/src/influence/charters.test.ts +112 -0
  98. package/src/influence/charters.ts +150 -0
  99. package/src/influence/citizenry.ts +38 -0
  100. package/src/influence/civic-shuttle.test.ts +202 -0
  101. package/src/influence/civic-shuttle.ts +142 -0
  102. package/src/influence/civic.test.ts +287 -0
  103. package/src/influence/civic.ts +269 -0
  104. package/src/influence/constants.ts +84 -0
  105. package/src/influence/decay.ts +20 -0
  106. package/src/influence/decomp.ts +95 -0
  107. package/src/influence/demand.ts +106 -0
  108. package/src/influence/duration.ts +101 -0
  109. package/src/influence/index.ts +118 -0
  110. package/src/influence/influence.test.ts +450 -0
  111. package/src/influence/quality.ts +25 -0
  112. package/src/influence/valuation.ts +154 -0
  113. package/src/managers/actions.ts +950 -93
  114. package/src/managers/balances.test.ts +161 -0
  115. package/src/managers/balances.ts +93 -0
  116. package/src/managers/base.ts +10 -2
  117. package/src/managers/cluster.test.ts +39 -0
  118. package/src/managers/cluster.ts +53 -0
  119. package/src/managers/construction-types.ts +80 -0
  120. package/src/managers/construction.ts +453 -0
  121. package/src/managers/context.ts +58 -1
  122. package/src/managers/coordinates.ts +14 -0
  123. package/src/managers/entities.ts +35 -63
  124. package/src/managers/epochs.ts +72 -1
  125. package/src/managers/index.ts +38 -1
  126. package/src/managers/influence.test.ts +56 -0
  127. package/src/managers/influence.ts +425 -0
  128. package/src/managers/jobs.test.ts +96 -0
  129. package/src/managers/jobs.ts +58 -0
  130. package/src/managers/locations.ts +25 -29
  131. package/src/managers/nft.test.ts +21 -0
  132. package/src/managers/nft.ts +110 -0
  133. package/src/managers/players.ts +25 -0
  134. package/src/managers/plot.ts +122 -0
  135. package/src/nft/atomicassets.abi.json +1342 -0
  136. package/src/nft/atomicassets.ts +237 -0
  137. package/src/nft/atomicdata.ts +130 -0
  138. package/src/nft/buildImmutableData.ts +388 -0
  139. package/src/nft/description.ts +222 -100
  140. package/src/nft/index.ts +3 -0
  141. package/src/planner/index.ts +296 -0
  142. package/src/planner/planner.test.ts +459 -0
  143. package/src/receipts.ts +22 -0
  144. package/src/resolution/describe-module.ts +43 -25
  145. package/src/resolution/display-name.ts +39 -11
  146. package/src/resolution/resolve-item.test.ts +48 -0
  147. package/src/resolution/resolve-item.ts +112 -58
  148. package/src/scan/index.ts +244 -0
  149. package/src/scan/scan-wasm.base64.ts +2 -0
  150. package/src/scheduling/accessor.ts +65 -23
  151. package/src/scheduling/availability.test.ts +320 -0
  152. package/src/scheduling/availability.ts +251 -0
  153. package/src/scheduling/cancel.test.ts +550 -0
  154. package/src/scheduling/cancel.ts +254 -0
  155. package/src/scheduling/cluster-stock.test.ts +31 -0
  156. package/src/scheduling/cluster-stock.ts +15 -0
  157. package/src/scheduling/energy.ts +47 -0
  158. package/src/scheduling/epoch.ts +1 -0
  159. package/src/scheduling/idle-resolve.ts +45 -0
  160. package/src/scheduling/jobs.test.ts +448 -0
  161. package/src/scheduling/jobs.ts +343 -0
  162. package/src/scheduling/lane-core.ts +128 -0
  163. package/src/scheduling/lanes.test.ts +250 -0
  164. package/src/scheduling/lanes.ts +279 -0
  165. package/src/scheduling/projection.ts +265 -149
  166. package/src/scheduling/schedule.test.ts +163 -0
  167. package/src/scheduling/schedule.ts +282 -116
  168. package/src/scheduling/task-cargo.test.ts +110 -0
  169. package/src/scheduling/task-cargo.ts +62 -0
  170. package/src/scheduling/task-effects.test.ts +35 -0
  171. package/src/scheduling/task-effects.ts +34 -0
  172. package/src/scheduling/unwrap.test.ts +86 -0
  173. package/src/scheduling/unwrap.ts +190 -0
  174. package/src/shipload.ts +63 -4
  175. package/src/subscriptions/manager.cluster.test.ts +51 -0
  176. package/src/subscriptions/manager.ts +245 -172
  177. package/src/subscriptions/mappers.ts +5 -8
  178. package/src/subscriptions/types.ts +28 -3
  179. package/src/testing/catalog-hash.ts +19 -0
  180. package/src/testing/index.ts +2 -0
  181. package/src/testing/projection-parity.ts +167 -0
  182. package/src/travel/reach.test.ts +86 -0
  183. package/src/travel/reach.ts +34 -0
  184. package/src/travel/route-build.test.ts +63 -0
  185. package/src/travel/route-build.ts +60 -0
  186. package/src/travel/route-planner.ts +288 -0
  187. package/src/travel/route-simulator.ts +170 -0
  188. package/src/travel/travel.ts +190 -123
  189. package/src/types/capabilities.ts +37 -8
  190. package/src/types/entity.ts +3 -3
  191. package/src/types/index.ts +0 -1
  192. package/src/types.ts +81 -23
  193. package/src/utils/cargo.test.ts +14 -0
  194. package/src/utils/cargo.ts +2 -0
  195. package/src/utils/display-name.ts +70 -0
  196. package/src/utils/hash.ts +22 -2
  197. package/src/utils/system.ts +24 -5
  198. package/src/capabilities/loading.ts +0 -8
  199. package/src/entities/container.ts +0 -123
  200. package/src/entities/ship-deploy.ts +0 -295
  201. package/src/entities/ship.ts +0 -221
  202. package/src/entities/warehouse.ts +0 -127
  203. 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,5 +1,5 @@
1
- import type {Checksum256Type} from '@wharfkit/antelope'
2
- import type {CoordinatesType} from '../types'
1
+ import {Checksum256, type Checksum256Type} from '@wharfkit/antelope'
2
+ import {Coordinates, type CoordinatesType} from '../types'
3
3
  import {deriveLocationStatic} from '../utils/system'
4
4
  import {deriveLocationSize} from './location-size'
5
5
  import {deriveResourceStats, deriveStratum, type ResourceStats} from './stratum'
@@ -26,9 +26,11 @@ export function deriveStrata(
26
26
  if (size === 0) return []
27
27
 
28
28
  const subtype = Number(loc.subtype)
29
+ const seed = Checksum256.from(epochSeed)
30
+ const at = Coordinates.from(coords)
29
31
  const out: DerivedStratum[] = []
30
32
  for (let i = 0; i < size; i++) {
31
- const s = deriveStratum(epochSeed, coords, i, locType, subtype, size)
33
+ const s = deriveStratum(seed, at, i, locType, subtype, size)
32
34
  if (s.reserve === 0) continue
33
35
  out.push({
34
36
  index: i,
@@ -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: 36_000, max: 144_000},
12
+ medium: {min: 240_000, max: 480_000},
13
+ large: {min: 960_000, max: 1_680_000},
14
+ massive: {min: 2_400_000, max: 6_000_000},
15
+ motherlode: {min: 9_600_000, max: 24_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
+ }