@shipload/sdk 2.0.0-rc1 → 2.0.0-rc11

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 (74) hide show
  1. package/lib/shipload.d.ts +1701 -1183
  2. package/lib/shipload.js +6746 -3447
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +6429 -3229
  5. package/lib/shipload.m.js.map +1 -1
  6. package/package.json +6 -6
  7. package/src/capabilities/crafting.ts +26 -0
  8. package/src/capabilities/gathering.ts +36 -0
  9. package/src/capabilities/guards.ts +38 -0
  10. package/src/capabilities/hauling.ts +22 -0
  11. package/src/capabilities/index.ts +8 -0
  12. package/src/capabilities/loading.ts +8 -0
  13. package/src/capabilities/modules.ts +57 -0
  14. package/src/capabilities/movement.ts +29 -0
  15. package/src/capabilities/storage.ts +72 -0
  16. package/src/contracts/server.ts +932 -314
  17. package/src/data/capabilities.ts +408 -0
  18. package/src/data/categories.ts +58 -0
  19. package/src/data/colors.ts +53 -0
  20. package/src/data/items.json +17 -0
  21. package/src/data/locations.ts +53 -0
  22. package/src/data/nebula-adjectives.json +211 -0
  23. package/src/data/nebula-nouns.json +151 -0
  24. package/src/data/recipes.ts +571 -0
  25. package/src/data/syllables.json +1386 -780
  26. package/src/data/tiers.ts +45 -0
  27. package/src/derivation/crafting.ts +197 -0
  28. package/src/derivation/index.ts +28 -0
  29. package/src/derivation/location-size.ts +15 -0
  30. package/src/derivation/resources.ts +142 -0
  31. package/src/derivation/stats.ts +146 -0
  32. package/src/derivation/stratum.ts +124 -0
  33. package/src/entities/cargo-utils.ts +46 -9
  34. package/src/entities/container.ts +106 -0
  35. package/src/entities/entity-inventory.ts +13 -13
  36. package/src/entities/inventory-accessor.ts +42 -0
  37. package/src/entities/location.ts +7 -188
  38. package/src/entities/makers.ts +72 -0
  39. package/src/entities/player.ts +1 -273
  40. package/src/entities/ship-deploy.ts +263 -0
  41. package/src/entities/ship.ts +93 -453
  42. package/src/entities/warehouse.ts +34 -148
  43. package/src/errors.ts +4 -4
  44. package/src/index-module.ts +226 -42
  45. package/src/managers/actions.ts +111 -79
  46. package/src/managers/context.ts +0 -9
  47. package/src/managers/entities.ts +22 -5
  48. package/src/managers/index.ts +0 -1
  49. package/src/managers/locations.ts +15 -79
  50. package/src/market/items.ts +30 -0
  51. package/src/nft/description.ts +175 -0
  52. package/src/nft/deserializers.ts +81 -0
  53. package/src/nft/index.ts +2 -0
  54. package/src/resolution/resolve-item.ts +313 -0
  55. package/src/scheduling/accessor.ts +82 -0
  56. package/src/scheduling/projection.ts +158 -54
  57. package/src/scheduling/schedule.ts +24 -0
  58. package/src/shipload.ts +0 -5
  59. package/src/travel/travel.ts +93 -19
  60. package/src/types/capabilities.ts +71 -0
  61. package/src/types/entity-traits.ts +69 -0
  62. package/src/types/entity.ts +39 -0
  63. package/src/types/index.ts +3 -0
  64. package/src/types.ts +76 -33
  65. package/src/utils/hash.ts +1 -1
  66. package/src/utils/system.ts +148 -11
  67. package/src/data/goods.json +0 -23
  68. package/src/managers/trades.ts +0 -119
  69. package/src/market/goods.ts +0 -31
  70. package/src/market/market.ts +0 -209
  71. package/src/market/rolls.ts +0 -8
  72. package/src/trading/collect.ts +0 -939
  73. package/src/trading/deal.ts +0 -208
  74. package/src/trading/trade.ts +0 -203
@@ -0,0 +1,45 @@
1
+ export type CraftedItemCategory = 'component' | 'module' | 'entity' | 'resource'
2
+
3
+ export const ITEM_TYPE_RESOURCE = 0
4
+ export const ITEM_TYPE_COMPONENT = 1
5
+ export const ITEM_TYPE_MODULE = 2
6
+ export const ITEM_TYPE_ENTITY = 3
7
+
8
+ export function itemTypeCode(id: number): number {
9
+ switch (itemCategory(id)) {
10
+ case 'resource':
11
+ return ITEM_TYPE_RESOURCE
12
+ case 'component':
13
+ return ITEM_TYPE_COMPONENT
14
+ case 'module':
15
+ return ITEM_TYPE_MODULE
16
+ case 'entity':
17
+ return ITEM_TYPE_ENTITY
18
+ }
19
+ }
20
+
21
+ export function itemTier(id: number): number {
22
+ if (id < 10000) return 0
23
+ return Math.floor(id / 10000)
24
+ }
25
+
26
+ export function itemOffset(id: number): number {
27
+ return id % 10000
28
+ }
29
+
30
+ export function itemCategory(id: number): CraftedItemCategory {
31
+ if (id < 10000) return 'resource'
32
+ const offset = itemOffset(id)
33
+ if (offset >= 200) return 'entity'
34
+ if (offset >= 100) return 'module'
35
+ return 'component'
36
+ }
37
+
38
+ export function isRelatedItem(a: number, b: number): boolean {
39
+ if (a < 10000 || b < 10000) return false
40
+ return itemOffset(a) === itemOffset(b)
41
+ }
42
+
43
+ export function isCraftedItem(id: number): boolean {
44
+ return id >= 10000
45
+ }
@@ -0,0 +1,197 @@
1
+ import {UInt64} from '@wharfkit/antelope'
2
+ import type {ResourceCategory} from '../types'
3
+ import {
4
+ entityRecipes,
5
+ getComponentById,
6
+ getEntityRecipe,
7
+ getModuleRecipe,
8
+ moduleRecipes,
9
+ } from '../data/recipes'
10
+ import {deriveResourceStats} from './stratum'
11
+
12
+ export interface StackInput {
13
+ quantity: number
14
+ stats: Record<string, number>
15
+ }
16
+
17
+ export interface CategoryStacks {
18
+ category: ResourceCategory
19
+ stacks: StackInput[]
20
+ }
21
+
22
+ export function encodeStats(values: number[]): bigint {
23
+ let seed = 0n
24
+ for (let i = 0; i < values.length && i < 6; i++) {
25
+ seed |= BigInt(values[i] & 0x3ff) << BigInt(i * 10)
26
+ }
27
+ return seed
28
+ }
29
+
30
+ export function decodeStat(seed: bigint, index: number): number {
31
+ return Number((seed >> BigInt(index * 10)) & 0x3ffn)
32
+ }
33
+
34
+ export function decodeStats(seed: bigint, count: number): number[] {
35
+ const stats: number[] = []
36
+ for (let i = 0; i < count; i++) {
37
+ stats.push(decodeStat(seed, i))
38
+ }
39
+ return stats
40
+ }
41
+
42
+ function mapStatsToKeys(seed: bigint, statDefs: {key: string}[]): Record<string, number> {
43
+ const values = decodeStats(seed, statDefs.length)
44
+ const result: Record<string, number> = {}
45
+ for (let i = 0; i < statDefs.length; i++) {
46
+ result[statDefs[i].key] = values[i]
47
+ }
48
+ return result
49
+ }
50
+
51
+ export function decodeCraftedItemStats(itemId: number, seed: bigint): Record<string, number> {
52
+ const comp = getComponentById(itemId)
53
+ if (comp) return mapStatsToKeys(seed, comp.stats)
54
+
55
+ const entityRecipe = entityRecipes.find((r) => r.packedItemId === itemId)
56
+ if (entityRecipe) return mapStatsToKeys(seed, entityRecipe.stats)
57
+
58
+ const moduleRecipe = moduleRecipes.find((r) => r.itemId === itemId)
59
+ if (moduleRecipe) return mapStatsToKeys(seed, moduleRecipe.stats)
60
+
61
+ return {}
62
+ }
63
+
64
+ export function blendStacks(stacks: StackInput[], statKey: string): number {
65
+ let totalQty = 0
66
+ let weightedSum = 0
67
+ for (const stack of stacks) {
68
+ const val = stack.stats[statKey] ?? 0
69
+ weightedSum += val * stack.quantity
70
+ totalQty += stack.quantity
71
+ }
72
+ if (totalQty === 0) return 0
73
+ return Math.floor(weightedSum / totalQty)
74
+ }
75
+
76
+ export function computeComponentStats(
77
+ componentId: number,
78
+ categoryStacks: CategoryStacks[]
79
+ ): {key: string; value: number}[] {
80
+ const comp = getComponentById(componentId)
81
+ if (!comp) return []
82
+
83
+ return comp.stats.map((statDef) => {
84
+ const matching = categoryStacks.find((cs) => cs.category === statDef.source)
85
+ const value = matching ? blendStacks(matching.stacks, statDef.key) : 0
86
+ return {key: statDef.key, value: Math.max(1, Math.min(999, value))}
87
+ })
88
+ }
89
+
90
+ export function blendComponentStacks(
91
+ stacks: {quantity: number; stats: Record<string, number>}[]
92
+ ): Record<string, number> {
93
+ if (stacks.length === 0) return {}
94
+ const allKeys = new Set<string>()
95
+ for (const s of stacks) {
96
+ for (const k of Object.keys(s.stats)) allKeys.add(k)
97
+ }
98
+ const result: Record<string, number> = {}
99
+ for (const key of allKeys) {
100
+ result[key] = blendStacks(
101
+ stacks.map((s) => ({quantity: s.quantity, stats: s.stats})),
102
+ key
103
+ )
104
+ }
105
+ return result
106
+ }
107
+
108
+ export function computeEntityStats(
109
+ entityRecipeId: string,
110
+ componentStacks: Record<number, {quantity: number; stats: Record<string, number>}[]>
111
+ ): {key: string; value: number}[] {
112
+ const recipe = getEntityRecipe(entityRecipeId) ?? getModuleRecipe(entityRecipeId)
113
+ if (!recipe) return []
114
+
115
+ const blendedByComponent: Record<number, Record<string, number>> = {}
116
+ for (const [compId, stacks] of Object.entries(componentStacks)) {
117
+ blendedByComponent[Number(compId)] = blendComponentStacks(stacks)
118
+ }
119
+
120
+ return recipe.stats.map((stat) => {
121
+ const blended = blendedByComponent[stat.sourceComponentId] ?? {}
122
+ const value = blended[stat.sourceStatKey] ?? 0
123
+ return {key: stat.key, value: Math.max(1, Math.min(999, value))}
124
+ })
125
+ }
126
+
127
+ function decodeStackStats(itemId: number, seed: UInt64): Record<string, number> {
128
+ if (itemId >= 10000) {
129
+ return decodeCraftedItemStats(itemId, BigInt(seed.toString()))
130
+ }
131
+ const raw = deriveResourceStats(BigInt(seed.toString()))
132
+ return {stat1: raw.stat1, stat2: raw.stat2, stat3: raw.stat3}
133
+ }
134
+
135
+ export const categoryItemMass: Record<string, number> = {
136
+ metal: 30000,
137
+ precious: 40000,
138
+ gas: 15000,
139
+ mineral: 22000,
140
+ organic: 15000,
141
+ }
142
+
143
+ export function computeInputMass(
144
+ itemId: string | number,
145
+ itemType: 'component' | 'module' | 'entity'
146
+ ): number {
147
+ if (itemType === 'component') {
148
+ const comp = getComponentById(itemId as number)
149
+ if (!comp) return 0
150
+ return comp.recipe.reduce((sum, input) => {
151
+ const mass = input.category ? categoryItemMass[input.category] ?? 0 : 0
152
+ return sum + mass * input.quantity
153
+ }, 0)
154
+ }
155
+ if (itemType === 'module') {
156
+ const mod = getModuleRecipe(itemId as string)
157
+ if (!mod) return 0
158
+ return mod.recipe.reduce((sum, input) => {
159
+ const comp = input.itemId ? getComponentById(input.itemId) : undefined
160
+ return sum + (comp?.mass ?? 0) * input.quantity
161
+ }, 0)
162
+ }
163
+ if (itemType === 'entity') {
164
+ const ent = getEntityRecipe(itemId as string)
165
+ if (!ent) return 0
166
+ return ent.recipe.reduce((sum, input) => {
167
+ const comp = input.itemId ? getComponentById(input.itemId) : undefined
168
+ return sum + (comp?.mass ?? 0) * input.quantity
169
+ }, 0)
170
+ }
171
+ return 0
172
+ }
173
+
174
+ export function blendCrossGroup(sources: {value: number; weight: number}[]): number {
175
+ let weightedSum = 0
176
+ let totalWeight = 0
177
+ for (const src of sources) {
178
+ weightedSum += src.value * src.weight
179
+ totalWeight += src.weight
180
+ }
181
+ if (totalWeight === 0) return 1
182
+ const result = Math.floor(weightedSum / totalWeight)
183
+ return Math.max(1, Math.min(999, result))
184
+ }
185
+
186
+ export function blendCargoStacks(
187
+ itemId: number,
188
+ stacks: {quantity: number; seed: UInt64}[]
189
+ ): UInt64 {
190
+ const decoded = stacks.map((s) => ({
191
+ quantity: s.quantity,
192
+ stats: decodeStackStats(itemId, s.seed),
193
+ }))
194
+ const allKeys = Object.keys(decoded[0]?.stats ?? {})
195
+ const blended = allKeys.map((key) => Math.max(1, Math.min(999, blendStacks(decoded, key))))
196
+ return UInt64.from(encodeStats(blended))
197
+ }
@@ -0,0 +1,28 @@
1
+ export {deriveStratum, deriveResourceStats} from './stratum'
2
+ export type {StratumInfo, ResourceStats} from './stratum'
3
+ export {deriveLocationSize} from './location-size'
4
+ export {
5
+ getEligibleResources,
6
+ getResourceWeight,
7
+ getLocationCandidates,
8
+ getDepthThreshold,
9
+ getResourceTier,
10
+ depthScaleFactor,
11
+ DEPTH_THRESHOLD_T1,
12
+ DEPTH_THRESHOLD_T2,
13
+ DEPTH_THRESHOLD_T3,
14
+ DEPTH_THRESHOLD_T4,
15
+ DEPTH_THRESHOLD_T5,
16
+ LOCATION_MIN_DEPTH,
17
+ LOCATION_MAX_DEPTH,
18
+ YIELD_THRESHOLD,
19
+ PLANET_SUBTYPE_GAS_GIANT,
20
+ PLANET_SUBTYPE_ROCKY,
21
+ PLANET_SUBTYPE_TERRESTRIAL,
22
+ PLANET_SUBTYPE_ICY,
23
+ PLANET_SUBTYPE_OCEAN,
24
+ PLANET_SUBTYPE_INDUSTRIAL,
25
+ } from './resources'
26
+
27
+ export * from './stats'
28
+ export * from './crafting'
@@ -0,0 +1,15 @@
1
+ import {ServerContract} from '../contracts'
2
+ import {LocationType} from '../types'
3
+ import {LOCATION_MAX_DEPTH, LOCATION_MIN_DEPTH} from './resources'
4
+
5
+ export function deriveLocationSize(loc: ServerContract.Types.location_static): number {
6
+ if (loc.type.toNumber() === LocationType.EMPTY) return 0
7
+
8
+ const raw = (loc.seed0.toNumber() << 8) | loc.seed1.toNumber()
9
+ const normalized = raw / 65535
10
+
11
+ const curved = Math.pow(normalized, 3.0)
12
+
13
+ const range = LOCATION_MAX_DEPTH - LOCATION_MIN_DEPTH
14
+ return Math.floor(LOCATION_MIN_DEPTH + curved * range)
15
+ }
@@ -0,0 +1,142 @@
1
+ import {ResourceTier} from '../types'
2
+
3
+ export const DEPTH_THRESHOLD_T1 = 0
4
+ export const DEPTH_THRESHOLD_T2 = 2000
5
+ export const DEPTH_THRESHOLD_T3 = 10000
6
+ export const DEPTH_THRESHOLD_T4 = 30000
7
+ export const DEPTH_THRESHOLD_T5 = 55000
8
+
9
+ export const LOCATION_MIN_DEPTH = 500
10
+ export const LOCATION_MAX_DEPTH = 65535
11
+
12
+ export const YIELD_THRESHOLD = Math.floor(0.003 * 0xffffffff)
13
+
14
+ export const PLANET_SUBTYPE_GAS_GIANT = 0
15
+ export const PLANET_SUBTYPE_ROCKY = 1
16
+ export const PLANET_SUBTYPE_TERRESTRIAL = 2
17
+ export const PLANET_SUBTYPE_ICY = 3
18
+ export const PLANET_SUBTYPE_OCEAN = 4
19
+ export const PLANET_SUBTYPE_INDUSTRIAL = 5
20
+
21
+ interface ResourceEntry {
22
+ id: number
23
+ tier: ResourceTier
24
+ }
25
+
26
+ const RESOURCE_CATALOG: ResourceEntry[] = [
27
+ {id: 26, tier: 't1'},
28
+ {id: 13, tier: 't2'},
29
+ {id: 24, tier: 't3'},
30
+ {id: 29, tier: 't1'},
31
+ {id: 47, tier: 't2'},
32
+ {id: 79, tier: 't3'},
33
+ {id: 1, tier: 't1'},
34
+ {id: 2, tier: 't2'},
35
+ {id: 18, tier: 't3'},
36
+ {id: 14, tier: 't1'},
37
+ {id: 1000, tier: 't2'},
38
+ {id: 1001, tier: 't3'},
39
+ {id: 6, tier: 't1'},
40
+ {id: 1003, tier: 't2'},
41
+ {id: 1002, tier: 't3'},
42
+ ]
43
+
44
+ export function getDepthThreshold(tier: ResourceTier): number {
45
+ switch (tier) {
46
+ case 't1':
47
+ return DEPTH_THRESHOLD_T1
48
+ case 't2':
49
+ return DEPTH_THRESHOLD_T2
50
+ case 't3':
51
+ return DEPTH_THRESHOLD_T3
52
+ case 't4':
53
+ return DEPTH_THRESHOLD_T4
54
+ case 't5':
55
+ return DEPTH_THRESHOLD_T5
56
+ }
57
+ }
58
+
59
+ export function getResourceTier(itemId: number): ResourceTier {
60
+ const entry = RESOURCE_CATALOG.find((r) => r.id === itemId)
61
+ return entry ? entry.tier : 't5'
62
+ }
63
+
64
+ export function getResourceWeight(itemId: number, stratum: number): number {
65
+ const tier = getResourceTier(itemId)
66
+ const threshold = getDepthThreshold(tier)
67
+ if (stratum < threshold) return 0
68
+
69
+ const depthAbove = stratum - threshold
70
+
71
+ switch (tier) {
72
+ case 't1':
73
+ if (stratum < 2000) return 100
74
+ if (stratum < 10000) return 80
75
+ if (stratum < 30000) return 50
76
+ return 30
77
+ case 't2':
78
+ if (depthAbove < 3000) return 40
79
+ if (depthAbove < 8000) return 60
80
+ return 50
81
+ case 't3':
82
+ if (depthAbove < 5000) return 20
83
+ if (depthAbove < 15000) return 35
84
+ return 40
85
+ case 't4':
86
+ if (depthAbove < 10000) return 10
87
+ if (depthAbove < 25000) return 20
88
+ return 30
89
+ case 't5':
90
+ return 10
91
+ }
92
+ }
93
+
94
+ const ASTEROID_RESOURCES = [26, 13, 24, 29, 47]
95
+ const NEBULA_RESOURCES = [47, 79, 1, 2, 18]
96
+ const GAS_GIANT_RESOURCES = [1, 2, 18, 14, 6]
97
+ const ROCKY_RESOURCES = [26, 13, 24, 14, 1000, 1001, 1002]
98
+ const TERRESTRIAL_RESOURCES = [29, 47, 14, 1000, 6, 1003, 1002]
99
+ const ICY_RESOURCES = [26, 1, 2, 14, 1001, 6, 1003]
100
+ const OCEAN_RESOURCES = [29, 79, 1, 18, 6, 1003, 1002]
101
+ const INDUSTRIAL_RESOURCES = [26, 13, 24, 29, 79, 1000, 1001]
102
+
103
+ export function getLocationCandidates(locationType: number, subtype: number): number[] {
104
+ if (locationType === 2) return ASTEROID_RESOURCES
105
+ if (locationType === 3) return NEBULA_RESOURCES
106
+ if (locationType === 1) {
107
+ switch (subtype) {
108
+ case PLANET_SUBTYPE_GAS_GIANT:
109
+ return GAS_GIANT_RESOURCES
110
+ case PLANET_SUBTYPE_ROCKY:
111
+ return ROCKY_RESOURCES
112
+ case PLANET_SUBTYPE_TERRESTRIAL:
113
+ return TERRESTRIAL_RESOURCES
114
+ case PLANET_SUBTYPE_ICY:
115
+ return ICY_RESOURCES
116
+ case PLANET_SUBTYPE_OCEAN:
117
+ return OCEAN_RESOURCES
118
+ case PLANET_SUBTYPE_INDUSTRIAL:
119
+ return INDUSTRIAL_RESOURCES
120
+ }
121
+ }
122
+ return []
123
+ }
124
+
125
+ export function getEligibleResources(
126
+ locationType: number,
127
+ subtype: number,
128
+ stratum: number
129
+ ): number[] {
130
+ const candidates = getLocationCandidates(locationType, subtype)
131
+ return candidates.filter((itemId) => {
132
+ const tier = getResourceTier(itemId)
133
+ const threshold = getDepthThreshold(tier)
134
+ return stratum >= threshold
135
+ })
136
+ }
137
+
138
+ export function depthScaleFactor(stratum: number): number {
139
+ if (stratum <= 1) return 1.0
140
+ const logScale = Math.log(stratum) / Math.log(65535)
141
+ return 1.0 + logScale * 2.0
142
+ }
@@ -0,0 +1,146 @@
1
+ import type {ResourceCategory} from '../types'
2
+
3
+ export interface StatDefinition {
4
+ key: string
5
+ label: string
6
+ abbreviation: string
7
+ purpose: string
8
+ inverted?: boolean
9
+ }
10
+
11
+ const METAL_STATS: StatDefinition[] = [
12
+ {
13
+ key: 'strength',
14
+ label: 'Strength',
15
+ abbreviation: 'STR',
16
+ purpose: 'Raw structural/mechanical force',
17
+ },
18
+ {
19
+ key: 'tolerance',
20
+ label: 'Tolerance',
21
+ abbreviation: 'TOL',
22
+ purpose: 'Ability to withstand heat, pressure, and stress extremes',
23
+ },
24
+ {
25
+ key: 'density',
26
+ label: 'Density',
27
+ abbreviation: 'DEN',
28
+ purpose: 'Mass per unit',
29
+ inverted: true,
30
+ },
31
+ ]
32
+
33
+ const PRECIOUS_STATS: StatDefinition[] = [
34
+ {
35
+ key: 'conductivity',
36
+ label: 'Conductivity',
37
+ abbreviation: 'CON',
38
+ purpose: 'Efficiency of energy/signal transfer',
39
+ },
40
+ {
41
+ key: 'ductility',
42
+ label: 'Ductility',
43
+ abbreviation: 'DUC',
44
+ purpose: 'Ability to be worked into fine, precise shapes',
45
+ },
46
+ {
47
+ key: 'reflectivity',
48
+ label: 'Reflectivity',
49
+ abbreviation: 'REF',
50
+ purpose: 'Surface quality for heat management and precision optics',
51
+ },
52
+ ]
53
+
54
+ const GAS_STATS: StatDefinition[] = [
55
+ {
56
+ key: 'volatility',
57
+ label: 'Volatility',
58
+ abbreviation: 'VOL',
59
+ purpose: 'Energy release potential for propulsion and force',
60
+ },
61
+ {
62
+ key: 'reactivity',
63
+ label: 'Reactivity',
64
+ abbreviation: 'REA',
65
+ purpose: 'Chemical interaction speed for processing and penetration',
66
+ },
67
+ {
68
+ key: 'thermal',
69
+ label: 'Thermal',
70
+ abbreviation: 'THM',
71
+ purpose: 'Heat capacity for thermal management',
72
+ },
73
+ ]
74
+
75
+ const MINERAL_STATS: StatDefinition[] = [
76
+ {
77
+ key: 'resonance',
78
+ label: 'Resonance',
79
+ abbreviation: 'RES',
80
+ purpose: 'Energy field interaction — storage, focusing, projection',
81
+ },
82
+ {
83
+ key: 'hardness',
84
+ label: 'Hardness',
85
+ abbreviation: 'HRD',
86
+ purpose: 'Resistance to wear — cutting surfaces, penetration',
87
+ },
88
+ {
89
+ key: 'clarity',
90
+ label: 'Clarity',
91
+ abbreviation: 'CLR',
92
+ purpose: 'Crystalline perfection — precision optics',
93
+ },
94
+ ]
95
+
96
+ const ORGANIC_STATS: StatDefinition[] = [
97
+ {
98
+ key: 'plasticity',
99
+ label: 'Plasticity',
100
+ abbreviation: 'PLA',
101
+ purpose: 'Ease of reshaping — speeds processing',
102
+ },
103
+ {
104
+ key: 'insulation',
105
+ label: 'Insulation',
106
+ abbreviation: 'INS',
107
+ purpose: 'Energy containment — reduces energy loss',
108
+ },
109
+ {
110
+ key: 'purity',
111
+ label: 'Purity',
112
+ abbreviation: 'PUR',
113
+ purpose: 'Biological cleanliness — better composites and lubricants',
114
+ },
115
+ ]
116
+
117
+ const STAT_MAP: Record<ResourceCategory, StatDefinition[]> = {
118
+ metal: METAL_STATS,
119
+ precious: PRECIOUS_STATS,
120
+ gas: GAS_STATS,
121
+ mineral: MINERAL_STATS,
122
+ organic: ORGANIC_STATS,
123
+ }
124
+
125
+ export function getStatDefinitions(category: ResourceCategory): StatDefinition[] {
126
+ return STAT_MAP[category]
127
+ }
128
+
129
+ export function getStatName(category: ResourceCategory, index: 0 | 1 | 2): StatDefinition {
130
+ return STAT_MAP[category][index]
131
+ }
132
+
133
+ export interface NamedStats {
134
+ definitions: StatDefinition[]
135
+ values: [number, number, number]
136
+ }
137
+
138
+ export function resolveStats(
139
+ category: ResourceCategory,
140
+ stats: {stat1: number; stat2: number; stat3: number}
141
+ ): NamedStats {
142
+ return {
143
+ definitions: STAT_MAP[category],
144
+ values: [stats.stat1, stats.stat2, stats.stat3],
145
+ }
146
+ }
@@ -0,0 +1,124 @@
1
+ import {Bytes, Checksum256, Checksum256Type} from '@wharfkit/antelope'
2
+ import {hash512} from '../utils/hash'
3
+ import {Coordinates, CoordinatesType} from '../types'
4
+ import {
5
+ depthScaleFactor,
6
+ getEligibleResources,
7
+ getResourceWeight,
8
+ YIELD_THRESHOLD,
9
+ } from './resources'
10
+
11
+ export interface StratumInfo {
12
+ itemId: number
13
+ seed: bigint
14
+ richness: number
15
+ reserve: number
16
+ }
17
+
18
+ export interface ResourceStats {
19
+ stat1: number
20
+ stat2: number
21
+ stat3: number
22
+ }
23
+
24
+ export function deriveStratum(
25
+ epochSeed: Checksum256Type,
26
+ coords: CoordinatesType,
27
+ stratum: number,
28
+ locationType: number,
29
+ subtype: number,
30
+ _maxDepth: number
31
+ ): StratumInfo {
32
+ const seed = Checksum256.from(epochSeed)
33
+ const c = Coordinates.from(coords)
34
+ const input = `stratum-${c.x}-${c.y}-${stratum}`
35
+ const hashResult = hash512(seed, input)
36
+ const bytes = hashResult.array
37
+
38
+ const rawReserve = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0
39
+
40
+ let reserve = 0
41
+ if (rawReserve <= YIELD_THRESHOLD) {
42
+ const baseReserve = (rawReserve % 333) + 1
43
+ const scale = depthScaleFactor(stratum)
44
+ reserve = Math.floor(baseReserve * scale)
45
+ }
46
+
47
+ if (reserve === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
48
+
49
+ const eligible = getEligibleResources(locationType, subtype, stratum)
50
+ if (eligible.length === 0) return {itemId: 0, seed: 0n, richness: 0, reserve: 0}
51
+
52
+ const resourceRoll = ((bytes[4] << 24) | (bytes[5] << 16) | (bytes[6] << 8) | bytes[7]) >>> 0
53
+
54
+ let totalWeight = 0
55
+ for (const id of eligible) {
56
+ totalWeight += getResourceWeight(id, stratum)
57
+ }
58
+
59
+ let selectedItemId = eligible[0]
60
+ if (totalWeight > 0) {
61
+ const roll = resourceRoll % totalWeight
62
+ let cumulative = 0
63
+ for (const id of eligible) {
64
+ cumulative += getResourceWeight(id, stratum)
65
+ if (roll < cumulative) {
66
+ selectedItemId = id
67
+ break
68
+ }
69
+ }
70
+ }
71
+
72
+ const seedBigInt =
73
+ (BigInt(bytes[8]) << 56n) |
74
+ (BigInt(bytes[9]) << 48n) |
75
+ (BigInt(bytes[10]) << 40n) |
76
+ (BigInt(bytes[11]) << 32n) |
77
+ (BigInt(bytes[12]) << 24n) |
78
+ (BigInt(bytes[13]) << 16n) |
79
+ (BigInt(bytes[14]) << 8n) |
80
+ BigInt(bytes[15])
81
+
82
+ const rawRichness = (bytes[16] << 8) | bytes[17]
83
+ const normalized = rawRichness / 65535
84
+ const baseRichness = Math.floor(normalized * normalized * 999) + 1
85
+
86
+ let depthBonus = 0
87
+ if (stratum > 1) {
88
+ depthBonus = (50 * Math.log(stratum)) / Math.log(65535)
89
+ }
90
+ const richness = Math.min(Math.floor(baseRichness + depthBonus), 1000)
91
+
92
+ return {itemId: selectedItemId, seed: seedBigInt, richness, reserve}
93
+ }
94
+
95
+ export function deriveResourceStats(seed: bigint): ResourceStats {
96
+ const seedBytes = new Uint8Array(8)
97
+ for (let i = 0; i < 8; i++) {
98
+ seedBytes[i] = Number(seed & 0xffn)
99
+ seed >>= 8n
100
+ }
101
+ const hashResult = Checksum256.hash(Bytes.from(seedBytes))
102
+ const hashBytes = hashResult.array
103
+
104
+ const extractU32 = (offset: number): number =>
105
+ (hashBytes[offset] * 0x1000000 +
106
+ (hashBytes[offset + 1] << 16) +
107
+ (hashBytes[offset + 2] << 8) +
108
+ hashBytes[offset + 3]) >>> 0
109
+
110
+ const weibull = (raw: number): number => {
111
+ const u = raw / 4294967296
112
+ let x = 0.27 * Math.sqrt(-Math.log(1 - u))
113
+ if (x > 1) x = 1
114
+ let val = Math.floor(x * 999) + 1
115
+ if (val > 999) val = 999
116
+ return val
117
+ }
118
+
119
+ return {
120
+ stat1: weibull(extractU32(0)),
121
+ stat2: weibull(extractU32(4)),
122
+ stat3: weibull(extractU32(8)),
123
+ }
124
+ }