@shipload/sdk 2.0.0-rc2 → 2.0.0-rc20

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 (80) hide show
  1. package/README.md +1 -349
  2. package/lib/shipload.d.ts +1658 -1126
  3. package/lib/shipload.js +6847 -3082
  4. package/lib/shipload.js.map +1 -1
  5. package/lib/shipload.m.js +6468 -2793
  6. package/lib/shipload.m.js.map +1 -1
  7. package/package.json +6 -4
  8. package/src/capabilities/crafting.ts +22 -0
  9. package/src/capabilities/gathering.ts +36 -0
  10. package/src/capabilities/guards.ts +3 -8
  11. package/src/capabilities/hauling.ts +22 -0
  12. package/src/capabilities/index.ts +4 -1
  13. package/src/capabilities/modules.ts +57 -0
  14. package/src/capabilities/storage.ts +101 -9
  15. package/src/contracts/server.ts +717 -293
  16. package/src/data/capabilities.ts +408 -0
  17. package/src/data/categories.ts +55 -0
  18. package/src/data/colors.ts +71 -0
  19. package/src/data/items.json +17 -0
  20. package/src/data/locations.ts +53 -0
  21. package/src/data/nebula-adjectives.json +211 -0
  22. package/src/data/nebula-nouns.json +151 -0
  23. package/src/data/recipes.ts +587 -0
  24. package/src/data/syllables.json +1386 -780
  25. package/src/data/tiers.ts +45 -0
  26. package/src/derivation/crafting.ts +287 -0
  27. package/src/derivation/index.ts +30 -0
  28. package/src/derivation/location-size.ts +15 -0
  29. package/src/derivation/resources.ts +136 -0
  30. package/src/derivation/stats.ts +146 -0
  31. package/src/derivation/stratum.ts +134 -0
  32. package/src/derivation/tiers.ts +54 -0
  33. package/src/entities/cargo-utils.ts +10 -68
  34. package/src/entities/container.ts +37 -0
  35. package/src/entities/entity-inventory.ts +13 -13
  36. package/src/entities/inventory-accessor.ts +2 -6
  37. package/src/entities/location.ts +5 -200
  38. package/src/entities/makers.ts +136 -17
  39. package/src/entities/player.ts +1 -274
  40. package/src/entities/ship-deploy.ts +258 -0
  41. package/src/entities/ship.ts +28 -34
  42. package/src/entities/warehouse.ts +35 -7
  43. package/src/errors.ts +59 -5
  44. package/src/format.ts +12 -0
  45. package/src/index-module.ts +233 -50
  46. package/src/managers/actions.ts +138 -88
  47. package/src/managers/context.ts +19 -9
  48. package/src/managers/index.ts +0 -1
  49. package/src/managers/locations.ts +2 -85
  50. package/src/market/items.ts +93 -0
  51. package/src/nft/description.ts +176 -0
  52. package/src/nft/deserializers.ts +81 -0
  53. package/src/nft/index.ts +2 -0
  54. package/src/resolution/describe-module.ts +165 -0
  55. package/src/resolution/display-name.ts +39 -0
  56. package/src/resolution/resolve-item.ts +343 -0
  57. package/src/scheduling/projection.ts +220 -67
  58. package/src/scheduling/schedule.ts +2 -2
  59. package/src/shipload.ts +10 -5
  60. package/src/subscriptions/connection.ts +154 -0
  61. package/src/subscriptions/debug.ts +17 -0
  62. package/src/subscriptions/index.ts +5 -0
  63. package/src/subscriptions/manager.ts +240 -0
  64. package/src/subscriptions/mappers.ts +28 -0
  65. package/src/subscriptions/types.ts +143 -0
  66. package/src/travel/travel.ts +30 -17
  67. package/src/types/capabilities.ts +11 -14
  68. package/src/types/entity-traits.ts +3 -4
  69. package/src/types/entity.ts +9 -6
  70. package/src/types.ts +61 -55
  71. package/src/utils/system.ts +66 -53
  72. package/src/capabilities/extraction.ts +0 -37
  73. package/src/data/goods.json +0 -23
  74. package/src/managers/trades.ts +0 -119
  75. package/src/market/goods.ts +0 -31
  76. package/src/market/market.ts +0 -208
  77. package/src/market/rolls.ts +0 -8
  78. package/src/trading/collect.ts +0 -938
  79. package/src/trading/deal.ts +0 -207
  80. package/src/trading/trade.ts +0 -203
@@ -0,0 +1,134 @@
1
+ import {Bytes, Checksum256, Checksum256Type} from '@wharfkit/antelope'
2
+ import {hash512} from '../utils/hash'
3
+ import {Coordinates, CoordinatesType} from '../types'
4
+ import {getEligibleResources, getResourceWeight, YIELD_THRESHOLD} from './resources'
5
+ import {RESERVE_TIERS, rollTier, rollWithinTier} from './tiers'
6
+
7
+ export interface StratumInfo {
8
+ itemId: number
9
+ seed: bigint
10
+ richness: number
11
+ reserve: number
12
+ }
13
+
14
+ export interface ResourceStats {
15
+ stat1: number
16
+ stat2: number
17
+ stat3: number
18
+ }
19
+
20
+ export function deriveStratum(
21
+ epochSeed: Checksum256Type,
22
+ coords: CoordinatesType,
23
+ stratum: number,
24
+ locationType: number,
25
+ subtype: number,
26
+ _maxDepth: number
27
+ ): StratumInfo {
28
+ const seed = Checksum256.from(epochSeed)
29
+ const c = Coordinates.from(coords)
30
+ const input = `stratum-${c.x}-${c.y}-${stratum}`
31
+ const hashResult = hash512(seed, input)
32
+ const bytes = hashResult.array
33
+
34
+ const rawReserve = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0
35
+
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}
45
+
46
+ const eligible = getEligibleResources(locationType, subtype, stratum)
47
+ if (eligible.length === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
48
+
49
+ const resourceRoll = ((bytes[4] << 24) | (bytes[5] << 16) | (bytes[6] << 8) | bytes[7]) >>> 0
50
+
51
+ let totalWeight = 0
52
+ for (const id of eligible) {
53
+ totalWeight += getResourceWeight(id, stratum)
54
+ }
55
+
56
+ let selectedItemId = eligible[0]
57
+ if (totalWeight > 0) {
58
+ const roll = resourceRoll % totalWeight
59
+ let cumulative = 0
60
+ for (const id of eligible) {
61
+ cumulative += getResourceWeight(id, stratum)
62
+ if (roll < cumulative) {
63
+ selectedItemId = id
64
+ break
65
+ }
66
+ }
67
+ }
68
+
69
+ const seedBigInt =
70
+ (BigInt(bytes[8]) << 56n) |
71
+ (BigInt(bytes[9]) << 48n) |
72
+ (BigInt(bytes[10]) << 40n) |
73
+ (BigInt(bytes[11]) << 32n) |
74
+ (BigInt(bytes[12]) << 24n) |
75
+ (BigInt(bytes[13]) << 16n) |
76
+ (BigInt(bytes[14]) << 8n) |
77
+ BigInt(bytes[15])
78
+
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)
88
+
89
+ return {itemId: selectedItemId, seed: seedBigInt, richness, reserve}
90
+ }
91
+
92
+ /**
93
+ * Derives the three stat values for a raw resource from a deposit's
94
+ * entropy seed (hash-based, weibull-transformed).
95
+ *
96
+ * **Use only on deposit seeds** — the bigint returned by `deriveStratum`
97
+ * or carried on a `MassDeposit`. Do NOT call this on a cargo item's
98
+ * `stats` field; cargo stats are bit-packed and must be read via
99
+ * `decodeStat` (or `decodeStackStats` for category-mapped output).
100
+ *
101
+ * Passing a cargo `stats` value here produces meaningless output
102
+ * (hash of the packed bits, unrelated to the actual stats).
103
+ */
104
+ export function deriveResourceStats(seed: bigint): ResourceStats {
105
+ const seedBytes = new Uint8Array(8)
106
+ for (let i = 0; i < 8; i++) {
107
+ seedBytes[i] = Number(seed & 0xffn)
108
+ seed >>= 8n
109
+ }
110
+ const hashResult = Checksum256.hash(Bytes.from(seedBytes))
111
+ const hashBytes = hashResult.array
112
+
113
+ const extractU32 = (offset: number): number =>
114
+ (hashBytes[offset] * 0x1000000 +
115
+ (hashBytes[offset + 1] << 16) +
116
+ (hashBytes[offset + 2] << 8) +
117
+ hashBytes[offset + 3]) >>>
118
+ 0
119
+
120
+ const weibull = (raw: number): number => {
121
+ const u = raw / 4294967296
122
+ let x = 0.24 * Math.sqrt(-Math.log(1 - u))
123
+ if (x > 1) x = 1
124
+ let val = Math.floor(x * 999) + 1
125
+ if (val > 999) val = 999
126
+ return val
127
+ }
128
+
129
+ return {
130
+ stat1: weibull(extractU32(0)),
131
+ stat2: weibull(extractU32(4)),
132
+ stat3: weibull(extractU32(8)),
133
+ }
134
+ }
@@ -0,0 +1,54 @@
1
+ export type ReserveTier = 'small' | 'medium' | 'large' | 'massive' | 'motherlode'
2
+
3
+ export interface TierRange {
4
+ min: number
5
+ max: number
6
+ }
7
+
8
+ 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},
14
+ }
15
+
16
+ const SHALLOW_THRESHOLDS = {
17
+ small: 0.8,
18
+ medium: 0.991946,
19
+ large: 0.999946,
20
+ massive: 0.999996,
21
+ }
22
+
23
+ const DEEP_THRESHOLDS = {
24
+ small: 0.5,
25
+ medium: 0.95892,
26
+ large: 0.99892,
27
+ massive: 0.99992,
28
+ }
29
+
30
+ export const TIER_ROLL_MAX = 0x10000 // 65536
31
+
32
+ function lerp(a: number, b: number, t: number): number {
33
+ return a + (b - a) * t
34
+ }
35
+
36
+ export function rollTier(tierRoll: number, stratum: number): ReserveTier {
37
+ const d = Math.min(stratum, 65535) / 65535
38
+ const smallMax = lerp(SHALLOW_THRESHOLDS.small, DEEP_THRESHOLDS.small, d) * TIER_ROLL_MAX
39
+ const mediumMax = lerp(SHALLOW_THRESHOLDS.medium, DEEP_THRESHOLDS.medium, d) * TIER_ROLL_MAX
40
+ const largeMax = lerp(SHALLOW_THRESHOLDS.large, DEEP_THRESHOLDS.large, d) * TIER_ROLL_MAX
41
+ const massiveMax = lerp(SHALLOW_THRESHOLDS.massive, DEEP_THRESHOLDS.massive, d) * TIER_ROLL_MAX
42
+
43
+ if (tierRoll < smallMax) return 'small'
44
+ if (tierRoll < mediumMax) return 'medium'
45
+ if (tierRoll < largeMax) return 'large'
46
+ if (tierRoll < massiveMax) return 'massive'
47
+ return 'motherlode'
48
+ }
49
+
50
+ export function rollWithinTier(withinRoll: number, range: TierRange): number {
51
+ const u = withinRoll / 65535
52
+ const skewed = u * u
53
+ return Math.floor(range.min + skewed * (range.max - range.min))
54
+ }
@@ -12,17 +12,11 @@ export function totalCargoMass(cargo: EntityInventory[]): UInt64 {
12
12
  }, UInt64.from(0))
13
13
  }
14
14
 
15
- export function cargoValue(cargo: EntityInventory[]): UInt64 {
16
- return cargo.reduce((sum, c) => {
17
- return sum.adding(c.totalCost)
18
- }, UInt64.from(0))
19
- }
20
-
21
- export function getCargoForGood(
15
+ export function getCargoForItem(
22
16
  cargo: EntityInventory[],
23
17
  goodId: UInt64Type
24
18
  ): EntityInventory | undefined {
25
- return cargo.find((c) => c.good_id.equals(goodId))
19
+ return cargo.find((c) => c.item_id.equals(goodId))
26
20
  }
27
21
 
28
22
  export function hasSpace(
@@ -47,84 +41,33 @@ export function isFull(currentMass: UInt64, maxCapacity: UInt64): boolean {
47
41
  return currentMass.gte(maxCapacity)
48
42
  }
49
43
 
50
- export interface SaleValue {
51
- revenue: UInt64
52
- profit: UInt64
53
- cost: UInt64
54
- }
55
-
56
- export function calculateSaleValue(
57
- cargo: ServerContract.Types.cargo_item[],
58
- prices: Map<number, UInt64>
59
- ): SaleValue {
60
- if (cargo.length === 0) {
61
- return {revenue: UInt64.from(0), profit: UInt64.from(0), cost: UInt64.from(0)}
62
- }
63
-
64
- let revenue = UInt64.from(0)
65
- let cost = UInt64.from(0)
66
-
67
- for (const item of cargo) {
68
- if (UInt32.from(item.quantity).equals(UInt32.from(0))) continue
69
-
70
- const goodId = Number(item.good_id)
71
- const salePrice = prices.get(goodId)
72
-
73
- if (salePrice) {
74
- revenue = revenue.adding(salePrice.multiplying(item.quantity))
75
- }
76
-
77
- cost = cost.adding(item.unit_cost.multiplying(item.quantity))
78
- }
79
-
80
- const profit = revenue.gte(cost) ? revenue.subtracting(cost) : UInt64.from(0)
81
-
82
- return {
83
- revenue,
84
- profit,
85
- cost,
86
- }
87
- }
88
-
89
- export function calculateSaleValueFromArray(
90
- cargo: ServerContract.Types.cargo_item[],
91
- prices: UInt64[]
92
- ): SaleValue {
93
- const priceMap = new Map<number, UInt64>()
94
- prices.forEach((price, index) => {
95
- priceMap.set(index, price)
96
- })
97
- return calculateSaleValue(cargo, priceMap)
98
- }
99
-
100
- export function afterSellGoods(
44
+ export function afterRemoveItems(
101
45
  cargo: ServerContract.Types.cargo_item[],
102
- goodsToSell: Array<{goodId: number; quantity: number}>
46
+ goodsToRemove: Array<{goodId: number; quantity: number}>
103
47
  ): EntityInventory[] {
104
48
  if (cargo.length === 0) {
105
49
  return []
106
50
  }
107
51
 
108
52
  return cargo.map((item) => {
109
- const saleItem = goodsToSell.find((s) => Number(item.good_id) === s.goodId)
110
- if (!saleItem) {
53
+ const removeItem = goodsToRemove.find((s) => Number(item.item_id) === s.goodId)
54
+ if (!removeItem) {
111
55
  return new EntityInventory(item)
112
56
  }
113
57
 
114
58
  const currentQty = Number(item.quantity)
115
- const newQty = Math.max(0, currentQty - saleItem.quantity)
59
+ const newQty = Math.max(0, currentQty - removeItem.quantity)
116
60
 
117
61
  return new EntityInventory(
118
62
  ServerContract.Types.cargo_item.from({
119
- good_id: item.good_id,
63
+ item_id: item.item_id,
120
64
  quantity: UInt32.from(newQty),
121
- unit_cost: item.unit_cost,
122
65
  })
123
66
  )
124
67
  })
125
68
  }
126
69
 
127
- export function afterSellAllGoods(cargo: ServerContract.Types.cargo_item[]): EntityInventory[] {
70
+ export function afterRemoveAllItems(cargo: ServerContract.Types.cargo_item[]): EntityInventory[] {
128
71
  if (cargo.length === 0) {
129
72
  return []
130
73
  }
@@ -133,9 +76,8 @@ export function afterSellAllGoods(cargo: ServerContract.Types.cargo_item[]): Ent
133
76
  (item) =>
134
77
  new EntityInventory(
135
78
  ServerContract.Types.cargo_item.from({
136
- good_id: item.good_id,
79
+ item_id: item.item_id,
137
80
  quantity: UInt32.from(0),
138
- unit_cost: item.unit_cost,
139
81
  })
140
82
  )
141
83
  )
@@ -13,6 +13,7 @@ export interface ContainerStateInput {
13
13
  hullmass: number
14
14
  capacity: number
15
15
  cargomass?: number
16
+ cargo?: ServerContract.Types.cargo_item[]
16
17
  schedule?: ServerContract.Types.schedule
17
18
  }
18
19
 
@@ -68,3 +69,39 @@ export class Container extends ServerContract.Types.entity_info {
68
69
  return this.coordinates.z?.toNumber() || 0
69
70
  }
70
71
  }
72
+
73
+ export function computeContainerCapabilities(stats: Record<string, number>): {
74
+ hullmass: number
75
+ capacity: number
76
+ } {
77
+ const density = stats['density'] ?? 500
78
+ const strength = stats['strength'] ?? 500
79
+ const fineness = stats['fineness'] ?? 500
80
+ const saturation = stats['saturation'] ?? 500
81
+
82
+ const hullmass = 25000 + 75 * density
83
+
84
+ const statSum = strength + fineness + saturation
85
+ const exponent = statSum / 2997
86
+ const capacity = Math.floor(1000000 * Math.pow(10, exponent))
87
+
88
+ return {hullmass, capacity}
89
+ }
90
+
91
+ export function computeContainerT2Capabilities(stats: Record<string, number>): {
92
+ hullmass: number
93
+ capacity: number
94
+ } {
95
+ const strength = stats['strength'] ?? 0
96
+ const density = stats['density'] ?? 0
97
+ const fineness = stats['fineness'] ?? 0
98
+ const saturation = stats['saturation'] ?? 0
99
+
100
+ const hullmass = 20000 + 50 * density
101
+
102
+ const statSum = strength + fineness + saturation
103
+ const exponent = statSum / 2500
104
+ const capacity = Math.floor(1500000 * Math.pow(10, exponent))
105
+
106
+ return {hullmass, capacity}
107
+ }
@@ -1,34 +1,34 @@
1
1
  import {UInt32, UInt64} from '@wharfkit/antelope'
2
2
  import {ServerContract} from '../contracts'
3
- import {getGood} from '../market/goods'
4
- import {Good} from '../types'
3
+ import {getItem} from '../market/items'
4
+ import {Item} from '../types'
5
5
 
6
6
  export class EntityInventory extends ServerContract.Types.cargo_item {
7
- private _good?: Good
7
+ private _item?: Item
8
8
 
9
- get good(): Good {
10
- if (!this._good) {
11
- this._good = getGood(this.good_id)
9
+ get item(): Item {
10
+ if (!this._item) {
11
+ this._item = getItem(this.item_id)
12
12
  }
13
- return this._good
13
+ return this._item
14
+ }
15
+
16
+ get good(): Item {
17
+ return this.item
14
18
  }
15
19
 
16
20
  get name(): string {
17
- return this.good.name
21
+ return this.item.displayName
18
22
  }
19
23
 
20
24
  get unitMass(): UInt32 {
21
- return this.good.mass
25
+ return this.item.mass
22
26
  }
23
27
 
24
28
  get totalMass(): UInt64 {
25
29
  return UInt64.from(this.unitMass).multiplying(this.quantity)
26
30
  }
27
31
 
28
- get totalCost(): UInt64 {
29
- return this.unit_cost.multiplying(this.quantity)
30
- }
31
-
32
32
  get hasCargo(): boolean {
33
33
  return UInt32.from(this.quantity).gt(UInt32.from(0))
34
34
  }
@@ -20,12 +20,8 @@ export class InventoryAccessor {
20
20
  return this.items.reduce((sum, c) => sum.adding(c.totalMass), UInt64.from(0))
21
21
  }
22
22
 
23
- get totalValue(): UInt64 {
24
- return this.items.reduce((sum, c) => sum.adding(c.totalCost), UInt64.from(0))
25
- }
26
-
27
- forGood(goodId: UInt64Type): EntityInventory | undefined {
28
- return this.items.find((c) => c.good_id.equals(goodId))
23
+ forItem(goodId: UInt64Type): EntityInventory | undefined {
24
+ return this.items.find((c) => c.item_id.equals(goodId))
29
25
  }
30
26
 
31
27
  get sellable(): EntityInventory[] {
@@ -1,35 +1,22 @@
1
- import {Checksum256, Checksum256Type, UInt16, UInt16Type, UInt64} from '@wharfkit/antelope'
2
- import {ServerContract} from '../contracts'
3
- import {Coordinates, CoordinatesType, Distance, GoodPrice, LocationType} from '../types'
4
- import {getLocationType, hasSystem, isExtractableLocation} from '../utils/system'
1
+ import {Checksum256, Checksum256Type, UInt16Type, UInt64} from '@wharfkit/antelope'
2
+ import {Coordinates, CoordinatesType, Distance, LocationType} from '../types'
3
+ import {getLocationType, hasSystem, isGatherableLocation} from '../utils/system'
5
4
  import {findNearbyPlanets} from '../travel/travel'
6
5
 
7
- /**
8
- * Location helper class for working with game coordinates.
9
- * Provides system detection, market price caching, nearby planet finding, and supply tracking.
10
- */
11
6
  export class Location {
12
7
  readonly coordinates: Coordinates
13
- private _marketPrices?: GoodPrice[]
14
8
  private _gameSeed?: Checksum256
15
9
  private _hasSystem?: boolean
16
- private _locationRows?: ServerContract.Types.supply_row[]
17
10
  private _epoch?: UInt64
18
11
 
19
12
  constructor(coordinates: CoordinatesType) {
20
13
  this.coordinates = Coordinates.from(coordinates)
21
14
  }
22
15
 
23
- /**
24
- * Create a Location from coordinates
25
- */
26
16
  static from(coordinates: CoordinatesType): Location {
27
17
  return new Location(Coordinates.from(coordinates))
28
18
  }
29
19
 
30
- /**
31
- * Check if this location has a system (planet, asteroid, or nebula)
32
- */
33
20
  hasSystemAt(gameSeed: Checksum256Type): boolean {
34
21
  const seed = Checksum256.from(gameSeed)
35
22
  if (this._hasSystem === undefined || !this._gameSeed?.equals(seed)) {
@@ -39,214 +26,32 @@ export class Location {
39
26
  return this._hasSystem
40
27
  }
41
28
 
42
- /**
43
- * Get the location type (EMPTY, PLANET, ASTEROID, or NEBULA)
44
- */
45
29
  getLocationTypeAt(gameSeed: Checksum256Type): LocationType {
46
30
  return getLocationType(gameSeed, this.coordinates)
47
31
  }
48
32
 
49
- /**
50
- * Check if this location is extractable (asteroid or nebula)
51
- */
52
- isExtractableAt(gameSeed: Checksum256Type): boolean {
53
- return isExtractableLocation(this.getLocationTypeAt(gameSeed))
33
+ isGatherableAt(gameSeed: Checksum256Type): boolean {
34
+ return isGatherableLocation(this.getLocationTypeAt(gameSeed))
54
35
  }
55
36
 
56
- /**
57
- * Set cached market prices for this location
58
- */
59
- setMarketPrices(prices: GoodPrice[]): void {
60
- this._marketPrices = prices
61
- }
62
-
63
- /**
64
- * Get cached market prices (returns undefined if not cached)
65
- */
66
- get marketPrices(): GoodPrice[] | undefined {
67
- return this._marketPrices
68
- }
69
-
70
- /**
71
- * Get price for a specific good (from cache)
72
- */
73
- getPrice(goodId: UInt16Type): GoodPrice | undefined {
74
- if (!this._marketPrices) return undefined
75
- return this._marketPrices.find((p) => p.id.equals(goodId))
76
- }
77
-
78
- /**
79
- * Find nearby planets from this location
80
- */
81
37
  findNearby(gameSeed: Checksum256Type, maxDistance: UInt16Type = 20): Distance[] {
82
38
  return findNearbyPlanets(Checksum256.from(gameSeed), this.coordinates, maxDistance)
83
39
  }
84
40
 
85
- /**
86
- * Check if this location equals another location
87
- */
88
41
  equals(other: CoordinatesType | Location): boolean {
89
42
  const otherCoords = other instanceof Location ? other.coordinates : Coordinates.from(other)
90
43
  return this.coordinates.equals(otherCoords)
91
44
  }
92
45
 
93
- /**
94
- * Set location rows (supply data) for this location
95
- */
96
- setLocationRows(rows: ServerContract.Types.supply_row[], epoch: UInt64): void {
97
- this._locationRows = rows
98
- this._epoch = epoch
99
- }
100
-
101
- /**
102
- * Get cached location rows (supply data)
103
- */
104
- get locationRows(): ServerContract.Types.supply_row[] | undefined {
105
- return this._locationRows
106
- }
107
-
108
- /**
109
- * Get supply for a specific good at this location
110
- * Returns undefined if location rows not cached or good not found
111
- */
112
- getSupply(goodId: UInt16Type): UInt16 | undefined {
113
- if (!this._locationRows) return undefined
114
- const row = this._locationRows.find(
115
- (r) => r.good_id.equals(goodId) && this._epoch && r.epoch.equals(this._epoch)
116
- )
117
- return row ? row.supply : undefined
118
- }
119
-
120
- /**
121
- * Get all available goods at this location (goods with supply > 0)
122
- * Returns undefined if location rows not cached
123
- */
124
- get availableGoods(): ServerContract.Types.supply_row[] | undefined {
125
- if (!this._locationRows) return undefined
126
- return this._locationRows.filter(
127
- (r) => this._epoch && r.epoch.equals(this._epoch) && r.supply.gt(UInt16.from(0))
128
- )
129
- }
130
-
131
- /**
132
- * Check if a specific good is available (has supply)
133
- * Returns false if location rows not cached
134
- */
135
- hasGood(goodId: UInt16Type): boolean {
136
- const supply = this.getSupply(goodId)
137
- return supply !== undefined && supply.gt(UInt16.from(0))
138
- }
139
-
140
- /**
141
- * Get the epoch for cached location data
142
- */
143
46
  get epoch(): UInt64 | undefined {
144
47
  return this._epoch
145
48
  }
146
49
 
147
- /**
148
- * Check if cached data exists
149
- */
150
- get hasCachedData(): boolean {
151
- return this._marketPrices !== undefined || this._locationRows !== undefined
152
- }
153
-
154
- /**
155
- * Check if supply data is cached
156
- */
157
- get hasSupplyData(): boolean {
158
- return this._locationRows !== undefined
159
- }
160
-
161
- /**
162
- * Clear all cached data
163
- */
164
50
  clearCache(): void {
165
- this._marketPrices = undefined
166
- this._locationRows = undefined
167
51
  this._epoch = undefined
168
52
  }
169
-
170
- /**
171
- * Create optimistic Location with updated supply after purchase/sale.
172
- * Matches contract: update_location_supply (delta can be positive or negative)
173
- * Contract reference: market.cpp:53, 123-151
174
- *
175
- * @param goodId - Good ID to update supply for
176
- * @param quantityDelta - Change in supply (negative for purchase, positive for sale)
177
- * @returns New Location with updated supply in cached data
178
- *
179
- * @example
180
- * // After buying 10 units (supply decreases)
181
- * const newLocation = location.withUpdatedSupply(1, -10)
182
- *
183
- * // After selling 5 units (supply increases)
184
- * const newLocation = location.withUpdatedSupply(1, 5)
185
- */
186
- withUpdatedSupply(goodId: UInt16Type, quantityDelta: number): Location {
187
- const newLocation = Location.from(this.coordinates)
188
-
189
- // Copy market prices if cached
190
- if (this._marketPrices) {
191
- newLocation._marketPrices = this._marketPrices.map((price) => {
192
- if (price.id.equals(goodId)) {
193
- const currentSupply = UInt16.from(price.supply)
194
- const delta = UInt16.from(Math.abs(quantityDelta))
195
- const newSupply =
196
- quantityDelta < 0
197
- ? currentSupply.gte(delta)
198
- ? currentSupply.subtracting(delta)
199
- : UInt16.from(0)
200
- : currentSupply.adding(quantityDelta)
201
-
202
- return GoodPrice.from({
203
- id: price.id,
204
- good: price.good,
205
- price: price.price,
206
- supply: newSupply,
207
- })
208
- }
209
- return price
210
- })
211
- }
212
-
213
- // Copy location rows if cached
214
- if (this._locationRows && this._epoch) {
215
- newLocation._locationRows = this._locationRows.map((row) => {
216
- if (row.good_id.equals(goodId) && row.epoch.equals(this._epoch!)) {
217
- const currentSupply = UInt16.from(row.supply)
218
- const delta = UInt16.from(Math.abs(quantityDelta))
219
- const newSupply =
220
- quantityDelta < 0
221
- ? currentSupply.gte(delta)
222
- ? currentSupply.subtracting(delta)
223
- : UInt16.from(0)
224
- : currentSupply.adding(quantityDelta)
225
-
226
- return ServerContract.Types.supply_row.from({
227
- id: row.id,
228
- coordinates: row.coordinates,
229
- epoch: row.epoch,
230
- good_id: row.good_id,
231
- supply: newSupply,
232
- })
233
- }
234
- return row
235
- })
236
- newLocation._epoch = this._epoch
237
- }
238
-
239
- // Copy other cached data
240
- newLocation._gameSeed = this._gameSeed
241
- newLocation._hasSystem = this._hasSystem
242
-
243
- return newLocation
244
- }
245
53
  }
246
54
 
247
- /**
248
- * Helper function to convert various coordinate types to Location
249
- */
250
55
  export function toLocation(coords: CoordinatesType | Location): Location {
251
56
  if (coords instanceof Location) {
252
57
  return coords