@shipload/sdk 1.0.0-next.64 → 1.0.0-next.66
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 +754 -316
- package/lib/shipload.js +2650 -1336
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +2564 -1308
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +219 -145
- package/lib/testing.js +910 -557
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +910 -557
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/crafting.test.ts +51 -6
- package/src/capabilities/crafting.ts +19 -4
- package/src/capabilities/gathering.ts +2 -2
- package/src/capabilities/storage.test.ts +26 -0
- package/src/capabilities/storage.ts +6 -6
- package/src/contracts/fund.ts +110 -5
- package/src/contracts/platform.ts +10 -1
- package/src/contracts/server.ts +498 -331
- package/src/data/charters.json +325 -127
- package/src/data/entities.json +24 -24
- package/src/data/items.json +114 -114
- package/src/data/kind-registry.json +3 -3
- package/src/data/kind-registry.ts +5 -0
- package/src/data/recipes.json +56 -95
- package/src/derivation/build-methods.test.ts +59 -1
- package/src/derivation/build-methods.ts +2 -0
- package/src/derivation/capabilities.ts +7 -7
- package/src/derivation/strata.ts +5 -3
- package/src/derivation/tiers.ts +5 -5
- package/src/entity/hydrate.test.ts +156 -0
- package/src/entity/hydrate.ts +86 -0
- package/src/errors.ts +9 -0
- package/src/format.ts +15 -13
- package/src/index-module.ts +33 -5
- package/src/influence/ballot.test.ts +103 -0
- package/src/influence/ballot.ts +98 -0
- package/src/influence/charter-metadata.test.ts +136 -0
- package/src/influence/charter-metadata.ts +236 -0
- package/src/influence/charters.test.ts +51 -65
- package/src/influence/charters.ts +70 -40
- package/src/influence/civic.test.ts +278 -0
- package/src/influence/civic.ts +269 -0
- package/src/influence/constants.ts +30 -9
- package/src/influence/decomp.ts +15 -15
- package/src/influence/duration.ts +42 -6
- package/src/influence/index.ts +49 -6
- package/src/influence/influence.test.ts +76 -9
- package/src/influence/valuation.ts +17 -17
- package/src/managers/actions.ts +65 -37
- package/src/managers/base.ts +4 -0
- package/src/managers/context.ts +3 -1
- package/src/managers/index.ts +5 -2
- package/src/managers/influence.test.ts +18 -20
- package/src/managers/influence.ts +162 -84
- package/src/managers/jobs.test.ts +2 -2
- package/src/managers/jobs.ts +4 -4
- package/src/nft/description.ts +10 -11
- package/src/planner/index.ts +54 -0
- package/src/planner/planner.test.ts +127 -2
- package/src/resolution/display-name.ts +1 -1
- package/src/scheduling/availability.test.ts +113 -0
- package/src/scheduling/availability.ts +48 -8
- package/src/scheduling/jobs.ts +1 -1
- package/src/scheduling/projection.ts +2 -16
- package/src/scheduling/task-cargo.test.ts +62 -1
- package/src/scheduling/task-cargo.ts +25 -10
- package/src/scheduling/task-effects.test.ts +35 -0
- package/src/scheduling/task-effects.ts +34 -0
- package/src/scheduling/unwrap.test.ts +3 -3
- package/src/scheduling/unwrap.ts +3 -3
- package/src/shipload.ts +23 -4
- package/src/travel/reach.test.ts +86 -0
- package/src/travel/reach.ts +23 -10
- package/src/travel/route-build.test.ts +63 -0
- package/src/travel/route-build.ts +60 -0
- package/src/travel/travel.ts +2 -1
- package/src/types.ts +6 -2
- package/src/utils/hash.ts +18 -2
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import chartersJson from '../data/charters.json'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
CHARTER_NONE,
|
|
4
|
+
CIVIC_GRANT_LEVEL_GATE,
|
|
5
|
+
CIVIC_GRANT_MODULE,
|
|
6
|
+
CIVIC_GRANT_RUNG,
|
|
7
|
+
} from './constants'
|
|
8
|
+
|
|
9
|
+
export interface CharterGrant {
|
|
5
10
|
kind: number
|
|
6
|
-
|
|
7
|
-
targetItemId: number
|
|
8
|
-
slotMask: number
|
|
11
|
+
building: number
|
|
9
12
|
stat: number
|
|
13
|
+
value: number
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
export interface CharterNode {
|
|
13
17
|
nodeId: number
|
|
14
18
|
cost: bigint
|
|
15
19
|
prereqs: number[]
|
|
16
|
-
|
|
20
|
+
grants: CharterGrant[]
|
|
21
|
+
exclusionGroup: number
|
|
22
|
+
repeatCap: number
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
export const CHARTER_REGISTRY: CharterNode[] = chartersJson.nodes.map((node) => ({
|
|
20
26
|
nodeId: node.nodeId,
|
|
21
27
|
cost: BigInt(node.cost),
|
|
22
28
|
prereqs: [...node.prereqs],
|
|
23
|
-
|
|
29
|
+
grants: node.grants.map((grant) => ({...grant})),
|
|
30
|
+
exclusionGroup: node.exclusionGroup,
|
|
31
|
+
repeatCap: node.repeatCap,
|
|
24
32
|
}))
|
|
25
33
|
|
|
26
34
|
export function charterNode(nodeId: number): CharterNode | undefined {
|
|
@@ -28,58 +36,77 @@ export function charterNode(nodeId: number): CharterNode | undefined {
|
|
|
28
36
|
return CHARTER_REGISTRY.find((n) => n.nodeId === nodeId)
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
export type CharterIneligibility = 'already-taken' | 'prereq-missing'
|
|
39
|
+
export type CharterIneligibility = 'already-taken' | 'prereq-missing'
|
|
32
40
|
|
|
33
41
|
export const CHARTER_INELIGIBILITY_MESSAGES: Record<CharterIneligibility, string> = {
|
|
34
42
|
'already-taken': 'charter is already completed at this world',
|
|
35
43
|
'prereq-missing': 'charter prerequisites are not completed',
|
|
36
|
-
'refit-target-missing': 'charter refit target not found at this world',
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
export interface BuiltCharter {
|
|
40
47
|
nodeId: number
|
|
41
|
-
|
|
48
|
+
repeats: number
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
export interface CharterWorld {
|
|
45
52
|
built: BuiltCharter[]
|
|
46
|
-
entityExists?: (entityId: bigint) => boolean
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
export interface
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
export interface WorldBuilding {
|
|
56
|
+
entityId: bigint
|
|
57
|
+
building: number
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
function builtCharter(world: CharterWorld, nodeId: number): BuiltCharter | undefined {
|
|
55
61
|
return world.built.find((row) => row.nodeId === nodeId)
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
export function
|
|
59
|
-
return CHARTER_REGISTRY.find(
|
|
60
|
-
(
|
|
61
|
-
|
|
64
|
+
export function charterGateNodeFor(building: number, level: number = 1): CharterNode | undefined {
|
|
65
|
+
return CHARTER_REGISTRY.find((node) =>
|
|
66
|
+
node.grants.some(
|
|
67
|
+
(grant) =>
|
|
68
|
+
grant.kind === CIVIC_GRANT_LEVEL_GATE &&
|
|
69
|
+
grant.building === building &&
|
|
70
|
+
grant.value === level
|
|
71
|
+
)
|
|
62
72
|
)
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
export function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
export function charterBuildingEntity(buildings: WorldBuilding[], building: number): bigint {
|
|
76
|
+
const record = buildings.find((row) => row.building === building)
|
|
77
|
+
return record ? record.entityId : 0n
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function charterRungValue(world: CharterWorld, building: number, stat: number): number {
|
|
81
|
+
let total = 0
|
|
82
|
+
for (const node of CHARTER_REGISTRY) {
|
|
83
|
+
if (builtCharter(world, node.nodeId) === undefined) continue
|
|
84
|
+
for (const grant of node.grants) {
|
|
85
|
+
if (grant.kind !== CIVIC_GRANT_RUNG) continue
|
|
86
|
+
if (grant.building !== building || grant.stat !== stat) continue
|
|
87
|
+
total += grant.value
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return total
|
|
69
91
|
}
|
|
70
92
|
|
|
71
|
-
export function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
export function charterModuleCount(world: CharterWorld, building: number): number {
|
|
94
|
+
let total = 0
|
|
95
|
+
for (const node of CHARTER_REGISTRY) {
|
|
96
|
+
if (builtCharter(world, node.nodeId) === undefined) continue
|
|
97
|
+
for (const grant of node.grants) {
|
|
98
|
+
if (grant.kind !== CIVIC_GRANT_MODULE) continue
|
|
99
|
+
if (grant.building !== building) continue
|
|
100
|
+
total += grant.value
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return total
|
|
78
104
|
}
|
|
79
105
|
|
|
80
|
-
export function
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
export function charterPrereqsMet(world: CharterWorld, node: CharterNode): boolean {
|
|
107
|
+
return node.prereqs.every(
|
|
108
|
+
(prereq) => prereq === CHARTER_NONE || builtCharter(world, prereq) !== undefined
|
|
109
|
+
)
|
|
83
110
|
}
|
|
84
111
|
|
|
85
112
|
export function charterIneligible(
|
|
@@ -88,7 +115,6 @@ export function charterIneligible(
|
|
|
88
115
|
): CharterIneligibility | undefined {
|
|
89
116
|
if (builtCharter(world, node.nodeId) !== undefined) return 'already-taken'
|
|
90
117
|
if (!charterPrereqsMet(world, node)) return 'prereq-missing'
|
|
91
|
-
if (!charterEffectTargetPresent(world, node)) return 'refit-target-missing'
|
|
92
118
|
return undefined
|
|
93
119
|
}
|
|
94
120
|
|
|
@@ -110,11 +136,15 @@ export function charterSingletonMandate(world: CharterWorld): number {
|
|
|
110
136
|
return only
|
|
111
137
|
}
|
|
112
138
|
|
|
113
|
-
export function
|
|
114
|
-
stored: ChosenCharter,
|
|
139
|
+
export function charterEligibleChained(
|
|
115
140
|
world: CharterWorld,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
141
|
+
node: CharterNode,
|
|
142
|
+
seated: number[]
|
|
143
|
+
): boolean {
|
|
144
|
+
if (builtCharter(world, node.nodeId) !== undefined || seated.includes(node.nodeId)) return false
|
|
145
|
+
for (const prereq of node.prereqs) {
|
|
146
|
+
if (prereq === CHARTER_NONE) continue
|
|
147
|
+
if (builtCharter(world, prereq) === undefined && !seated.includes(prereq)) return false
|
|
148
|
+
}
|
|
149
|
+
return true
|
|
120
150
|
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import {describe, expect, test} from 'bun:test'
|
|
2
|
+
import {Name} from '@wharfkit/antelope'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
MODULE_BUILDER,
|
|
6
|
+
MODULE_CRAFTER,
|
|
7
|
+
MODULE_LOADER,
|
|
8
|
+
MODULE_STORAGE,
|
|
9
|
+
} from '../capabilities/modules'
|
|
10
|
+
import {
|
|
11
|
+
ITEM_BUILDER_T1,
|
|
12
|
+
ITEM_CONSTRUCTION_DOCK_T1_PACKED,
|
|
13
|
+
ITEM_CRAFTER_T1,
|
|
14
|
+
ITEM_DEPOT_T1_PACKED,
|
|
15
|
+
ITEM_LOADER_T1,
|
|
16
|
+
ITEM_STORAGE_T1,
|
|
17
|
+
ITEM_WORKSHOP_T1_PACKED,
|
|
18
|
+
} from '../data/item-ids'
|
|
19
|
+
import {ENTITY_DEPOT, ENTITY_SHIP, ENTITY_WORKSHOP} from '../data/kind-registry'
|
|
20
|
+
import {decodeStats, encodeStats} from '../derivation/crafting'
|
|
21
|
+
import {CHARTER_REGISTRY, charterNode, type CharterNode} from './charters'
|
|
22
|
+
import {
|
|
23
|
+
civicBuildingFor,
|
|
24
|
+
civicHullItem,
|
|
25
|
+
civicModuleItemFor,
|
|
26
|
+
civicStatModuleType,
|
|
27
|
+
emptyCivicModules,
|
|
28
|
+
fitCivicModules,
|
|
29
|
+
isCivicEntity,
|
|
30
|
+
isCivicKind,
|
|
31
|
+
previewCharterGrants,
|
|
32
|
+
raiseCivicStat,
|
|
33
|
+
type CivicStanding,
|
|
34
|
+
} from './civic'
|
|
35
|
+
import {
|
|
36
|
+
CHARTER_BASELINE_STAT,
|
|
37
|
+
CHARTER_RUNG_STEP,
|
|
38
|
+
CIVIC_DEPOT,
|
|
39
|
+
CIVIC_DOCK,
|
|
40
|
+
CIVIC_GRANT_RUNG,
|
|
41
|
+
CIVIC_SPEED_CAP,
|
|
42
|
+
CIVIC_STAT_CRAFT_SPEED,
|
|
43
|
+
CIVIC_STAT_STORAGE_CAPACITY,
|
|
44
|
+
CIVIC_STAT_TRANSFER_SPEED,
|
|
45
|
+
CIVIC_WORKSHOP,
|
|
46
|
+
} from './constants'
|
|
47
|
+
import {getStatCount} from './quality'
|
|
48
|
+
|
|
49
|
+
const NODE_WORKSHOP = 10001
|
|
50
|
+
const NODE_WORKSHOP_TUNEUPS = [10100201, 10100202, 10100203, 10100204]
|
|
51
|
+
const NODE_WORKSHOP_TUNEUP = NODE_WORKSHOP_TUNEUPS[0]
|
|
52
|
+
const NODE_DOCK = 30100001
|
|
53
|
+
const NODE_DOCK_TUNEUPS = [30100301, 30100302, 30100303, 30100304]
|
|
54
|
+
const NODE_DEPOT = 40100001
|
|
55
|
+
const NODE_DEPOT_BAYS = [40100101, 40100102, 40100103, 40100104]
|
|
56
|
+
const NODE_DEPOT_LOADERS = [40100401, 40100402, 40100403, 40100404]
|
|
57
|
+
|
|
58
|
+
function node(nodeId: number): CharterNode {
|
|
59
|
+
const found = charterNode(nodeId)
|
|
60
|
+
if (!found) throw new Error(`no charter node ${nodeId}`)
|
|
61
|
+
return found
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function stats(
|
|
65
|
+
preview: {modules: {installed?: {item_id: unknown; stats: unknown}}[]},
|
|
66
|
+
slot: number
|
|
67
|
+
) {
|
|
68
|
+
const installed = preview.modules[slot].installed
|
|
69
|
+
if (!installed) throw new Error(`slot ${slot} is empty`)
|
|
70
|
+
const itemId = Number(installed.item_id)
|
|
71
|
+
return {itemId, stats: decodeStats(BigInt(String(installed.stats)), getStatCount(itemId))}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function complete(standing: CivicStanding, nodeId: number): CivicStanding {
|
|
75
|
+
const next = {...standing}
|
|
76
|
+
for (const preview of previewCharterGrants(node(nodeId), standing)) {
|
|
77
|
+
next[preview.building] = preview.modules
|
|
78
|
+
}
|
|
79
|
+
return next
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe('civic building tables', () => {
|
|
83
|
+
test('each building maps to its restored hull item', () => {
|
|
84
|
+
expect(civicHullItem(CIVIC_WORKSHOP)).toBe(ITEM_WORKSHOP_T1_PACKED)
|
|
85
|
+
expect(civicHullItem(CIVIC_DOCK)).toBe(ITEM_CONSTRUCTION_DOCK_T1_PACKED)
|
|
86
|
+
expect(civicHullItem(CIVIC_DEPOT)).toBe(ITEM_DEPOT_T1_PACKED)
|
|
87
|
+
expect(civicHullItem(0)).toBe(0)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('each civic stat is carried by one player T1 module type', () => {
|
|
91
|
+
expect(civicStatModuleType(CIVIC_STAT_CRAFT_SPEED)).toBe(MODULE_CRAFTER)
|
|
92
|
+
expect(civicStatModuleType(CIVIC_STAT_STORAGE_CAPACITY)).toBe(MODULE_STORAGE)
|
|
93
|
+
expect(civicStatModuleType(CIVIC_STAT_TRANSFER_SPEED)).toBe(MODULE_LOADER)
|
|
94
|
+
expect(civicModuleItemFor(MODULE_CRAFTER)).toBe(ITEM_CRAFTER_T1)
|
|
95
|
+
expect(civicModuleItemFor(MODULE_BUILDER)).toBe(ITEM_BUILDER_T1)
|
|
96
|
+
expect(civicModuleItemFor(MODULE_LOADER)).toBe(ITEM_LOADER_T1)
|
|
97
|
+
expect(civicModuleItemFor(MODULE_STORAGE)).toBe(ITEM_STORAGE_T1)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('civic kinds are the four buildings and nothing else', () => {
|
|
101
|
+
expect(isCivicKind(ENTITY_WORKSHOP)).toBe(true)
|
|
102
|
+
expect(isCivicKind('depot')).toBe(true)
|
|
103
|
+
expect(isCivicKind(ENTITY_SHIP)).toBe(false)
|
|
104
|
+
expect(civicBuildingFor(ENTITY_DEPOT)).toBe(CIVIC_DEPOT)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('civic-ness is the owner, not the kind', () => {
|
|
108
|
+
const civic = Name.from('eon.shipload')
|
|
109
|
+
expect(isCivicEntity({owner: 'eon.shipload'}, civic)).toBe(true)
|
|
110
|
+
expect(isCivicEntity({owner: Name.from('alice')}, civic)).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('every rung in the registry lands on a stat its building defines', () => {
|
|
114
|
+
for (const entry of CHARTER_REGISTRY) {
|
|
115
|
+
for (const grant of entry.grants) {
|
|
116
|
+
if (grant.kind !== CIVIC_GRANT_RUNG) continue
|
|
117
|
+
expect(civicStatModuleType(grant.stat)).not.toBe(0)
|
|
118
|
+
const layout = emptyCivicModules(grant.building).map((m) => Number(m.type))
|
|
119
|
+
expect(layout).toContain(civicStatModuleType(grant.stat))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('the fourteen nodes reproduce the chain-fitted buildings', () => {
|
|
126
|
+
test('the Workshop node spawns five crafters at the baseline stat', () => {
|
|
127
|
+
const [workshop] = previewCharterGrants(node(NODE_WORKSHOP))
|
|
128
|
+
expect(workshop.building).toBe(CIVIC_WORKSHOP)
|
|
129
|
+
expect(workshop.spawned).toBe(true)
|
|
130
|
+
expect(workshop.itemId).toBe(ITEM_WORKSHOP_T1_PACKED)
|
|
131
|
+
expect(workshop.modules.length).toBe(5)
|
|
132
|
+
for (let slot = 0; slot < 5; slot++) {
|
|
133
|
+
expect(stats(workshop, slot)).toEqual({
|
|
134
|
+
itemId: ITEM_CRAFTER_T1,
|
|
135
|
+
stats: [CHARTER_BASELINE_STAT, CHARTER_BASELINE_STAT],
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('each Workshop tune-up raises every crafter by one rung step, reaching 400', () => {
|
|
141
|
+
let standing = complete({}, NODE_WORKSHOP)
|
|
142
|
+
let raised = CHARTER_BASELINE_STAT
|
|
143
|
+
for (const tuneup of NODE_WORKSHOP_TUNEUPS) {
|
|
144
|
+
const [workshop] = previewCharterGrants(node(tuneup), standing)
|
|
145
|
+
expect(workshop.spawned).toBe(false)
|
|
146
|
+
raised += CHARTER_RUNG_STEP
|
|
147
|
+
for (let slot = 0; slot < 5; slot++) {
|
|
148
|
+
expect(stats(workshop, slot).stats).toEqual([raised, raised])
|
|
149
|
+
}
|
|
150
|
+
standing = complete(standing, tuneup)
|
|
151
|
+
}
|
|
152
|
+
expect(raised).toBe(400)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('the Dock node fits one builder and its four tune-ups reach 400', () => {
|
|
156
|
+
let standing = complete(complete({}, NODE_WORKSHOP), NODE_DOCK)
|
|
157
|
+
expect(stats({modules: standing[CIVIC_DOCK]!}, 0)).toEqual({
|
|
158
|
+
itemId: ITEM_BUILDER_T1,
|
|
159
|
+
stats: [CHARTER_BASELINE_STAT, CHARTER_BASELINE_STAT],
|
|
160
|
+
})
|
|
161
|
+
let raised = CHARTER_BASELINE_STAT
|
|
162
|
+
for (const tuneup of NODE_DOCK_TUNEUPS) {
|
|
163
|
+
const [dock] = previewCharterGrants(node(tuneup), standing)
|
|
164
|
+
raised += CHARTER_RUNG_STEP
|
|
165
|
+
expect(stats(dock, 0).stats).toEqual([raised, raised])
|
|
166
|
+
standing = complete(standing, tuneup)
|
|
167
|
+
}
|
|
168
|
+
expect(raised).toBe(400)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('the Depot node fits a baseline loader and leaves the bays empty', () => {
|
|
172
|
+
const standing = complete(complete({}, NODE_WORKSHOP), NODE_DEPOT)
|
|
173
|
+
const depot = {modules: standing[CIVIC_DEPOT]!}
|
|
174
|
+
expect(stats(depot, 0)).toEqual({
|
|
175
|
+
itemId: ITEM_LOADER_T1,
|
|
176
|
+
stats: [CHARTER_BASELINE_STAT, CHARTER_BASELINE_STAT],
|
|
177
|
+
})
|
|
178
|
+
for (let slot = 1; slot <= 4; slot++) expect(depot.modules[slot].installed).toBeUndefined()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('the four bays fill the storage slots in order at the baseline stat', () => {
|
|
182
|
+
let standing = complete(complete({}, NODE_WORKSHOP), NODE_DEPOT)
|
|
183
|
+
for (let i = 0; i < NODE_DEPOT_BAYS.length; i++) {
|
|
184
|
+
standing = complete(standing, NODE_DEPOT_BAYS[i])
|
|
185
|
+
const depot = {modules: standing[CIVIC_DEPOT]!}
|
|
186
|
+
for (let slot = 1; slot <= 4; slot++) {
|
|
187
|
+
if (slot <= i + 1) {
|
|
188
|
+
expect(stats(depot, slot)).toEqual({
|
|
189
|
+
itemId: ITEM_STORAGE_T1,
|
|
190
|
+
stats: [
|
|
191
|
+
CHARTER_BASELINE_STAT,
|
|
192
|
+
CHARTER_BASELINE_STAT,
|
|
193
|
+
CHARTER_BASELINE_STAT,
|
|
194
|
+
CHARTER_BASELINE_STAT,
|
|
195
|
+
],
|
|
196
|
+
})
|
|
197
|
+
} else {
|
|
198
|
+
expect(depot.modules[slot].installed).toBeUndefined()
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('the four loader rungs step the slot-0 loader by one rung each, reaching 400', () => {
|
|
205
|
+
let standing = complete(complete({}, NODE_WORKSHOP), NODE_DEPOT)
|
|
206
|
+
for (let i = 0; i < NODE_DEPOT_LOADERS.length; i++) {
|
|
207
|
+
standing = complete(standing, NODE_DEPOT_LOADERS[i])
|
|
208
|
+
const depot = {modules: standing[CIVIC_DEPOT]!}
|
|
209
|
+
const stat = CHARTER_BASELINE_STAT + CHARTER_RUNG_STEP * (i + 1)
|
|
210
|
+
expect(stats(depot, 0)).toEqual({itemId: ITEM_LOADER_T1, stats: [stat, stat]})
|
|
211
|
+
for (let slot = 1; slot <= 4; slot++)
|
|
212
|
+
expect(depot.modules[slot].installed).toBeUndefined()
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('grant mechanics', () => {
|
|
218
|
+
test('a module grant joins at a sibling stat', () => {
|
|
219
|
+
const base = fitCivicModules(emptyCivicModules(CIVIC_DEPOT), 1)
|
|
220
|
+
const raised = raiseCivicStat(base, {
|
|
221
|
+
kind: CIVIC_GRANT_RUNG,
|
|
222
|
+
building: CIVIC_DEPOT,
|
|
223
|
+
stat: CIVIC_STAT_TRANSFER_SPEED,
|
|
224
|
+
value: 50,
|
|
225
|
+
})
|
|
226
|
+
const withBay = fitCivicModules(raised, 1)
|
|
227
|
+
expect(stats({modules: withBay}, 1).stats).toEqual([0, 0, 0, 0])
|
|
228
|
+
const withSecondBay = fitCivicModules(
|
|
229
|
+
raiseCivicStat(withBay, {
|
|
230
|
+
kind: CIVIC_GRANT_RUNG,
|
|
231
|
+
building: CIVIC_DEPOT,
|
|
232
|
+
stat: CIVIC_STAT_STORAGE_CAPACITY,
|
|
233
|
+
value: 7,
|
|
234
|
+
}),
|
|
235
|
+
1
|
|
236
|
+
)
|
|
237
|
+
expect(stats({modules: withSecondBay}, 2).stats).toEqual([7, 7, 7, 7])
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test('a rung caps at the civic speed cap', () => {
|
|
241
|
+
const fitted = fitCivicModules(emptyCivicModules(CIVIC_WORKSHOP), 5)
|
|
242
|
+
const raised = raiseCivicStat(fitted, {
|
|
243
|
+
kind: CIVIC_GRANT_RUNG,
|
|
244
|
+
building: CIVIC_WORKSHOP,
|
|
245
|
+
stat: CIVIC_STAT_CRAFT_SPEED,
|
|
246
|
+
value: 5000,
|
|
247
|
+
})
|
|
248
|
+
expect(stats({modules: raised}, 0).stats).toEqual([CIVIC_SPEED_CAP, CIVIC_SPEED_CAP])
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
test('a module grant past the layout is refused', () => {
|
|
252
|
+
const full = fitCivicModules(emptyCivicModules(CIVIC_DOCK), 2)
|
|
253
|
+
expect(() => fitCivicModules(full, 1)).toThrow('no open slot')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('a rung with no carrier is refused', () => {
|
|
257
|
+
expect(() =>
|
|
258
|
+
raiseCivicStat(emptyCivicModules(CIVIC_DEPOT), {
|
|
259
|
+
kind: CIVIC_GRANT_RUNG,
|
|
260
|
+
building: CIVIC_DEPOT,
|
|
261
|
+
stat: CIVIC_STAT_TRANSFER_SPEED,
|
|
262
|
+
value: 1,
|
|
263
|
+
})
|
|
264
|
+
).toThrow('no module carrying')
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
test('a grant on a building that is not standing is refused', () => {
|
|
268
|
+
expect(() => previewCharterGrants(node(NODE_WORKSHOP_TUNEUP))).toThrow('not standing')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('inputs are never mutated', () => {
|
|
272
|
+
const before = emptyCivicModules(CIVIC_WORKSHOP)
|
|
273
|
+
const snapshot = before.map((m) => String(m.installed?.stats ?? 'empty'))
|
|
274
|
+
fitCivicModules(before, 5)
|
|
275
|
+
expect(before.map((m) => String(m.installed?.stats ?? 'empty'))).toEqual(snapshot)
|
|
276
|
+
expect(encodeStats([1, 2])).not.toBe(0n)
|
|
277
|
+
})
|
|
278
|
+
})
|