@shipload/sdk 1.0.0-next.5 → 1.0.0-next.50

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 (144) 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 +2881 -1149
  7. package/lib/shipload.js +13031 -5511
  8. package/lib/shipload.js.map +1 -1
  9. package/lib/shipload.m.js +12807 -5460
  10. package/lib/shipload.m.js.map +1 -1
  11. package/lib/testing.d.ts +1028 -0
  12. package/lib/testing.js +4336 -0
  13. package/lib/testing.js.map +1 -0
  14. package/lib/testing.m.js +4330 -0
  15. package/lib/testing.m.js.map +1 -0
  16. package/package.json +20 -2
  17. package/src/capabilities/craftable.ts +51 -0
  18. package/src/capabilities/crafting.test.ts +7 -0
  19. package/src/capabilities/crafting.ts +6 -7
  20. package/src/capabilities/gathering.test.ts +29 -0
  21. package/src/capabilities/gathering.ts +37 -19
  22. package/src/capabilities/index.ts +0 -1
  23. package/src/capabilities/modules.ts +22 -0
  24. package/src/capabilities/storage.ts +16 -1
  25. package/src/contracts/platform.ts +231 -3
  26. package/src/contracts/server.ts +1160 -520
  27. package/src/coordinates/address.ts +88 -0
  28. package/src/coordinates/constants.test.ts +15 -0
  29. package/src/coordinates/constants.ts +23 -0
  30. package/src/coordinates/index.ts +15 -0
  31. package/src/coordinates/memo.test.ts +47 -0
  32. package/src/coordinates/memo.ts +20 -0
  33. package/src/coordinates/permutation.ts +77 -0
  34. package/src/coordinates/regions.ts +48 -0
  35. package/src/coordinates/sectors.ts +115 -0
  36. package/src/data/capabilities.ts +31 -16
  37. package/src/data/capability-formulas.ts +21 -14
  38. package/src/data/catalog.ts +0 -5
  39. package/src/data/colors.ts +13 -47
  40. package/src/data/entities.json +154 -10
  41. package/src/data/item-ids.ts +31 -12
  42. package/src/data/items.json +192 -74
  43. package/src/data/kind-registry.json +131 -0
  44. package/src/data/kind-registry.ts +172 -0
  45. package/src/data/metadata.ts +166 -48
  46. package/src/data/recipes-runtime.ts +5 -23
  47. package/src/data/recipes.json +1003 -163
  48. package/src/derivation/build-methods.test.ts +13 -0
  49. package/src/derivation/build-methods.ts +48 -0
  50. package/src/derivation/capabilities.test.ts +151 -0
  51. package/src/derivation/capabilities.ts +527 -0
  52. package/src/derivation/capability-mappings.ts +58 -16
  53. package/src/derivation/crafting.ts +36 -33
  54. package/src/derivation/index.ts +28 -2
  55. package/src/derivation/recipe-usage.test.ts +81 -0
  56. package/src/derivation/recipe-usage.ts +141 -0
  57. package/src/derivation/reserve-regen.ts +34 -0
  58. package/src/derivation/resources.ts +98 -19
  59. package/src/derivation/rollups.test.ts +55 -0
  60. package/src/derivation/rollups.ts +56 -0
  61. package/src/derivation/stars.test.ts +67 -0
  62. package/src/derivation/stars.ts +25 -0
  63. package/src/derivation/stats.ts +6 -6
  64. package/src/derivation/stratum.ts +26 -22
  65. package/src/derivation/tiers.ts +40 -7
  66. package/src/derivation/upgrades.ts +14 -0
  67. package/src/derivation/wormhole.ts +141 -0
  68. package/src/entities/entity.ts +98 -0
  69. package/src/entities/gamestate.ts +3 -28
  70. package/src/entities/makers.ts +131 -134
  71. package/src/entities/slot-multiplier.ts +43 -0
  72. package/src/errors.ts +12 -16
  73. package/src/format.ts +26 -4
  74. package/src/index-module.ts +290 -34
  75. package/src/managers/actions.ts +602 -105
  76. package/src/managers/base.ts +6 -2
  77. package/src/managers/cluster.test.ts +39 -0
  78. package/src/managers/cluster.ts +53 -0
  79. package/src/managers/construction-types.ts +80 -0
  80. package/src/managers/construction.ts +412 -0
  81. package/src/managers/context.ts +29 -1
  82. package/src/managers/coordinates.ts +14 -0
  83. package/src/managers/entities.ts +27 -66
  84. package/src/managers/epochs.ts +40 -0
  85. package/src/managers/flatten-gather-plan.test.ts +80 -0
  86. package/src/managers/index.ts +20 -1
  87. package/src/managers/locations.ts +25 -29
  88. package/src/managers/nft.test.ts +14 -0
  89. package/src/managers/nft.ts +70 -0
  90. package/src/managers/players.ts +25 -0
  91. package/src/managers/plot.ts +122 -0
  92. package/src/nft/atomicassets.abi.json +1342 -0
  93. package/src/nft/atomicassets.ts +237 -0
  94. package/src/nft/atomicdata.ts +130 -0
  95. package/src/nft/buildImmutableData.ts +336 -0
  96. package/src/nft/description.ts +104 -36
  97. package/src/nft/index.ts +3 -0
  98. package/src/planner/index.ts +242 -0
  99. package/src/planner/planner.test.ts +325 -0
  100. package/src/resolution/describe-module.ts +43 -25
  101. package/src/resolution/display-name.ts +38 -10
  102. package/src/resolution/resolve-item.test.ts +48 -0
  103. package/src/resolution/resolve-item.ts +74 -47
  104. package/src/scan/index.ts +244 -0
  105. package/src/scan/scan-wasm.base64.ts +2 -0
  106. package/src/scheduling/accessor.ts +65 -23
  107. package/src/scheduling/availability.ts +108 -0
  108. package/src/scheduling/cancel.test.ts +348 -0
  109. package/src/scheduling/cancel.ts +209 -0
  110. package/src/scheduling/energy.ts +47 -0
  111. package/src/scheduling/idle-resolve.ts +45 -0
  112. package/src/scheduling/lane-core.ts +128 -0
  113. package/src/scheduling/lanes.test.ts +249 -0
  114. package/src/scheduling/lanes.ts +198 -0
  115. package/src/scheduling/projection.ts +210 -107
  116. package/src/scheduling/schedule.ts +240 -117
  117. package/src/scheduling/task-cargo.ts +46 -0
  118. package/src/scheduling/unwrap.test.ts +86 -0
  119. package/src/scheduling/unwrap.ts +190 -0
  120. package/src/shipload.ts +26 -1
  121. package/src/subscriptions/manager.cluster.test.ts +51 -0
  122. package/src/subscriptions/manager.ts +245 -172
  123. package/src/subscriptions/mappers.ts +5 -8
  124. package/src/subscriptions/types.ts +28 -3
  125. package/src/testing/catalog-hash.ts +19 -0
  126. package/src/testing/index.ts +2 -0
  127. package/src/testing/projection-parity.ts +167 -0
  128. package/src/travel/reach.ts +23 -0
  129. package/src/travel/route-planner.ts +277 -0
  130. package/src/travel/travel.ts +148 -117
  131. package/src/types/capabilities.ts +29 -6
  132. package/src/types/entity.ts +3 -3
  133. package/src/types/index.ts +0 -1
  134. package/src/types.ts +36 -14
  135. package/src/utils/cargo.test.ts +14 -0
  136. package/src/utils/cargo.ts +29 -0
  137. package/src/utils/display-name.ts +70 -0
  138. package/src/utils/system.ts +36 -24
  139. package/src/capabilities/loading.ts +0 -8
  140. package/src/entities/container.ts +0 -123
  141. package/src/entities/ship-deploy.ts +0 -295
  142. package/src/entities/ship.ts +0 -221
  143. package/src/entities/warehouse.ts +0 -127
  144. package/src/types/entity-traits.ts +0 -69
@@ -0,0 +1,56 @@
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 rollupLoaders(
41
+ lanes: ServerContract.Types.loader_lane[]
42
+ ): {mass: UInt32; thrust: UInt16; quantity: UInt8} | undefined {
43
+ if (lanes.length === 0) return undefined
44
+ const count = lanes.length
45
+ let totalMass = 0
46
+ let totalThrust = 0
47
+ for (const l of lanes) {
48
+ totalMass += Number(l.mass)
49
+ totalThrust += Number(l.thrust)
50
+ }
51
+ return {
52
+ mass: UInt32.from(Math.floor(totalMass / count)),
53
+ thrust: UInt16.from(Math.min(totalThrust, 65535)),
54
+ quantity: UInt8.from(count),
55
+ }
56
+ }
@@ -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
+ }
@@ -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,141 @@
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
+ function wormholeOfRegion(
80
+ seed: Checksum256Type,
81
+ R: Region
82
+ ): {A: {x: number; y: number}; B: {x: number; y: number}} | null {
83
+ const P = partnerRegion(seed, R)
84
+ if (P.rx === R.rx && P.ry === R.ry) return null
85
+ const key = pairKey(R, P)
86
+ if (roll16(seed, `wh-exists-${key}`) >= WH.THRESHOLD) return null
87
+ const A = endpointInRegion(seed, R, key)
88
+ const B = endpointInRegion(seed, P, key)
89
+ if (!inBounds(A) || !inBounds(B)) return null
90
+ if (dist(A, B) < WH.MIN_REACH) return null
91
+ return {A, B}
92
+ }
93
+ export function wormholeAtRegionEndpoint(
94
+ seed: Checksum256Type,
95
+ rx: number,
96
+ ry: number
97
+ ): {from: {x: number; y: number}; to: {x: number; y: number}} | null {
98
+ const w = wormholeOfRegion(seed, {rx, ry})
99
+ if (!w) return null
100
+ return {from: w.A, to: w.B}
101
+ }
102
+ export function wormholeAt(
103
+ seed: Checksum256Type,
104
+ x: number,
105
+ y: number
106
+ ): {x: number; y: number} | null {
107
+ const w = wormholeOfRegion(seed, regionOf(x, y))
108
+ if (!w || w.A.x !== x || w.A.y !== y) return null
109
+ return w.B
110
+ }
111
+
112
+ // Wormhole mouths (the local A endpoint) within reachTiles of (x,y); regions are RSIZE-wide so only a few overlap.
113
+ export function nearbyWormholes(
114
+ seed: Checksum256Type,
115
+ x: number,
116
+ y: number,
117
+ reachTiles: number
118
+ ): {x: number; y: number}[] {
119
+ const min = regionOf(x - reachTiles, y - reachTiles)
120
+ const max = regionOf(x + reachTiles, y + reachTiles)
121
+ const out: {x: number; y: number}[] = []
122
+ for (let rx = min.rx; rx <= max.rx; rx++) {
123
+ for (let ry = min.ry; ry <= max.ry; ry++) {
124
+ const w = wormholeOfRegion(seed, {rx, ry})
125
+ if (!w) continue
126
+ if (w.A.x === x && w.A.y === y) continue
127
+ if (dist({x, y}, w.A) <= reachTiles) out.push(w.A)
128
+ }
129
+ }
130
+ return out
131
+ }
132
+ export function isValidWormholePair(
133
+ seed: Checksum256Type,
134
+ ax: number,
135
+ ay: number,
136
+ bx: number,
137
+ by: number
138
+ ): boolean {
139
+ const to = wormholeAt(seed, ax, ay)
140
+ return to !== null && to.x === bx && to.y === by
141
+ }
@@ -0,0 +1,98 @@
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 sched(): ScheduleAccessor {
35
+ this._sched ??= new ScheduleAccessor(this)
36
+ return this._sched
37
+ }
38
+
39
+ get inv(): InventoryAccessor {
40
+ this._inv ??= new InventoryAccessor(this)
41
+ return this._inv
42
+ }
43
+
44
+ get inventory(): EntityInventory[] {
45
+ return this.inv.items
46
+ }
47
+
48
+ get totalCargoMass(): UInt64 {
49
+ return this.inv.totalMass
50
+ }
51
+
52
+ get maxCapacity(): UInt64 {
53
+ return UInt64.from(this.capacity ?? 0)
54
+ }
55
+
56
+ get availableCapacity(): UInt64 {
57
+ const cargo = this.totalCargoMass
58
+ const max = this.maxCapacity
59
+ return cargo.gte(max) ? UInt64.from(0) : max.subtracting(cargo)
60
+ }
61
+
62
+ get isFull(): boolean {
63
+ return this.totalCargoMass.gte(this.maxCapacity)
64
+ }
65
+
66
+ get totalMass(): UInt64 {
67
+ const hull = this.hullmass ? UInt64.from(this.hullmass) : UInt64.from(0)
68
+ return hull.adding(this.totalCargoMass)
69
+ }
70
+
71
+ get entityClass(): EntityClass {
72
+ return getEntityClass(this.type)
73
+ }
74
+
75
+ get canWrap(): boolean {
76
+ return kindCan(this.type, CAP_WRAP)
77
+ }
78
+
79
+ get canUndeploy(): boolean {
80
+ return kindCan(this.type, CAP_UNDEPLOY)
81
+ }
82
+
83
+ get canDemolish(): boolean {
84
+ return kindCan(this.type, CAP_DEMOLISH)
85
+ }
86
+
87
+ get canUseModules(): boolean {
88
+ return kindCan(this.type, CAP_MODULES)
89
+ }
90
+
91
+ isLoading(now: Date): boolean {
92
+ return schedule.isLoading(this, now)
93
+ }
94
+
95
+ isUnloading(now: Date): boolean {
96
+ return schedule.isUnloading(this, now)
97
+ }
98
+ }
@@ -1,4 +1,4 @@
1
- import {Checksum256, Int64, type UInt64} from '@wharfkit/antelope'
1
+ import {Checksum256, Int64, UInt64} from '@wharfkit/antelope'
2
2
  import {type PlatformContract, ServerContract} from '../contracts'
3
3
  import {type EpochInfo, getCurrentEpoch, getEpochInfo} from '../scheduling/epoch'
4
4
  import {hasSystem} from '../utils/system'
@@ -34,7 +34,7 @@ export class GameState extends ServerContract.Types.state_row {
34
34
  * Get the current epoch number from the state
35
35
  */
36
36
  get currentEpoch(): UInt64 {
37
- return this.epoch
37
+ return UInt64.from(this.epoch.toString())
38
38
  }
39
39
 
40
40
  /**
@@ -58,27 +58,6 @@ export class GameState extends ServerContract.Types.state_row {
58
58
  return this.enabled
59
59
  }
60
60
 
61
- /**
62
- * Get the total number of ships in the game
63
- */
64
- get shipCount(): number {
65
- return Number(this.ships)
66
- }
67
-
68
- /**
69
- * Get the current salt value (used for random number generation)
70
- */
71
- get currentSalt(): UInt64 {
72
- return this.salt
73
- }
74
-
75
- /**
76
- * Get the commit hash for the next epoch
77
- */
78
- get nextEpochCommit(): Checksum256 {
79
- return this.commit
80
- }
81
-
82
61
  /**
83
62
  * Calculate the current epoch from game config (if game is set)
84
63
  * This might differ from state.epoch if the blockchain hasn't advanced yet
@@ -97,7 +76,7 @@ export class GameState extends ServerContract.Types.state_row {
97
76
  if (!this._game) {
98
77
  return undefined
99
78
  }
100
- return getEpochInfo(this._game, this.epoch)
79
+ return getEpochInfo(this._game, this.currentEpoch)
101
80
  }
102
81
 
103
82
  /**
@@ -137,16 +116,12 @@ export class GameState extends ServerContract.Types.state_row {
137
116
  get summary(): {
138
117
  enabled: boolean
139
118
  epoch: string
140
- ships: number
141
119
  hasSeed: boolean
142
- hasCommit: boolean
143
120
  } {
144
121
  return {
145
122
  enabled: this.enabled,
146
123
  epoch: this.epoch.toString(),
147
- ships: this.shipCount,
148
124
  hasSeed: !this.seed.equals(Checksum256.from('0'.repeat(64))),
149
- hasCommit: !this.commit.equals(Checksum256.from('0'.repeat(64))),
150
125
  }
151
126
  }
152
127
  }