@shipload/sdk 1.0.0-next.35 → 1.0.0-next.36
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.
- package/lib/shipload.d.ts +237 -80
- package/lib/shipload.js +2979 -2598
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +2960 -2599
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +66 -20
- package/lib/testing.js +95 -57
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +95 -57
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/crafting.ts +2 -3
- package/src/capabilities/gathering.test.ts +16 -0
- package/src/capabilities/gathering.ts +8 -11
- package/src/contracts/server.ts +45 -29
- package/src/coordinates/address.ts +9 -5
- package/src/coordinates/constants.test.ts +15 -0
- package/src/coordinates/constants.ts +5 -3
- package/src/coordinates/index.ts +11 -0
- package/src/coordinates/memo.test.ts +47 -0
- package/src/coordinates/memo.ts +20 -0
- package/src/data/capability-formulas.ts +0 -1
- package/src/data/entities.json +4 -4
- package/src/data/items.json +5 -5
- package/src/data/recipes.json +39 -65
- package/src/derivation/capabilities.test.ts +133 -0
- package/src/derivation/capabilities.ts +66 -14
- package/src/derivation/rollups.test.ts +55 -0
- package/src/derivation/rollups.ts +56 -0
- package/src/entities/makers.ts +30 -3
- package/src/index-module.ts +15 -2
- package/src/managers/actions.ts +34 -3
- package/src/managers/construction.ts +6 -4
- package/src/managers/context.ts +9 -0
- package/src/managers/coordinates.ts +14 -0
- package/src/managers/plot.ts +2 -4
- package/src/nft/description.ts +25 -6
- package/src/planner/index.ts +127 -0
- package/src/planner/planner.test.ts +319 -0
- package/src/resolution/resolve-item.ts +4 -1
- package/src/scheduling/cancel.test.ts +21 -0
- package/src/scheduling/lanes.test.ts +249 -0
- package/src/scheduling/lanes.ts +140 -2
- package/src/scheduling/projection.ts +73 -16
- package/src/shipload.ts +5 -0
- package/src/testing/projection-parity.ts +26 -2
- package/src/travel/travel.ts +102 -101
- package/src/types/capabilities.ts +23 -6
- package/src/types/entity.ts +3 -3
- package/src/types.ts +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type {Checksum256Type} from '@wharfkit/antelope'
|
|
2
|
+
import {Checksum256} from '@wharfkit/antelope'
|
|
3
|
+
import {type CoordinateAddress, encodeAddress} from './address'
|
|
4
|
+
|
|
5
|
+
const cache = new Map<string, CoordinateAddress>()
|
|
6
|
+
const CACHE_MAX = 4096
|
|
7
|
+
|
|
8
|
+
export function encodeAddressMemo(seed: Checksum256Type, x: number, y: number): CoordinateAddress {
|
|
9
|
+
const key = `${Checksum256.from(seed).toString()}:${x},${y}`
|
|
10
|
+
let hit = cache.get(key)
|
|
11
|
+
if (!hit) {
|
|
12
|
+
hit = encodeAddress(seed, x, y)
|
|
13
|
+
if (cache.size >= CACHE_MAX) {
|
|
14
|
+
const oldest = cache.keys().next().value
|
|
15
|
+
if (oldest !== undefined) cache.delete(oldest)
|
|
16
|
+
}
|
|
17
|
+
cache.set(key, hit)
|
|
18
|
+
}
|
|
19
|
+
return hit
|
|
20
|
+
}
|
|
@@ -23,7 +23,6 @@ const ENTITY_HULL_SLOTS: Record<number, SlotConsumer> = {
|
|
|
23
23
|
0: {capability: 'Storage', attribute: 'capacity'},
|
|
24
24
|
1: {capability: 'Hull', attribute: 'mass'},
|
|
25
25
|
2: {capability: 'Storage', attribute: 'capacity'},
|
|
26
|
-
3: {capability: 'Storage', attribute: 'capacity'},
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
export const SLOT_FORMULAS: Record<SlotConsumerKind, Record<number, SlotConsumer>> = {
|
package/src/data/entities.json
CHANGED
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"slots": [
|
|
59
59
|
{
|
|
60
60
|
"type": "generator",
|
|
61
|
-
"outputPct":
|
|
61
|
+
"outputPct": 200
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
"type": "gatherer",
|
|
65
|
-
"outputPct":
|
|
65
|
+
"outputPct": 200
|
|
66
66
|
}
|
|
67
67
|
]
|
|
68
68
|
},
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
"slots": [
|
|
72
72
|
{
|
|
73
73
|
"type": "generator",
|
|
74
|
-
"outputPct":
|
|
74
|
+
"outputPct": 200
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
"type": "crafter",
|
|
78
|
-
"outputPct":
|
|
78
|
+
"outputPct": 200
|
|
79
79
|
}
|
|
80
80
|
]
|
|
81
81
|
},
|
package/src/data/items.json
CHANGED
|
@@ -474,31 +474,31 @@
|
|
|
474
474
|
},
|
|
475
475
|
{
|
|
476
476
|
"id": 10200,
|
|
477
|
-
"mass":
|
|
477
|
+
"mass": 1900000,
|
|
478
478
|
"type": "entity",
|
|
479
479
|
"tier": 1
|
|
480
480
|
},
|
|
481
481
|
{
|
|
482
482
|
"id": 10201,
|
|
483
|
-
"mass":
|
|
483
|
+
"mass": 2400000,
|
|
484
484
|
"type": "entity",
|
|
485
485
|
"tier": 1
|
|
486
486
|
},
|
|
487
487
|
{
|
|
488
488
|
"id": 10202,
|
|
489
|
-
"mass":
|
|
489
|
+
"mass": 3200000,
|
|
490
490
|
"type": "entity",
|
|
491
491
|
"tier": 1
|
|
492
492
|
},
|
|
493
493
|
{
|
|
494
494
|
"id": 10203,
|
|
495
|
-
"mass":
|
|
495
|
+
"mass": 1900000,
|
|
496
496
|
"type": "entity",
|
|
497
497
|
"tier": 1
|
|
498
498
|
},
|
|
499
499
|
{
|
|
500
500
|
"id": 10204,
|
|
501
|
-
"mass":
|
|
501
|
+
"mass": 1900000,
|
|
502
502
|
"type": "entity",
|
|
503
503
|
"tier": 1
|
|
504
504
|
},
|
package/src/data/recipes.json
CHANGED
|
@@ -651,15 +651,15 @@
|
|
|
651
651
|
},
|
|
652
652
|
{
|
|
653
653
|
"outputItemId": 10200,
|
|
654
|
-
"outputMass":
|
|
654
|
+
"outputMass": 1900000,
|
|
655
655
|
"inputs": [
|
|
656
656
|
{
|
|
657
657
|
"itemId": 10001,
|
|
658
658
|
"quantity": 600
|
|
659
659
|
},
|
|
660
660
|
{
|
|
661
|
-
"itemId":
|
|
662
|
-
"quantity":
|
|
661
|
+
"itemId": 10008,
|
|
662
|
+
"quantity": 600
|
|
663
663
|
}
|
|
664
664
|
],
|
|
665
665
|
"statSlots": [
|
|
@@ -700,15 +700,27 @@
|
|
|
700
700
|
},
|
|
701
701
|
{
|
|
702
702
|
"outputItemId": 10201,
|
|
703
|
-
"outputMass":
|
|
703
|
+
"outputMass": 2400000,
|
|
704
704
|
"inputs": [
|
|
705
705
|
{
|
|
706
706
|
"itemId": 10001,
|
|
707
|
-
"quantity":
|
|
707
|
+
"quantity": 300
|
|
708
708
|
},
|
|
709
709
|
{
|
|
710
|
-
"itemId":
|
|
711
|
-
"quantity":
|
|
710
|
+
"itemId": 10008,
|
|
711
|
+
"quantity": 300
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
"itemId": 10007,
|
|
715
|
+
"quantity": 300
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
"itemId": 10003,
|
|
719
|
+
"quantity": 300
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
"itemId": 10006,
|
|
723
|
+
"quantity": 300
|
|
712
724
|
}
|
|
713
725
|
],
|
|
714
726
|
"statSlots": [
|
|
@@ -729,34 +741,24 @@
|
|
|
729
741
|
]
|
|
730
742
|
},
|
|
731
743
|
{
|
|
732
|
-
"sources": [
|
|
733
|
-
{
|
|
734
|
-
"inputIndex": 1,
|
|
735
|
-
"statIndex": 0
|
|
736
|
-
}
|
|
737
|
-
]
|
|
744
|
+
"sources": []
|
|
738
745
|
},
|
|
739
746
|
{
|
|
740
|
-
"sources": [
|
|
741
|
-
{
|
|
742
|
-
"inputIndex": 1,
|
|
743
|
-
"statIndex": 1
|
|
744
|
-
}
|
|
745
|
-
]
|
|
747
|
+
"sources": []
|
|
746
748
|
}
|
|
747
749
|
],
|
|
748
750
|
"blendWeights": []
|
|
749
751
|
},
|
|
750
752
|
{
|
|
751
753
|
"outputItemId": 10202,
|
|
752
|
-
"outputMass":
|
|
754
|
+
"outputMass": 3200000,
|
|
753
755
|
"inputs": [
|
|
754
756
|
{
|
|
755
757
|
"itemId": 10001,
|
|
756
|
-
"quantity":
|
|
758
|
+
"quantity": 1000
|
|
757
759
|
},
|
|
758
760
|
{
|
|
759
|
-
"itemId":
|
|
761
|
+
"itemId": 10008,
|
|
760
762
|
"quantity": 1000
|
|
761
763
|
}
|
|
762
764
|
],
|
|
@@ -798,25 +800,20 @@
|
|
|
798
800
|
},
|
|
799
801
|
{
|
|
800
802
|
"outputItemId": 10203,
|
|
801
|
-
"outputMass":
|
|
803
|
+
"outputMass": 1900000,
|
|
802
804
|
"inputs": [
|
|
803
805
|
{
|
|
804
|
-
"itemId":
|
|
805
|
-
"quantity":
|
|
806
|
+
"itemId": 10008,
|
|
807
|
+
"quantity": 600
|
|
806
808
|
},
|
|
807
809
|
{
|
|
808
|
-
"itemId":
|
|
809
|
-
"quantity":
|
|
810
|
+
"itemId": 10006,
|
|
811
|
+
"quantity": 600
|
|
810
812
|
}
|
|
811
813
|
],
|
|
812
814
|
"statSlots": [
|
|
813
815
|
{
|
|
814
|
-
"sources": [
|
|
815
|
-
{
|
|
816
|
-
"inputIndex": 0,
|
|
817
|
-
"statIndex": 0
|
|
818
|
-
}
|
|
819
|
-
]
|
|
816
|
+
"sources": []
|
|
820
817
|
},
|
|
821
818
|
{
|
|
822
819
|
"sources": [
|
|
@@ -829,51 +826,33 @@
|
|
|
829
826
|
{
|
|
830
827
|
"sources": [
|
|
831
828
|
{
|
|
832
|
-
"inputIndex":
|
|
829
|
+
"inputIndex": 0,
|
|
833
830
|
"statIndex": 0
|
|
834
831
|
}
|
|
835
832
|
]
|
|
836
833
|
},
|
|
837
834
|
{
|
|
838
|
-
"sources": [
|
|
839
|
-
{
|
|
840
|
-
"inputIndex": 1,
|
|
841
|
-
"statIndex": 1
|
|
842
|
-
}
|
|
843
|
-
]
|
|
835
|
+
"sources": []
|
|
844
836
|
}
|
|
845
837
|
],
|
|
846
838
|
"blendWeights": []
|
|
847
839
|
},
|
|
848
840
|
{
|
|
849
841
|
"outputItemId": 10204,
|
|
850
|
-
"outputMass":
|
|
842
|
+
"outputMass": 1900000,
|
|
851
843
|
"inputs": [
|
|
852
|
-
{
|
|
853
|
-
"itemId": 10001,
|
|
854
|
-
"quantity": 1500
|
|
855
|
-
},
|
|
856
|
-
{
|
|
857
|
-
"itemId": 10002,
|
|
858
|
-
"quantity": 600
|
|
859
|
-
},
|
|
860
844
|
{
|
|
861
845
|
"itemId": 10008,
|
|
862
|
-
"quantity":
|
|
846
|
+
"quantity": 600
|
|
863
847
|
},
|
|
864
848
|
{
|
|
865
|
-
"itemId":
|
|
866
|
-
"quantity":
|
|
849
|
+
"itemId": 10007,
|
|
850
|
+
"quantity": 600
|
|
867
851
|
}
|
|
868
852
|
],
|
|
869
853
|
"statSlots": [
|
|
870
854
|
{
|
|
871
|
-
"sources": [
|
|
872
|
-
{
|
|
873
|
-
"inputIndex": 0,
|
|
874
|
-
"statIndex": 0
|
|
875
|
-
}
|
|
876
|
-
]
|
|
855
|
+
"sources": []
|
|
877
856
|
},
|
|
878
857
|
{
|
|
879
858
|
"sources": [
|
|
@@ -886,18 +865,13 @@
|
|
|
886
865
|
{
|
|
887
866
|
"sources": [
|
|
888
867
|
{
|
|
889
|
-
"inputIndex":
|
|
868
|
+
"inputIndex": 0,
|
|
890
869
|
"statIndex": 0
|
|
891
870
|
}
|
|
892
871
|
]
|
|
893
872
|
},
|
|
894
873
|
{
|
|
895
|
-
"sources": [
|
|
896
|
-
{
|
|
897
|
-
"inputIndex": 1,
|
|
898
|
-
"statIndex": 1
|
|
899
|
-
}
|
|
900
|
-
]
|
|
874
|
+
"sources": []
|
|
901
875
|
}
|
|
902
876
|
],
|
|
903
877
|
"blendWeights": []
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {expect, test} from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
computeEntityCapabilities,
|
|
4
|
+
computeGathererCapabilities,
|
|
5
|
+
computeCrafterCapabilities,
|
|
6
|
+
computeLoaderCapabilities,
|
|
7
|
+
} from './capabilities'
|
|
8
|
+
import {applySlotMultiplier, U16_MAX} from '../entities/slot-multiplier'
|
|
9
|
+
import {encodeStats} from './crafting'
|
|
10
|
+
import {
|
|
11
|
+
ITEM_EXTRACTOR_T1_PACKED,
|
|
12
|
+
ITEM_GATHERER_T1,
|
|
13
|
+
ITEM_CRAFTER_T1,
|
|
14
|
+
ITEM_LOADER_T1,
|
|
15
|
+
} from '../data/item-ids'
|
|
16
|
+
import type {InstalledModule} from '../entities/slot-multiplier'
|
|
17
|
+
import type {EntitySlot} from '../data/recipes-runtime'
|
|
18
|
+
|
|
19
|
+
function makeGathererStats(strength: number, tolerance: number, conductivity: number): bigint {
|
|
20
|
+
return encodeStats([strength, tolerance, conductivity, 0])
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeCrafterStats(reactivity: number, fineness: number): bigint {
|
|
24
|
+
return encodeStats([reactivity, fineness])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeLoaderStats(insulation: number, plasticity: number): bigint {
|
|
28
|
+
return encodeStats([insulation, plasticity])
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test('computeEntityCapabilities emits gathererLanes alongside legacy gatherer sum', () => {
|
|
32
|
+
// Two gatherers with distinct stats in separate slots, amp=100 for both
|
|
33
|
+
const gathStats1 = makeGathererStats(300, 200, 400)
|
|
34
|
+
const gathStats2 = makeGathererStats(500, 100, 300)
|
|
35
|
+
|
|
36
|
+
const modules: InstalledModule[] = [
|
|
37
|
+
{slotIndex: 0, itemId: ITEM_GATHERER_T1, stats: gathStats1},
|
|
38
|
+
{slotIndex: 1, itemId: ITEM_GATHERER_T1, stats: gathStats2},
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
const layout: EntitySlot[] = [
|
|
42
|
+
{type: 'gatherer', outputPct: 100},
|
|
43
|
+
{type: 'gatherer', outputPct: 100},
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
const result = computeEntityCapabilities({}, ITEM_EXTRACTOR_T1_PACKED, modules, layout)
|
|
47
|
+
|
|
48
|
+
// Lane lists must exist
|
|
49
|
+
expect(result.gathererLanes).toBeDefined()
|
|
50
|
+
expect(result.gathererLanes!.length).toBe(2)
|
|
51
|
+
|
|
52
|
+
// Each lane has the right slotIndex
|
|
53
|
+
expect(result.gathererLanes![0].slotIndex).toBe(0)
|
|
54
|
+
expect(result.gathererLanes![1].slotIndex).toBe(1)
|
|
55
|
+
|
|
56
|
+
// Yields are amp-scaled and distinct
|
|
57
|
+
const caps1 = computeGathererCapabilities({strength: 300, tolerance: 200, conductivity: 400}, 1)
|
|
58
|
+
const caps2 = computeGathererCapabilities({strength: 500, tolerance: 100, conductivity: 300}, 1)
|
|
59
|
+
const expectedYield1 = applySlotMultiplier(caps1.yield, 100)
|
|
60
|
+
const expectedYield2 = applySlotMultiplier(caps2.yield, 100)
|
|
61
|
+
expect(result.gathererLanes![0].yield).toBe(expectedYield1)
|
|
62
|
+
expect(result.gathererLanes![1].yield).toBe(expectedYield2)
|
|
63
|
+
expect(result.gathererLanes![0].yield).not.toBe(result.gathererLanes![1].yield)
|
|
64
|
+
|
|
65
|
+
// Unscaled per-module drain and depth carried verbatim from the compute helper
|
|
66
|
+
expect(result.gathererLanes![0].drain).toBe(caps1.drain)
|
|
67
|
+
expect(result.gathererLanes![1].drain).toBe(caps2.drain)
|
|
68
|
+
expect(result.gathererLanes![0].depth).toBe(caps1.depth)
|
|
69
|
+
expect(result.gathererLanes![1].depth).toBe(caps2.depth)
|
|
70
|
+
|
|
71
|
+
// outputPct reflects the slot amp
|
|
72
|
+
expect(result.gathererLanes![0].outputPct).toBe(100)
|
|
73
|
+
expect(result.gathererLanes![1].outputPct).toBe(100)
|
|
74
|
+
|
|
75
|
+
// Legacy sum still equals sum of both lane yields
|
|
76
|
+
expect(result.gatherer).toBeDefined()
|
|
77
|
+
expect(result.gatherer!.yield).toBe(expectedYield1 + expectedYield2)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('computeEntityCapabilities emits crafterLanes alongside legacy crafter sum', () => {
|
|
81
|
+
const crafterStats = makeCrafterStats(400, 300)
|
|
82
|
+
|
|
83
|
+
const modules: InstalledModule[] = [
|
|
84
|
+
{slotIndex: 0, itemId: ITEM_CRAFTER_T1, stats: crafterStats},
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
const layout: EntitySlot[] = [{type: 'crafter', outputPct: 120}]
|
|
88
|
+
|
|
89
|
+
const result = computeEntityCapabilities({}, ITEM_EXTRACTOR_T1_PACKED, modules, layout)
|
|
90
|
+
|
|
91
|
+
expect(result.crafterLanes).toBeDefined()
|
|
92
|
+
expect(result.crafterLanes!.length).toBe(1)
|
|
93
|
+
expect(result.crafterLanes![0].slotIndex).toBe(0)
|
|
94
|
+
|
|
95
|
+
const caps = computeCrafterCapabilities({reactivity: 400, fineness: 300})
|
|
96
|
+
const expectedSpeed = applySlotMultiplier(caps.speed, 120)
|
|
97
|
+
expect(result.crafterLanes![0].speed).toBe(expectedSpeed)
|
|
98
|
+
expect(result.crafterLanes![0].drain).toBe(caps.drain)
|
|
99
|
+
expect(result.crafterLanes![0].outputPct).toBe(120)
|
|
100
|
+
|
|
101
|
+
// Legacy crafter speed equals single-lane speed
|
|
102
|
+
expect(result.crafter).toBeDefined()
|
|
103
|
+
expect(result.crafter!.speed).toBe(expectedSpeed)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('computeEntityCapabilities emits loaderLanes alongside legacy loaders sum', () => {
|
|
107
|
+
const loaderStats = makeLoaderStats(600, 500)
|
|
108
|
+
|
|
109
|
+
const modules: InstalledModule[] = [{slotIndex: 0, itemId: ITEM_LOADER_T1, stats: loaderStats}]
|
|
110
|
+
|
|
111
|
+
const layout: EntitySlot[] = [{type: 'loader', outputPct: 80}]
|
|
112
|
+
|
|
113
|
+
const result = computeEntityCapabilities({}, ITEM_EXTRACTOR_T1_PACKED, modules, layout)
|
|
114
|
+
|
|
115
|
+
expect(result.loaderLanes).toBeDefined()
|
|
116
|
+
expect(result.loaderLanes!.length).toBe(1)
|
|
117
|
+
expect(result.loaderLanes![0].slotIndex).toBe(0)
|
|
118
|
+
|
|
119
|
+
const caps = computeLoaderCapabilities({insulation: 600, plasticity: 500})
|
|
120
|
+
// mass is unscaled (raw); thrust is amp-scaled
|
|
121
|
+
expect(result.loaderLanes![0].mass).toBe(caps.mass)
|
|
122
|
+
expect(result.loaderLanes![0].thrust).toBe(applySlotMultiplier(caps.thrust, 80))
|
|
123
|
+
expect(result.loaderLanes![0].outputPct).toBe(80)
|
|
124
|
+
|
|
125
|
+
// Legacy loaders.mass is total (same as single-lane raw mass here)
|
|
126
|
+
expect(result.loaders).toBeDefined()
|
|
127
|
+
expect(result.loaders!.mass).toBe(caps.mass)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('per-lane amp-scaled stats clamp to UInt16, matching the contract clamp_to_uint16', () => {
|
|
131
|
+
expect(applySlotMultiplier(60000, 200)).toBe(U16_MAX)
|
|
132
|
+
expect(applySlotMultiplier(1000, 150)).toBe(1500)
|
|
133
|
+
})
|
|
@@ -6,8 +6,8 @@ export function computeShipHullCapabilities(stats: Record<string, number>): {
|
|
|
6
6
|
hullmass: number
|
|
7
7
|
capacity: number
|
|
8
8
|
} {
|
|
9
|
-
const statSum = stats.strength + stats.hardness
|
|
10
|
-
const exponent = statSum /
|
|
9
|
+
const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
|
|
10
|
+
const exponent = statSum / 1998.0
|
|
11
11
|
return {
|
|
12
12
|
hullmass: computeBaseHullmass(stats),
|
|
13
13
|
capacity: Math.floor(5000000 * 6 ** exponent),
|
|
@@ -184,9 +184,9 @@ import type {EntitySlot} from '../data/recipes-runtime'
|
|
|
184
184
|
export function computeBaseCapacity(itemId: number, stats: Record<string, number>): number {
|
|
185
185
|
switch (itemId) {
|
|
186
186
|
case ITEM_SHIP_T1_PACKED:
|
|
187
|
+
return computeShipHullCapabilities(stats).capacity
|
|
187
188
|
case ITEM_EXTRACTOR_T1_PACKED:
|
|
188
189
|
case ITEM_FACTORY_T1_PACKED:
|
|
189
|
-
return computeShipHullCapabilities(stats).capacity
|
|
190
190
|
case ITEM_CONTAINER_T1_PACKED:
|
|
191
191
|
return computeContainerCapabilities(stats).capacity
|
|
192
192
|
case ITEM_WAREHOUSE_T1_PACKED:
|
|
@@ -209,22 +209,47 @@ export function computeWarehouseHullCapabilities(stats: Record<string, number>):
|
|
|
209
209
|
hullmass: number
|
|
210
210
|
capacity: number
|
|
211
211
|
} {
|
|
212
|
-
const statSum = stats.strength + stats.hardness
|
|
213
|
-
const exponent = statSum /
|
|
212
|
+
const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
|
|
213
|
+
const exponent = statSum / 1998.0
|
|
214
214
|
return {
|
|
215
215
|
hullmass: computeBaseHullmass(stats),
|
|
216
216
|
capacity: Math.floor(100000000 * 6 ** exponent),
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
export interface GathererLaneEntry {
|
|
221
|
+
slotIndex: number
|
|
222
|
+
yield: number
|
|
223
|
+
drain: number
|
|
224
|
+
depth: number
|
|
225
|
+
outputPct: number
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface CrafterLaneEntry {
|
|
229
|
+
slotIndex: number
|
|
230
|
+
speed: number
|
|
231
|
+
drain: number
|
|
232
|
+
outputPct: number
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface LoaderLaneEntry {
|
|
236
|
+
slotIndex: number
|
|
237
|
+
mass: number
|
|
238
|
+
thrust: number
|
|
239
|
+
outputPct: number
|
|
240
|
+
}
|
|
241
|
+
|
|
220
242
|
export interface ComputedCapabilities {
|
|
221
243
|
hullmass: number
|
|
222
244
|
capacity: number
|
|
223
245
|
engines?: {thrust: number; drain: number}
|
|
224
246
|
generator?: {capacity: number; recharge: number}
|
|
225
247
|
gatherer?: {yield: number; drain: number; depth: number}
|
|
248
|
+
gathererLanes?: GathererLaneEntry[]
|
|
226
249
|
loaders?: {mass: number; thrust: number; quantity: number}
|
|
250
|
+
loaderLanes?: LoaderLaneEntry[]
|
|
227
251
|
crafter?: {speed: number; drain: number}
|
|
252
|
+
crafterLanes?: CrafterLaneEntry[]
|
|
228
253
|
hauler?: {capacity: number; efficiency: number; drain: number}
|
|
229
254
|
warp?: {range: number}
|
|
230
255
|
}
|
|
@@ -272,6 +297,10 @@ export function computeEntityCapabilities(
|
|
|
272
297
|
let totalBatteryStatSum = 0
|
|
273
298
|
let batteryCount = 0
|
|
274
299
|
|
|
300
|
+
const gathererLanes: GathererLaneEntry[] = []
|
|
301
|
+
const crafterLanes: CrafterLaneEntry[] = []
|
|
302
|
+
const loaderLanes: LoaderLaneEntry[] = []
|
|
303
|
+
|
|
275
304
|
for (const mod of modules) {
|
|
276
305
|
const item = getItem(mod.itemId)
|
|
277
306
|
const modType = getModuleCapabilityType(mod.itemId)
|
|
@@ -293,23 +322,44 @@ export function computeEntityCapabilities(
|
|
|
293
322
|
hasGatherer = true
|
|
294
323
|
const tier = item.tier
|
|
295
324
|
const caps = computeGathererCapabilities(decodedStats, tier)
|
|
296
|
-
|
|
325
|
+
const scaledYield = applySlotMultiplier(caps.yield, amp)
|
|
326
|
+
totalGathYield += scaledYield
|
|
297
327
|
totalGathDrain += caps.drain
|
|
298
328
|
if (caps.depth > maxGathDepth) maxGathDepth = caps.depth
|
|
329
|
+
gathererLanes.push({
|
|
330
|
+
slotIndex: mod.slotIndex,
|
|
331
|
+
yield: scaledYield,
|
|
332
|
+
drain: caps.drain,
|
|
333
|
+
depth: caps.depth,
|
|
334
|
+
outputPct: amp,
|
|
335
|
+
})
|
|
299
336
|
} else if (modType === MODULE_LOADER) {
|
|
300
337
|
hasLoader = true
|
|
301
338
|
const caps = computeLoaderCapabilities(decodedStats)
|
|
302
339
|
totalLoaderMass += caps.mass
|
|
303
340
|
totalLoaderThrust += applySlotMultiplier(caps.thrust, amp)
|
|
304
341
|
totalLoaderQuantity += caps.quantity
|
|
342
|
+
loaderLanes.push({
|
|
343
|
+
slotIndex: mod.slotIndex,
|
|
344
|
+
mass: caps.mass,
|
|
345
|
+
thrust: applySlotMultiplier(caps.thrust, amp),
|
|
346
|
+
outputPct: amp,
|
|
347
|
+
})
|
|
305
348
|
} else if (modType === MODULE_STORAGE) {
|
|
306
349
|
const caps = computeStorageCapabilities(decodedStats, baseCapacity)
|
|
307
350
|
totalStorageBonus += caps.capacityBonus
|
|
308
351
|
} else if (modType === MODULE_CRAFTER) {
|
|
309
352
|
hasCrafter = true
|
|
310
353
|
const caps = computeCrafterCapabilities(decodedStats)
|
|
311
|
-
|
|
354
|
+
const scaledSpeed = applySlotMultiplier(caps.speed, amp)
|
|
355
|
+
totalCrafterSpeed += scaledSpeed
|
|
312
356
|
totalCrafterDrain += caps.drain
|
|
357
|
+
crafterLanes.push({
|
|
358
|
+
slotIndex: mod.slotIndex,
|
|
359
|
+
speed: scaledSpeed,
|
|
360
|
+
drain: caps.drain,
|
|
361
|
+
outputPct: amp,
|
|
362
|
+
})
|
|
313
363
|
} else if (modType === MODULE_HAULER) {
|
|
314
364
|
hasHauler = true
|
|
315
365
|
const caps = computeHaulerCapabilities(decodedStats)
|
|
@@ -357,6 +407,7 @@ export function computeEntityCapabilities(
|
|
|
357
407
|
drain: totalGathDrain,
|
|
358
408
|
depth: maxGathDepth,
|
|
359
409
|
}
|
|
410
|
+
result.gathererLanes = gathererLanes
|
|
360
411
|
}
|
|
361
412
|
if (hasLoader) {
|
|
362
413
|
result.loaders = {
|
|
@@ -364,9 +415,11 @@ export function computeEntityCapabilities(
|
|
|
364
415
|
thrust: clampUint16(totalLoaderThrust),
|
|
365
416
|
quantity: totalLoaderQuantity,
|
|
366
417
|
}
|
|
418
|
+
result.loaderLanes = loaderLanes
|
|
367
419
|
}
|
|
368
420
|
if (hasCrafter) {
|
|
369
421
|
result.crafter = {speed: clampUint16(totalCrafterSpeed), drain: totalCrafterDrain}
|
|
422
|
+
result.crafterLanes = crafterLanes
|
|
370
423
|
}
|
|
371
424
|
if (hasHauler) {
|
|
372
425
|
const efficiency =
|
|
@@ -388,8 +441,8 @@ export function computeContainerCapabilities(stats: Record<string, number>): {
|
|
|
388
441
|
hullmass: number
|
|
389
442
|
capacity: number
|
|
390
443
|
} {
|
|
391
|
-
const statSum = stats.strength + stats.hardness
|
|
392
|
-
const exponent = statSum /
|
|
444
|
+
const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
|
|
445
|
+
const exponent = statSum / 1998.0
|
|
393
446
|
return {
|
|
394
447
|
hullmass: computeBaseHullmass(stats),
|
|
395
448
|
capacity: Math.floor(22000000 * 6 ** exponent),
|
|
@@ -400,14 +453,13 @@ export function computeContainerT2Capabilities(stats: Record<string, number>): {
|
|
|
400
453
|
hullmass: number
|
|
401
454
|
capacity: number
|
|
402
455
|
} {
|
|
403
|
-
const strength = stats.strength
|
|
404
|
-
const density = stats.density
|
|
405
|
-
const hardness = stats.hardness
|
|
406
|
-
const cohesion = stats.cohesion
|
|
456
|
+
const strength = stats.strength ?? 0
|
|
457
|
+
const density = stats.density ?? 0
|
|
458
|
+
const hardness = stats.hardness ?? 0
|
|
407
459
|
|
|
408
460
|
const hullmass = 70000 - 50 * density
|
|
409
461
|
|
|
410
|
-
const statSum = strength + hardness
|
|
462
|
+
const statSum = strength + hardness
|
|
411
463
|
const exponent = statSum / 2947
|
|
412
464
|
const capacity = Math.floor(24000000 * 6 ** exponent)
|
|
413
465
|
|
|
@@ -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
|
+
})
|