@shipload/sdk 1.0.0-next.65 → 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 +698 -316
- package/lib/shipload.js +2473 -1235
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +2392 -1216
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +205 -146
- package/lib/testing.js +846 -556
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +846 -556
- 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 +79 -2
- package/src/contracts/platform.ts +10 -1
- package/src/contracts/server.ts +467 -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 +32 -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 +4 -2
- package/src/managers/influence.test.ts +18 -20
- package/src/managers/influence.ts +137 -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 +22 -3
- 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
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {describe, expect, test} from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import {CHARTER_REGISTRY, type CharterNode} from './charters'
|
|
4
|
+
import {
|
|
5
|
+
charterMetadata,
|
|
6
|
+
charterName,
|
|
7
|
+
charterSignature,
|
|
8
|
+
charterSummary,
|
|
9
|
+
romanNumeral,
|
|
10
|
+
type CharterSignature,
|
|
11
|
+
} from './charter-metadata'
|
|
12
|
+
|
|
13
|
+
function nodeById(nodeId: number): CharterNode {
|
|
14
|
+
const node = CHARTER_REGISTRY.find((candidate) => candidate.nodeId === nodeId)
|
|
15
|
+
if (!node) throw new Error(`no charter node ${nodeId} in the registry`)
|
|
16
|
+
return node
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function signatureFor(nodeId: number): CharterSignature {
|
|
20
|
+
const signature = charterSignature(nodeById(nodeId))
|
|
21
|
+
if (!signature) throw new Error(`no signature for charter node ${nodeId}`)
|
|
22
|
+
return signature
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sharesWord(name: string, label: string): boolean {
|
|
26
|
+
const haystack = name.toLowerCase()
|
|
27
|
+
return label
|
|
28
|
+
.split(/\s+/)
|
|
29
|
+
.filter((word) => word.length > 2)
|
|
30
|
+
.some((word) => haystack.includes(word.toLowerCase()))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('charter metadata', () => {
|
|
34
|
+
test('covers every node in the chain-synced registry', () => {
|
|
35
|
+
const missing = CHARTER_REGISTRY.filter((node) => !charterMetadata[node.nodeId])
|
|
36
|
+
expect(missing).toEqual([])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('carries no entry for a node the registry does not define', () => {
|
|
40
|
+
const known = new Set(CHARTER_REGISTRY.map((node) => node.nodeId))
|
|
41
|
+
const orphans = Object.keys(charterMetadata)
|
|
42
|
+
.map(Number)
|
|
43
|
+
.filter((nodeId) => !known.has(nodeId))
|
|
44
|
+
expect(orphans).toEqual([])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('every entry has a name and a summary', () => {
|
|
48
|
+
for (const node of CHARTER_REGISTRY) {
|
|
49
|
+
expect(charterName(node.nodeId).length).toBeGreaterThan(0)
|
|
50
|
+
expect(charterSummary(node.nodeId).length).toBeGreaterThan(0)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('falls back to a node-numbered name for an unknown id', () => {
|
|
55
|
+
expect(charterName(9999)).toBe('Charter 9999')
|
|
56
|
+
expect(charterSummary(9999)).toBe('')
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('charter names agree with the grant they are derived from', () => {
|
|
61
|
+
for (const node of CHARTER_REGISTRY) {
|
|
62
|
+
const signature = signatureFor(node.nodeId)
|
|
63
|
+
const name = charterName(node.nodeId)
|
|
64
|
+
|
|
65
|
+
test(`node ${node.nodeId} names the building it grants (${signature.buildingLabel})`, () => {
|
|
66
|
+
expect(sharesWord(name, signature.buildingLabel)).toBeTrue()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (signature.kind !== 'gate' && signature.rankCount > 1) {
|
|
70
|
+
test(`node ${node.nodeId} carries rank ${signature.rank} of ${signature.rankCount}`, () => {
|
|
71
|
+
expect(name.endsWith(` ${romanNumeral(signature.rank)}`)).toBeTrue()
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('charter signatures', () => {
|
|
78
|
+
test('reads the depot level gate from its grant list', () => {
|
|
79
|
+
expect(signatureFor(40100001)).toEqual({kind: 'gate', buildingLabel: 'Depot', level: 1})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('ranks the depot bays by their prereq chain', () => {
|
|
83
|
+
expect([40100101, 40100102, 40100103, 40100104].map(signatureFor)).toEqual(
|
|
84
|
+
[1, 2, 3, 4].map((rank) => ({
|
|
85
|
+
kind: 'module',
|
|
86
|
+
buildingLabel: 'Depot',
|
|
87
|
+
count: 1,
|
|
88
|
+
rank,
|
|
89
|
+
rankCount: 4,
|
|
90
|
+
}))
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('ranks the depot transfer rungs by their prereq chain', () => {
|
|
95
|
+
expect([40100401, 40100402, 40100403, 40100404].map(signatureFor)).toEqual([
|
|
96
|
+
{
|
|
97
|
+
kind: 'rung',
|
|
98
|
+
buildingLabel: 'Depot',
|
|
99
|
+
statLabel: 'transfer speed',
|
|
100
|
+
rank: 1,
|
|
101
|
+
rankCount: 4,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
kind: 'rung',
|
|
105
|
+
buildingLabel: 'Depot',
|
|
106
|
+
statLabel: 'transfer speed',
|
|
107
|
+
rank: 2,
|
|
108
|
+
rankCount: 4,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
kind: 'rung',
|
|
112
|
+
buildingLabel: 'Depot',
|
|
113
|
+
statLabel: 'transfer speed',
|
|
114
|
+
rank: 3,
|
|
115
|
+
rankCount: 4,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
kind: 'rung',
|
|
119
|
+
buildingLabel: 'Depot',
|
|
120
|
+
statLabel: 'transfer speed',
|
|
121
|
+
rank: 4,
|
|
122
|
+
rankCount: 4,
|
|
123
|
+
},
|
|
124
|
+
])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('ranks the Workshop tune-ups as a four-rung ladder', () => {
|
|
128
|
+
expect(signatureFor(10100203)).toEqual({
|
|
129
|
+
kind: 'rung',
|
|
130
|
+
buildingLabel: 'Workshop',
|
|
131
|
+
statLabel: 'crafting speed',
|
|
132
|
+
rank: 3,
|
|
133
|
+
rankCount: 4,
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
})
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {CHARTER_REGISTRY, type CharterGrant, type CharterNode} from './charters'
|
|
2
|
+
import {
|
|
3
|
+
CHARTER_NONE,
|
|
4
|
+
CIVIC_DEPOT,
|
|
5
|
+
CIVIC_DOCK,
|
|
6
|
+
CIVIC_GRANT_LEVEL_GATE,
|
|
7
|
+
CIVIC_GRANT_MODULE,
|
|
8
|
+
CIVIC_GRANT_RUNG,
|
|
9
|
+
CIVIC_NEXUS,
|
|
10
|
+
CIVIC_STAT_BUILD_SPEED,
|
|
11
|
+
CIVIC_STAT_CRAFT_SPEED,
|
|
12
|
+
CIVIC_STAT_STORAGE_CAPACITY,
|
|
13
|
+
CIVIC_STAT_TRANSFER_SPEED,
|
|
14
|
+
CIVIC_WORKSHOP,
|
|
15
|
+
} from './constants'
|
|
16
|
+
|
|
17
|
+
const BUILDING_LABELS: Record<number, string> = {
|
|
18
|
+
[CIVIC_WORKSHOP]: 'Workshop',
|
|
19
|
+
[CIVIC_NEXUS]: 'Nexus',
|
|
20
|
+
[CIVIC_DOCK]: 'Construction Dock',
|
|
21
|
+
[CIVIC_DEPOT]: 'Depot',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const STAT_LABELS: Record<number, string> = {
|
|
25
|
+
[CIVIC_STAT_CRAFT_SPEED]: 'crafting speed',
|
|
26
|
+
[CIVIC_STAT_BUILD_SPEED]: 'build speed',
|
|
27
|
+
[CIVIC_STAT_TRANSFER_SPEED]: 'transfer speed',
|
|
28
|
+
[CIVIC_STAT_STORAGE_CAPACITY]: 'storage capacity',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function civicBuildingLabel(building: number): string {
|
|
32
|
+
return BUILDING_LABELS[building] ?? `Building ${building}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function civicStatLabel(stat: number): string {
|
|
36
|
+
return STAT_LABELS[stat] ?? `Stat ${stat}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CharterMeta {
|
|
40
|
+
name: string
|
|
41
|
+
summary: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const charterMetadata: Record<number, CharterMeta> = {
|
|
45
|
+
10001: {
|
|
46
|
+
name: 'Communal Workshop',
|
|
47
|
+
summary: 'Shared crafting for anyone at this world',
|
|
48
|
+
},
|
|
49
|
+
10100201: {
|
|
50
|
+
name: 'Workshop tune-up I',
|
|
51
|
+
summary: "Raises the Workshop's Fabricator stats",
|
|
52
|
+
},
|
|
53
|
+
10100202: {
|
|
54
|
+
name: 'Workshop tune-up II',
|
|
55
|
+
summary: "Raises the Workshop's Fabricator stats",
|
|
56
|
+
},
|
|
57
|
+
10100203: {
|
|
58
|
+
name: 'Workshop tune-up III',
|
|
59
|
+
summary: "Raises the Workshop's Fabricator stats",
|
|
60
|
+
},
|
|
61
|
+
10100204: {
|
|
62
|
+
name: 'Workshop tune-up IV',
|
|
63
|
+
summary: "Raises the Workshop's Fabricator stats",
|
|
64
|
+
},
|
|
65
|
+
20001: {
|
|
66
|
+
name: 'Civic Nexus',
|
|
67
|
+
summary: 'Advances the world to Tier II; wrap items for trade and unwrap them here',
|
|
68
|
+
},
|
|
69
|
+
30100001: {
|
|
70
|
+
name: 'Construction Dock',
|
|
71
|
+
summary: 'Ships queue here for upgrades',
|
|
72
|
+
},
|
|
73
|
+
30100301: {
|
|
74
|
+
name: 'Dock tune-up I',
|
|
75
|
+
summary: "Raises the Dock's Assembly Arm stats",
|
|
76
|
+
},
|
|
77
|
+
30100302: {
|
|
78
|
+
name: 'Dock tune-up II',
|
|
79
|
+
summary: "Raises the Dock's Assembly Arm stats",
|
|
80
|
+
},
|
|
81
|
+
30100303: {
|
|
82
|
+
name: 'Dock tune-up III',
|
|
83
|
+
summary: "Raises the Dock's Assembly Arm stats",
|
|
84
|
+
},
|
|
85
|
+
30100304: {
|
|
86
|
+
name: 'Dock tune-up IV',
|
|
87
|
+
summary: "Raises the Dock's Assembly Arm stats",
|
|
88
|
+
},
|
|
89
|
+
40100001: {
|
|
90
|
+
name: 'Public Depot',
|
|
91
|
+
summary: 'Storage at this world for every player',
|
|
92
|
+
},
|
|
93
|
+
40100101: {
|
|
94
|
+
name: 'Depot Cargo Hold I',
|
|
95
|
+
summary: 'Raises what each player can keep at the depot',
|
|
96
|
+
},
|
|
97
|
+
40100102: {
|
|
98
|
+
name: 'Depot Cargo Hold II',
|
|
99
|
+
summary: 'Raises what each player can keep at the depot',
|
|
100
|
+
},
|
|
101
|
+
40100103: {
|
|
102
|
+
name: 'Depot Cargo Hold III',
|
|
103
|
+
summary: 'Raises what each player can keep at the depot',
|
|
104
|
+
},
|
|
105
|
+
40100104: {
|
|
106
|
+
name: 'Depot Cargo Hold IV',
|
|
107
|
+
summary: 'Raises what each player can keep at the depot',
|
|
108
|
+
},
|
|
109
|
+
40100401: {
|
|
110
|
+
name: 'Depot Shuttle Bay I',
|
|
111
|
+
summary: 'Moves cargo in and out of the depot faster',
|
|
112
|
+
},
|
|
113
|
+
40100402: {
|
|
114
|
+
name: 'Depot Shuttle Bay II',
|
|
115
|
+
summary: 'Moves cargo in and out of the depot faster',
|
|
116
|
+
},
|
|
117
|
+
40100403: {
|
|
118
|
+
name: 'Depot Shuttle Bay III',
|
|
119
|
+
summary: 'Moves cargo in and out of the depot faster',
|
|
120
|
+
},
|
|
121
|
+
40100404: {
|
|
122
|
+
name: 'Depot Shuttle Bay IV',
|
|
123
|
+
summary: 'Moves cargo in and out of the depot faster',
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function charterName(nodeId: number): string {
|
|
128
|
+
return charterMetadata[nodeId]?.name ?? `Charter ${nodeId}`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function charterSummary(nodeId: number): string {
|
|
132
|
+
return charterMetadata[nodeId]?.summary ?? ''
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface CharterGateSignature {
|
|
136
|
+
kind: 'gate'
|
|
137
|
+
buildingLabel: string
|
|
138
|
+
level: number
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface CharterModuleSignature {
|
|
142
|
+
kind: 'module'
|
|
143
|
+
buildingLabel: string
|
|
144
|
+
count: number
|
|
145
|
+
rank: number
|
|
146
|
+
rankCount: number
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface CharterRungSignature {
|
|
150
|
+
kind: 'rung'
|
|
151
|
+
buildingLabel: string
|
|
152
|
+
statLabel: string
|
|
153
|
+
rank: number
|
|
154
|
+
rankCount: number
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export type CharterSignature = CharterGateSignature | CharterModuleSignature | CharterRungSignature
|
|
158
|
+
|
|
159
|
+
const ROMAN = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X']
|
|
160
|
+
|
|
161
|
+
export function romanNumeral(rank: number): string {
|
|
162
|
+
return ROMAN[rank - 1] ?? String(rank)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function headlineGrant(node: CharterNode): CharterGrant | undefined {
|
|
166
|
+
return (
|
|
167
|
+
node.grants.find((grant) => grant.kind === CIVIC_GRANT_LEVEL_GATE) ??
|
|
168
|
+
node.grants.find((grant) => grant.kind === CIVIC_GRANT_MODULE) ??
|
|
169
|
+
node.grants.find((grant) => grant.kind === CIVIC_GRANT_RUNG)
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function headlineSiblings(headline: CharterGrant): CharterNode[] {
|
|
174
|
+
return CHARTER_REGISTRY.filter((other) => {
|
|
175
|
+
const grant = headlineGrant(other)
|
|
176
|
+
return (
|
|
177
|
+
grant !== undefined &&
|
|
178
|
+
grant.kind === headline.kind &&
|
|
179
|
+
grant.building === headline.building &&
|
|
180
|
+
grant.stat === headline.stat
|
|
181
|
+
)
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function rungRank(node: CharterNode, siblings: CharterNode[]): number {
|
|
186
|
+
const byId = new Map(siblings.map((sibling) => [sibling.nodeId, sibling]))
|
|
187
|
+
const seen = new Set<number>([node.nodeId])
|
|
188
|
+
let current: CharterNode | undefined = node
|
|
189
|
+
let rank = 1
|
|
190
|
+
while (current) {
|
|
191
|
+
const previousId = current.prereqs.find((id) => byId.has(id) && !seen.has(id))
|
|
192
|
+
if (previousId === undefined) return rank
|
|
193
|
+
seen.add(previousId)
|
|
194
|
+
current = byId.get(previousId)
|
|
195
|
+
rank += 1
|
|
196
|
+
}
|
|
197
|
+
return rank
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function charterSignature(node: CharterNode): CharterSignature | undefined {
|
|
201
|
+
const grant = headlineGrant(node)
|
|
202
|
+
if (!grant) return undefined
|
|
203
|
+
if (grant.kind === CIVIC_GRANT_LEVEL_GATE) {
|
|
204
|
+
return {
|
|
205
|
+
kind: 'gate',
|
|
206
|
+
buildingLabel: civicBuildingLabel(grant.building),
|
|
207
|
+
level: grant.value,
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const siblings = headlineSiblings(grant)
|
|
211
|
+
if (grant.kind === CIVIC_GRANT_MODULE) {
|
|
212
|
+
return {
|
|
213
|
+
kind: 'module',
|
|
214
|
+
buildingLabel: civicBuildingLabel(grant.building),
|
|
215
|
+
count: grant.value,
|
|
216
|
+
rank: rungRank(node, siblings),
|
|
217
|
+
rankCount: siblings.length,
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
kind: 'rung',
|
|
222
|
+
buildingLabel: civicBuildingLabel(grant.building),
|
|
223
|
+
statLabel: civicStatLabel(grant.stat),
|
|
224
|
+
rank: rungRank(node, siblings),
|
|
225
|
+
rankCount: siblings.length,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const node of CHARTER_REGISTRY) {
|
|
230
|
+
if (node.nodeId === CHARTER_NONE) continue
|
|
231
|
+
if (!charterMetadata[node.nodeId]) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Missing charter metadata for node ${node.nodeId}. Add an entry to charter-metadata.ts.`
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -1,31 +1,39 @@
|
|
|
1
1
|
import {describe, expect, test} from 'bun:test'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CHARTER_NONE,
|
|
4
|
+
CIVIC_DEPOT,
|
|
5
|
+
CIVIC_DOCK,
|
|
6
|
+
CIVIC_STAT_CRAFT_SPEED,
|
|
7
|
+
CIVIC_STAT_TRANSFER_SPEED,
|
|
8
|
+
CIVIC_WORKSHOP,
|
|
9
|
+
} from './constants'
|
|
3
10
|
import {
|
|
4
11
|
charterEligible,
|
|
5
12
|
charterIneligible,
|
|
6
13
|
charterNode,
|
|
14
|
+
charterBuildingEntity,
|
|
15
|
+
charterGateNodeFor,
|
|
16
|
+
charterRungValue,
|
|
7
17
|
charterSingletonMandate,
|
|
8
|
-
|
|
9
|
-
effectiveMandate,
|
|
18
|
+
charterModuleCount,
|
|
10
19
|
eligibleCharters,
|
|
11
20
|
type BuiltCharter,
|
|
12
21
|
type CharterWorld,
|
|
22
|
+
type WorldBuilding,
|
|
13
23
|
} from './charters'
|
|
14
24
|
|
|
15
|
-
const WORKSHOP =
|
|
16
|
-
const NEXUS =
|
|
17
|
-
const DOCK =
|
|
18
|
-
const WORKSHOP_TUNEUP =
|
|
19
|
-
const DOCK_TUNEUP =
|
|
20
|
-
const DEPOT =
|
|
25
|
+
const WORKSHOP = 10001
|
|
26
|
+
const NEXUS = 20001
|
|
27
|
+
const DOCK = 30100001
|
|
28
|
+
const WORKSHOP_TUNEUP = 10100201
|
|
29
|
+
const DOCK_TUNEUP = 30100301
|
|
30
|
+
const DEPOT = 40100001
|
|
21
31
|
|
|
22
32
|
const WORKSHOP_ENTITY = 100n
|
|
23
33
|
const DOCK_ENTITY = 300n
|
|
24
|
-
const DEPOT_ENTITY = 600n
|
|
25
34
|
|
|
26
|
-
function world(built: BuiltCharter[]
|
|
27
|
-
|
|
28
|
-
return {built, entityExists: (id) => present.includes(id)}
|
|
35
|
+
function world(built: BuiltCharter[]): CharterWorld {
|
|
36
|
+
return {built}
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
function node(nodeId: number) {
|
|
@@ -48,13 +56,13 @@ describe('charter eligibility mirror', () => {
|
|
|
48
56
|
})
|
|
49
57
|
|
|
50
58
|
test('completing the root opens the fork and closes the singleton path', () => {
|
|
51
|
-
const built = world([{nodeId: WORKSHOP,
|
|
52
|
-
expect(eligibleIds(built)).toEqual([NEXUS, DOCK,
|
|
59
|
+
const built = world([{nodeId: WORKSHOP, repeats: 0}])
|
|
60
|
+
expect(eligibleIds(built)).toEqual([WORKSHOP_TUNEUP, NEXUS, DOCK, DEPOT])
|
|
53
61
|
expect(charterSingletonMandate(built)).toBe(CHARTER_NONE)
|
|
54
62
|
})
|
|
55
63
|
|
|
56
64
|
test('a completed charter reports already-taken', () => {
|
|
57
|
-
const built = world([{nodeId: WORKSHOP,
|
|
65
|
+
const built = world([{nodeId: WORKSHOP, repeats: 0}])
|
|
58
66
|
expect(charterIneligible(built, node(WORKSHOP))).toBe('already-taken')
|
|
59
67
|
})
|
|
60
68
|
|
|
@@ -63,64 +71,42 @@ describe('charter eligibility mirror', () => {
|
|
|
63
71
|
expect(charterIneligible(world([]), node(DOCK_TUNEUP))).toBe('prereq-missing')
|
|
64
72
|
})
|
|
65
73
|
|
|
66
|
-
test('a
|
|
67
|
-
const built = [{nodeId: WORKSHOP,
|
|
68
|
-
expect(charterEligible(world(built, [WORKSHOP_ENTITY]), node(WORKSHOP_TUNEUP))).toBe(true)
|
|
69
|
-
expect(charterIneligible(world(built, []), node(WORKSHOP_TUNEUP))).toBe(
|
|
70
|
-
'refit-target-missing'
|
|
71
|
-
)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test('a demolished target can empty an otherwise singleton eligible set', () => {
|
|
75
|
-
const built = [
|
|
76
|
-
{nodeId: WORKSHOP, entityId: WORKSHOP_ENTITY},
|
|
77
|
-
{nodeId: NEXUS, entityId: 200n},
|
|
78
|
-
{nodeId: DOCK, entityId: DOCK_ENTITY},
|
|
79
|
-
{nodeId: DOCK_TUNEUP, entityId: 0n},
|
|
80
|
-
{nodeId: DEPOT, entityId: DEPOT_ENTITY},
|
|
81
|
-
]
|
|
82
|
-
expect(charterSingletonMandate(world(built, [WORKSHOP_ENTITY, DOCK_ENTITY]))).toBe(
|
|
83
|
-
WORKSHOP_TUNEUP
|
|
84
|
-
)
|
|
85
|
-
expect(charterSingletonMandate(world(built, [DOCK_ENTITY]))).toBe(CHARTER_NONE)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
test('an absent entity predicate trusts the charter record', () => {
|
|
89
|
-
const built = world([{nodeId: WORKSHOP, entityId: WORKSHOP_ENTITY}])
|
|
74
|
+
test('a rung is eligible as soon as its prereq is complete', () => {
|
|
75
|
+
const built = world([{nodeId: WORKSHOP, repeats: 0}])
|
|
90
76
|
expect(charterEligible(built, node(WORKSHOP_TUNEUP))).toBe(true)
|
|
91
77
|
})
|
|
92
78
|
|
|
93
|
-
test('
|
|
94
|
-
expect(
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
expect(
|
|
98
|
-
expect(charterSpawnNodeFor(0)).toBeUndefined()
|
|
79
|
+
test('a level gate is the node that creates its building', () => {
|
|
80
|
+
expect(charterGateNodeFor(CIVIC_WORKSHOP)?.nodeId).toBe(WORKSHOP)
|
|
81
|
+
expect(charterGateNodeFor(CIVIC_DOCK)?.nodeId).toBe(DOCK)
|
|
82
|
+
expect(charterGateNodeFor(CIVIC_DEPOT)?.nodeId).toBe(DEPOT)
|
|
83
|
+
expect(charterGateNodeFor(99)).toBeUndefined()
|
|
99
84
|
})
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
describe('effective mandate', () => {
|
|
103
|
-
const forked = world([{nodeId: WORKSHOP, entityId: WORKSHOP_ENTITY}])
|
|
104
85
|
|
|
105
|
-
test('a
|
|
106
|
-
|
|
86
|
+
test('a building resolves to the entity recorded for its kind', () => {
|
|
87
|
+
const buildings: WorldBuilding[] = [
|
|
88
|
+
{entityId: WORKSHOP_ENTITY, building: CIVIC_WORKSHOP},
|
|
89
|
+
{entityId: DOCK_ENTITY, building: CIVIC_DOCK},
|
|
90
|
+
]
|
|
91
|
+
expect(charterBuildingEntity(buildings, CIVIC_WORKSHOP)).toBe(WORKSHOP_ENTITY)
|
|
92
|
+
expect(charterBuildingEntity(buildings, CIVIC_DEPOT)).toBe(0n)
|
|
93
|
+
expect(charterBuildingEntity([], CIVIC_DEPOT)).toBe(0n)
|
|
107
94
|
})
|
|
108
95
|
|
|
109
|
-
test('
|
|
110
|
-
|
|
111
|
-
|
|
96
|
+
test('rung values sum over the completed nodes only', () => {
|
|
97
|
+
const gateOnly = world([{nodeId: WORKSHOP, repeats: 0}])
|
|
98
|
+
expect(charterRungValue(gateOnly, CIVIC_WORKSHOP, CIVIC_STAT_CRAFT_SPEED)).toBe(200)
|
|
112
99
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
100
|
+
const tuned = world([
|
|
101
|
+
{nodeId: WORKSHOP, repeats: 0},
|
|
102
|
+
{nodeId: WORKSHOP_TUNEUP, repeats: 0},
|
|
103
|
+
])
|
|
104
|
+
expect(charterRungValue(tuned, CIVIC_WORKSHOP, CIVIC_STAT_CRAFT_SPEED)).toBe(250)
|
|
105
|
+
expect(charterRungValue(tuned, CIVIC_DEPOT, CIVIC_STAT_TRANSFER_SPEED)).toBe(0)
|
|
116
106
|
})
|
|
117
107
|
|
|
118
|
-
test('
|
|
119
|
-
expect(
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
expect(effectiveMandate({chosen: CHARTER_NONE, chosenEpoch: 0}, forked, 3)).toBe(
|
|
123
|
-
CHARTER_NONE
|
|
124
|
-
)
|
|
108
|
+
test('modules sum over the completed nodes only', () => {
|
|
109
|
+
expect(charterModuleCount(world([]), CIVIC_WORKSHOP)).toBe(0)
|
|
110
|
+
expect(charterModuleCount(world([{nodeId: WORKSHOP, repeats: 0}]), CIVIC_WORKSHOP)).toBe(5)
|
|
125
111
|
})
|
|
126
112
|
})
|