@shipload/sdk 1.0.0-next.62 → 1.0.0-next.64
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 +1829 -501
- package/lib/shipload.js +4325 -1324
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +4286 -1389
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +391 -13
- package/lib/testing.js +1692 -107
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +1693 -108
- package/lib/testing.m.js.map +1 -1
- package/package.json +2 -2
- package/src/contracts/fund.ts +201 -0
- package/src/contracts/platform.ts +73 -1
- package/src/contracts/server.ts +946 -26
- package/src/contracts/token.ts +170 -0
- package/src/contracts.ts +2 -0
- package/src/data/catalog.test.ts +19 -0
- package/src/data/catalog.ts +6 -0
- package/src/data/charters.json +198 -0
- package/src/data/citizenry-adjectives.json +42 -0
- package/src/data/citizenry-nouns.json +50 -0
- package/src/data/entities.json +36 -0
- package/src/data/item-ids.ts +2 -0
- package/src/data/items.json +12 -0
- package/src/data/kind-registry.json +20 -3
- package/src/data/kind-registry.ts +5 -0
- package/src/data/metadata.ts +14 -0
- package/src/data/recipes.json +0 -48
- package/src/derivation/capabilities.ts +14 -0
- package/src/derivation/recipe-usage.test.ts +0 -2
- package/src/format.ts +9 -0
- package/src/index-module.ts +32 -2
- package/src/influence/categories.ts +32 -0
- package/src/influence/charters.test.ts +126 -0
- package/src/influence/charters.ts +120 -0
- package/src/influence/citizenry.ts +38 -0
- package/src/influence/constants.ts +63 -0
- package/src/influence/decay.ts +20 -0
- package/src/influence/decomp.ts +95 -0
- package/src/influence/demand.ts +106 -0
- package/src/influence/duration.ts +36 -0
- package/src/influence/index.ts +63 -0
- package/src/influence/influence.test.ts +317 -0
- package/src/influence/quality.ts +25 -0
- package/src/influence/valuation.ts +154 -0
- package/src/managers/actions.ts +227 -5
- package/src/managers/balances.test.ts +161 -0
- package/src/managers/balances.ts +93 -0
- package/src/managers/construction.ts +11 -1
- package/src/managers/context.ts +18 -0
- package/src/managers/entities.ts +11 -0
- package/src/managers/index.ts +15 -1
- package/src/managers/influence.test.ts +58 -0
- package/src/managers/influence.ts +347 -0
- package/src/managers/nft.test.ts +8 -1
- package/src/managers/nft.ts +56 -16
- package/src/nft/description.ts +7 -0
- package/src/scheduling/task-cargo.ts +1 -0
- package/src/shipload.ts +10 -0
- package/src/types.ts +23 -4
- package/src/utils/hash.ts +4 -0
- package/src/utils/system.ts +1 -5
package/src/data/metadata.ts
CHANGED
|
@@ -320,12 +320,23 @@ export const itemMetadata: Record<number, ItemMetadata> = {
|
|
|
320
320
|
'A cargo ship built around a large storage hold. The extra mass makes it slower than a Roustabout.',
|
|
321
321
|
color: '#4AE898',
|
|
322
322
|
},
|
|
323
|
+
10216: {
|
|
324
|
+
name: 'Nexus',
|
|
325
|
+
description:
|
|
326
|
+
'A permanent installation at a world where NFT-backed assets are wrapped, unwrapped, and deployed.',
|
|
327
|
+
color: '#FFD24C',
|
|
328
|
+
},
|
|
323
329
|
10218: {
|
|
324
330
|
name: 'Smith',
|
|
325
331
|
description:
|
|
326
332
|
'A crafting ship. One engine, one power core, and a fabricator for crafting away from home.',
|
|
327
333
|
color: '#4AE898',
|
|
328
334
|
},
|
|
335
|
+
10219: {
|
|
336
|
+
name: 'Depot',
|
|
337
|
+
description: 'Planetary storage facility with a loader module slot and four cargo slots.',
|
|
338
|
+
color: '#EAB308',
|
|
339
|
+
},
|
|
329
340
|
|
|
330
341
|
// === Components (T2) ===
|
|
331
342
|
11001: {
|
|
@@ -485,6 +496,9 @@ export const entityMetadata: Record<number, EntityMetadata> = {
|
|
|
485
496
|
moduleSlotLabels: ['Fabricator', 'Fabricator', 'Fabricator', 'Fabricator', 'Fabricator'],
|
|
486
497
|
},
|
|
487
498
|
10209: {moduleSlotLabels: ['Power Core', 'Assembly Arm']},
|
|
499
|
+
10219: {
|
|
500
|
+
moduleSlotLabels: ['Shuttle Bay', 'Cargo Hold', 'Cargo Hold', 'Cargo Hold', 'Cargo Hold'],
|
|
501
|
+
},
|
|
488
502
|
11212: {moduleSlotLabels: ['Power Core', 'Engine', 'Limpet Bay']},
|
|
489
503
|
11213: {moduleSlotLabels: ['Power Core', 'Engine', 'Auxiliary System', 'Limpet Bay']},
|
|
490
504
|
11214: {moduleSlotLabels: ['Power Core', 'Engine', 'Limpet Bay', 'Cargo Hold']},
|
package/src/data/recipes.json
CHANGED
|
@@ -1016,54 +1016,6 @@
|
|
|
1016
1016
|
],
|
|
1017
1017
|
"blendWeights": []
|
|
1018
1018
|
},
|
|
1019
|
-
{
|
|
1020
|
-
"outputItemId": 10208,
|
|
1021
|
-
"outputMass": 4800000,
|
|
1022
|
-
"inputs": [
|
|
1023
|
-
{
|
|
1024
|
-
"itemId": 10006,
|
|
1025
|
-
"quantity": 1000
|
|
1026
|
-
},
|
|
1027
|
-
{
|
|
1028
|
-
"itemId": 10003,
|
|
1029
|
-
"quantity": 1000
|
|
1030
|
-
},
|
|
1031
|
-
{
|
|
1032
|
-
"itemId": 10007,
|
|
1033
|
-
"quantity": 1000
|
|
1034
|
-
}
|
|
1035
|
-
],
|
|
1036
|
-
"statSlots": [
|
|
1037
|
-
{
|
|
1038
|
-
"sources": []
|
|
1039
|
-
},
|
|
1040
|
-
{
|
|
1041
|
-
"sources": [
|
|
1042
|
-
{
|
|
1043
|
-
"inputIndex": 1,
|
|
1044
|
-
"statIndex": 0
|
|
1045
|
-
}
|
|
1046
|
-
]
|
|
1047
|
-
},
|
|
1048
|
-
{
|
|
1049
|
-
"sources": [
|
|
1050
|
-
{
|
|
1051
|
-
"inputIndex": 0,
|
|
1052
|
-
"statIndex": 0
|
|
1053
|
-
}
|
|
1054
|
-
]
|
|
1055
|
-
},
|
|
1056
|
-
{
|
|
1057
|
-
"sources": [
|
|
1058
|
-
{
|
|
1059
|
-
"inputIndex": 2,
|
|
1060
|
-
"statIndex": 0
|
|
1061
|
-
}
|
|
1062
|
-
]
|
|
1063
|
-
}
|
|
1064
|
-
],
|
|
1065
|
-
"blendWeights": []
|
|
1066
|
-
},
|
|
1067
1019
|
{
|
|
1068
1020
|
"outputItemId": 10209,
|
|
1069
1021
|
"outputMass": 1900000,
|
|
@@ -233,6 +233,7 @@ export function computeBatteryCapabilities(
|
|
|
233
233
|
|
|
234
234
|
import {
|
|
235
235
|
ITEM_CONTAINER_T1_PACKED,
|
|
236
|
+
ITEM_DEPOT_T1_PACKED,
|
|
236
237
|
ITEM_SHIP_T1_PACKED,
|
|
237
238
|
ITEM_WAREHOUSE_T1_PACKED,
|
|
238
239
|
ITEM_WORKSHOP_T1_PACKED,
|
|
@@ -315,6 +316,7 @@ export function computeGathererYield(str: number, tier: number): number {
|
|
|
315
316
|
|
|
316
317
|
const BASE_CAPACITY_FN_BY_KIND: Record<string, (stats: Record<string, number>) => number> = {
|
|
317
318
|
warehouse: (stats) => computeWarehouseHullCapabilities(stats).capacity,
|
|
319
|
+
depot: (stats) => computeDepotHullCapabilities(stats).capacity,
|
|
318
320
|
workshop: (stats) => computeWorkshopHullCapabilities(stats).capacity,
|
|
319
321
|
extractor: (stats) => computeContainerCapabilities(stats).capacity,
|
|
320
322
|
factory: (stats) => computeContainerCapabilities(stats).capacity,
|
|
@@ -361,6 +363,18 @@ export function computeWarehouseHullCapabilities(stats: Record<string, number>):
|
|
|
361
363
|
}
|
|
362
364
|
}
|
|
363
365
|
|
|
366
|
+
export function computeDepotHullCapabilities(stats: Record<string, number>): {
|
|
367
|
+
hullmass: number
|
|
368
|
+
capacity: number
|
|
369
|
+
} {
|
|
370
|
+
const statSum = (stats.strength ?? 0) + (stats.hardness ?? 0)
|
|
371
|
+
const exponent = statSum / 1998.0
|
|
372
|
+
return {
|
|
373
|
+
hullmass: computeBaseHullmass(ITEM_DEPOT_T1_PACKED, stats),
|
|
374
|
+
capacity: Math.floor(50000000 * 6 ** exponent),
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
364
378
|
export function computeWorkshopHullCapabilities(stats: Record<string, number>): {
|
|
365
379
|
hullmass: number
|
|
366
380
|
capacity: number
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
ITEM_CRAFTER_T1,
|
|
16
16
|
ITEM_BUILDER_T1,
|
|
17
17
|
ITEM_EXTRACTOR_T1_PACKED,
|
|
18
|
-
ITEM_WORKSHOP_T1_PACKED,
|
|
19
18
|
ITEM_ROUSTABOUT_T1A_PACKED,
|
|
20
19
|
ITEM_PROSPECTOR_T1A_PACKED,
|
|
21
20
|
ITEM_TENDER_T1A_PACKED,
|
|
@@ -37,7 +36,6 @@ test('getRecipeConsumers lists every recipe that consumes Sensor', () => {
|
|
|
37
36
|
ITEM_CRAFTER_T1,
|
|
38
37
|
ITEM_BUILDER_T1,
|
|
39
38
|
ITEM_EXTRACTOR_T1_PACKED,
|
|
40
|
-
ITEM_WORKSHOP_T1_PACKED,
|
|
41
39
|
ITEM_ROUSTABOUT_T1A_PACKED,
|
|
42
40
|
].sort((a, b) => a - b)
|
|
43
41
|
)
|
package/src/format.ts
CHANGED
|
@@ -32,3 +32,12 @@ export function formatMassScaled(kg: number): string {
|
|
|
32
32
|
if (tonnes >= 1_000) return `${sign}${trim(tonnes / 1_000)}k t`
|
|
33
33
|
return formatMass(kg)
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
export function formatInfluence(atomic: bigint | number | string): string {
|
|
37
|
+
const points = BigInt(String(atomic)) / 10_000n
|
|
38
|
+
return `${points.toLocaleString('en-US')} pts`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function formatNeedMultiplier(needFp: bigint | number | string): string {
|
|
42
|
+
return `×${(Number(BigInt(String(needFp))) / 10_000).toFixed(1)}`
|
|
43
|
+
}
|
package/src/index-module.ts
CHANGED
|
@@ -50,14 +50,30 @@ export {
|
|
|
50
50
|
ClusterManager,
|
|
51
51
|
computeFreeCells,
|
|
52
52
|
NftManager,
|
|
53
|
+
BalancesManager,
|
|
53
54
|
ConstructionManager,
|
|
54
55
|
JobsManager,
|
|
56
|
+
InfluenceManager,
|
|
55
57
|
} from './managers'
|
|
56
58
|
export type {GridCell, ClusterCell, Cluster} from './managers'
|
|
57
59
|
export type {
|
|
60
|
+
CharterProgress,
|
|
61
|
+
ContributePreview,
|
|
62
|
+
ContributePreviewRow,
|
|
63
|
+
FoundedWorld,
|
|
64
|
+
InfluenceStanding,
|
|
65
|
+
PendingBallot,
|
|
66
|
+
VoteCast,
|
|
67
|
+
VoteOption,
|
|
68
|
+
VoteStandings,
|
|
58
69
|
PlayerRosterEntry,
|
|
59
70
|
LocationStratum,
|
|
60
71
|
NftConfigForItem,
|
|
72
|
+
WrapConfig,
|
|
73
|
+
WrapGate,
|
|
74
|
+
DepositConfig,
|
|
75
|
+
PlatformBalance,
|
|
76
|
+
TokenBalance,
|
|
61
77
|
BuildableTarget,
|
|
62
78
|
BuildState,
|
|
63
79
|
SourceEntityRef,
|
|
@@ -77,7 +93,7 @@ export type {
|
|
|
77
93
|
LaunchStatsInput,
|
|
78
94
|
} from './managers/actions'
|
|
79
95
|
export type {WrapDeposit} from './managers/nft'
|
|
80
|
-
export {resolveLockedAmount} from './managers/nft'
|
|
96
|
+
export {resolveLockedAmount, wrapCostKey} from './managers/nft'
|
|
81
97
|
|
|
82
98
|
export {
|
|
83
99
|
getItem,
|
|
@@ -88,6 +104,7 @@ export {
|
|
|
88
104
|
getModules,
|
|
89
105
|
getEntityItems,
|
|
90
106
|
resolveItemCategory,
|
|
107
|
+
itemTypeIndex,
|
|
91
108
|
typeLabel,
|
|
92
109
|
categoryLabel,
|
|
93
110
|
categoryFromIndex,
|
|
@@ -356,6 +373,7 @@ export {
|
|
|
356
373
|
ENTITY_CONTAINER,
|
|
357
374
|
ENTITY_NEXUS,
|
|
358
375
|
ENTITY_HUB,
|
|
376
|
+
ENTITY_DEPOT,
|
|
359
377
|
getEntityClass,
|
|
360
378
|
getPackedEntityType,
|
|
361
379
|
getKindMeta,
|
|
@@ -376,6 +394,7 @@ export {
|
|
|
376
394
|
isNexus,
|
|
377
395
|
isPlot,
|
|
378
396
|
isHub,
|
|
397
|
+
isDepot,
|
|
379
398
|
} from './data/kind-registry'
|
|
380
399
|
export type {EntityTypeName, KindMeta, TemplateMeta} from './data/kind-registry'
|
|
381
400
|
export * from './capabilities'
|
|
@@ -462,6 +481,7 @@ export {
|
|
|
462
481
|
computeCrafterCapabilities,
|
|
463
482
|
computeBuilderCapabilities,
|
|
464
483
|
computeWarehouseHullCapabilities,
|
|
484
|
+
computeDepotHullCapabilities,
|
|
465
485
|
computeStorageCapabilities,
|
|
466
486
|
computeBatteryCapabilities,
|
|
467
487
|
computeContainerCapabilities,
|
|
@@ -583,6 +603,7 @@ export {
|
|
|
583
603
|
computeBaseHullmass,
|
|
584
604
|
computeBaseCapacityShip,
|
|
585
605
|
computeBaseCapacityWarehouse,
|
|
606
|
+
computeBaseCapacityDepot,
|
|
586
607
|
computeBaseCapacityContainer,
|
|
587
608
|
computeEngineThrust,
|
|
588
609
|
computeEngineDrain,
|
|
@@ -613,7 +634,14 @@ export {
|
|
|
613
634
|
itemTypeCode,
|
|
614
635
|
} from './data/tiers'
|
|
615
636
|
|
|
616
|
-
export {
|
|
637
|
+
export {
|
|
638
|
+
formatInfluence,
|
|
639
|
+
formatLocation,
|
|
640
|
+
formatMass,
|
|
641
|
+
formatMassDelta,
|
|
642
|
+
formatMassScaled,
|
|
643
|
+
formatNeedMultiplier,
|
|
644
|
+
} from './format'
|
|
617
645
|
export * from './coordinates'
|
|
618
646
|
|
|
619
647
|
export {displayName, baseName, describeItem} from './resolution/display-name'
|
|
@@ -639,3 +667,5 @@ export type {
|
|
|
639
667
|
LanePlanEntry,
|
|
640
668
|
PlanTarget,
|
|
641
669
|
} from './planner'
|
|
670
|
+
|
|
671
|
+
export * from './influence'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {categoryFromIndex} from '../data/catalog'
|
|
2
|
+
import type {ResourceCategory} from '../types'
|
|
3
|
+
import {
|
|
4
|
+
RESOURCE_BIOMASS,
|
|
5
|
+
RESOURCE_CATEGORY_COUNT,
|
|
6
|
+
RESOURCE_CRYSTAL,
|
|
7
|
+
RESOURCE_GAS,
|
|
8
|
+
RESOURCE_ORE,
|
|
9
|
+
RESOURCE_REGOLITH,
|
|
10
|
+
} from './constants'
|
|
11
|
+
|
|
12
|
+
const INDEX_BY_CATEGORY: Record<ResourceCategory, number> = {
|
|
13
|
+
ore: RESOURCE_ORE,
|
|
14
|
+
gas: RESOURCE_GAS,
|
|
15
|
+
regolith: RESOURCE_REGOLITH,
|
|
16
|
+
biomass: RESOURCE_BIOMASS,
|
|
17
|
+
crystal: RESOURCE_CRYSTAL,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function categoryIndex(category: ResourceCategory): number {
|
|
21
|
+
return INDEX_BY_CATEGORY[category]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function categoryAtIndex(index: number): ResourceCategory {
|
|
25
|
+
const category = categoryFromIndex(index)
|
|
26
|
+
if (!category) throw new Error(`influence: invalid resource category index ${index}`)
|
|
27
|
+
return category
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function allCategoryIndexes(): number[] {
|
|
31
|
+
return Array.from({length: RESOURCE_CATEGORY_COUNT}, (_, i) => i)
|
|
32
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {describe, expect, test} from 'bun:test'
|
|
2
|
+
import {CHARTER_NONE} from './constants'
|
|
3
|
+
import {
|
|
4
|
+
charterEligible,
|
|
5
|
+
charterIneligible,
|
|
6
|
+
charterNode,
|
|
7
|
+
charterSingletonMandate,
|
|
8
|
+
charterSpawnNodeFor,
|
|
9
|
+
effectiveMandate,
|
|
10
|
+
eligibleCharters,
|
|
11
|
+
type BuiltCharter,
|
|
12
|
+
type CharterWorld,
|
|
13
|
+
} from './charters'
|
|
14
|
+
|
|
15
|
+
const WORKSHOP = 1
|
|
16
|
+
const NEXUS = 2
|
|
17
|
+
const DOCK = 3
|
|
18
|
+
const WORKSHOP_TUNEUP = 4
|
|
19
|
+
const DOCK_TUNEUP = 5
|
|
20
|
+
const DEPOT = 6
|
|
21
|
+
|
|
22
|
+
const WORKSHOP_ENTITY = 100n
|
|
23
|
+
const DOCK_ENTITY = 300n
|
|
24
|
+
const DEPOT_ENTITY = 600n
|
|
25
|
+
|
|
26
|
+
function world(built: BuiltCharter[], present?: bigint[]): CharterWorld {
|
|
27
|
+
if (!present) return {built}
|
|
28
|
+
return {built, entityExists: (id) => present.includes(id)}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function node(nodeId: number) {
|
|
32
|
+
const found = charterNode(nodeId)
|
|
33
|
+
if (!found) throw new Error(`registry is missing node ${nodeId}`)
|
|
34
|
+
return found
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function eligibleIds(w: CharterWorld): number[] {
|
|
38
|
+
return eligibleCharters(w).map((n) => n.nodeId)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('charter eligibility mirror', () => {
|
|
42
|
+
test('an unbuilt world offers only the root', () => {
|
|
43
|
+
expect(eligibleIds(world([]))).toEqual([WORKSHOP])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('the root is the mandate by the singleton rule with no ballot', () => {
|
|
47
|
+
expect(charterSingletonMandate(world([]))).toBe(WORKSHOP)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('completing the root opens the fork and closes the singleton path', () => {
|
|
51
|
+
const built = world([{nodeId: WORKSHOP, entityId: WORKSHOP_ENTITY}])
|
|
52
|
+
expect(eligibleIds(built)).toEqual([NEXUS, DOCK, WORKSHOP_TUNEUP, DEPOT])
|
|
53
|
+
expect(charterSingletonMandate(built)).toBe(CHARTER_NONE)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('a completed charter reports already-taken', () => {
|
|
57
|
+
const built = world([{nodeId: WORKSHOP, entityId: WORKSHOP_ENTITY}])
|
|
58
|
+
expect(charterIneligible(built, node(WORKSHOP))).toBe('already-taken')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('an unmet prereq reports prereq-missing', () => {
|
|
62
|
+
expect(charterIneligible(world([]), node(NEXUS))).toBe('prereq-missing')
|
|
63
|
+
expect(charterIneligible(world([]), node(DOCK_TUNEUP))).toBe('prereq-missing')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('a refit whose spawned target is gone reports refit-target-missing', () => {
|
|
67
|
+
const built = [{nodeId: WORKSHOP, entityId: WORKSHOP_ENTITY}]
|
|
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}])
|
|
90
|
+
expect(charterEligible(built, node(WORKSHOP_TUNEUP))).toBe(true)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('refit targets resolve through the node that spawns the item', () => {
|
|
94
|
+
expect(charterSpawnNodeFor(node(WORKSHOP_TUNEUP).effect.targetItemId)?.nodeId).toBe(
|
|
95
|
+
WORKSHOP
|
|
96
|
+
)
|
|
97
|
+
expect(charterSpawnNodeFor(node(DOCK_TUNEUP).effect.targetItemId)?.nodeId).toBe(DOCK)
|
|
98
|
+
expect(charterSpawnNodeFor(0)).toBeUndefined()
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('effective mandate', () => {
|
|
103
|
+
const forked = world([{nodeId: WORKSHOP, entityId: WORKSHOP_ENTITY}])
|
|
104
|
+
|
|
105
|
+
test('a stored choice holds inside its own epoch', () => {
|
|
106
|
+
expect(effectiveMandate({chosen: DOCK, chosenEpoch: 7}, forked, 7)).toBe(DOCK)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('the same stored choice expires one epoch later', () => {
|
|
110
|
+
expect(effectiveMandate({chosen: DOCK, chosenEpoch: 7}, forked, 8)).toBe(CHARTER_NONE)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('an expired choice falls through to the singleton rule', () => {
|
|
114
|
+
const unbuilt = world([])
|
|
115
|
+
expect(effectiveMandate({chosen: NEXUS, chosenEpoch: 7}, unbuilt, 8)).toBe(WORKSHOP)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('a cleared pair reads the singleton rule', () => {
|
|
119
|
+
expect(effectiveMandate({chosen: CHARTER_NONE, chosenEpoch: 0}, world([]), 3)).toBe(
|
|
120
|
+
WORKSHOP
|
|
121
|
+
)
|
|
122
|
+
expect(effectiveMandate({chosen: CHARTER_NONE, chosenEpoch: 0}, forked, 3)).toBe(
|
|
123
|
+
CHARTER_NONE
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import chartersJson from '../data/charters.json'
|
|
2
|
+
import {CHARTER_EFFECT_REFIT_MODULES, CHARTER_EFFECT_SPAWN_ENTITY, CHARTER_NONE} from './constants'
|
|
3
|
+
|
|
4
|
+
export interface CharterEffect {
|
|
5
|
+
kind: number
|
|
6
|
+
itemId: number
|
|
7
|
+
targetItemId: number
|
|
8
|
+
slotMask: number
|
|
9
|
+
stat: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CharterNode {
|
|
13
|
+
nodeId: number
|
|
14
|
+
cost: bigint
|
|
15
|
+
prereqs: number[]
|
|
16
|
+
effect: CharterEffect
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const CHARTER_REGISTRY: CharterNode[] = chartersJson.nodes.map((node) => ({
|
|
20
|
+
nodeId: node.nodeId,
|
|
21
|
+
cost: BigInt(node.cost),
|
|
22
|
+
prereqs: [...node.prereqs],
|
|
23
|
+
effect: {...node.effect},
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
export function charterNode(nodeId: number): CharterNode | undefined {
|
|
27
|
+
if (nodeId === CHARTER_NONE) return undefined
|
|
28
|
+
return CHARTER_REGISTRY.find((n) => n.nodeId === nodeId)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type CharterIneligibility = 'already-taken' | 'prereq-missing' | 'refit-target-missing'
|
|
32
|
+
|
|
33
|
+
export const CHARTER_INELIGIBILITY_MESSAGES: Record<CharterIneligibility, string> = {
|
|
34
|
+
'already-taken': 'charter is already completed at this world',
|
|
35
|
+
'prereq-missing': 'charter prerequisites are not completed',
|
|
36
|
+
'refit-target-missing': 'charter refit target not found at this world',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface BuiltCharter {
|
|
40
|
+
nodeId: number
|
|
41
|
+
entityId: bigint
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CharterWorld {
|
|
45
|
+
built: BuiltCharter[]
|
|
46
|
+
entityExists?: (entityId: bigint) => boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ChosenCharter {
|
|
50
|
+
chosen: number
|
|
51
|
+
chosenEpoch: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function builtCharter(world: CharterWorld, nodeId: number): BuiltCharter | undefined {
|
|
55
|
+
return world.built.find((row) => row.nodeId === nodeId)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function charterSpawnNodeFor(targetItemId: number): CharterNode | undefined {
|
|
59
|
+
return CHARTER_REGISTRY.find(
|
|
60
|
+
(node) =>
|
|
61
|
+
node.effect.kind === CHARTER_EFFECT_SPAWN_ENTITY && node.effect.itemId === targetItemId
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function charterPrereqsMet(world: CharterWorld, node: CharterNode): boolean {
|
|
66
|
+
return node.prereqs.every(
|
|
67
|
+
(prereq) => prereq === CHARTER_NONE || builtCharter(world, prereq) !== undefined
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function charterEffectTargetEntity(world: CharterWorld, node: CharterNode): bigint {
|
|
72
|
+
const spawn = charterSpawnNodeFor(node.effect.targetItemId)
|
|
73
|
+
if (!spawn) return 0n
|
|
74
|
+
const record = builtCharter(world, spawn.nodeId)
|
|
75
|
+
if (!record) return 0n
|
|
76
|
+
if (world.entityExists && !world.entityExists(record.entityId)) return 0n
|
|
77
|
+
return record.entityId
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function charterEffectTargetPresent(world: CharterWorld, node: CharterNode): boolean {
|
|
81
|
+
if (node.effect.kind !== CHARTER_EFFECT_REFIT_MODULES) return true
|
|
82
|
+
return charterEffectTargetEntity(world, node) !== 0n
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function charterIneligible(
|
|
86
|
+
world: CharterWorld,
|
|
87
|
+
node: CharterNode
|
|
88
|
+
): CharterIneligibility | undefined {
|
|
89
|
+
if (builtCharter(world, node.nodeId) !== undefined) return 'already-taken'
|
|
90
|
+
if (!charterPrereqsMet(world, node)) return 'prereq-missing'
|
|
91
|
+
if (!charterEffectTargetPresent(world, node)) return 'refit-target-missing'
|
|
92
|
+
return undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function charterEligible(world: CharterWorld, node: CharterNode): boolean {
|
|
96
|
+
return charterIneligible(world, node) === undefined
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function eligibleCharters(world: CharterWorld): CharterNode[] {
|
|
100
|
+
return CHARTER_REGISTRY.filter((node) => charterEligible(world, node))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function charterSingletonMandate(world: CharterWorld): number {
|
|
104
|
+
let only = CHARTER_NONE
|
|
105
|
+
for (const node of CHARTER_REGISTRY) {
|
|
106
|
+
if (!charterEligible(world, node)) continue
|
|
107
|
+
if (only !== CHARTER_NONE) return CHARTER_NONE
|
|
108
|
+
only = node.nodeId
|
|
109
|
+
}
|
|
110
|
+
return only
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function effectiveMandate(
|
|
114
|
+
stored: ChosenCharter,
|
|
115
|
+
world: CharterWorld,
|
|
116
|
+
epoch: number
|
|
117
|
+
): number {
|
|
118
|
+
if (stored.chosen !== CHARTER_NONE && stored.chosenEpoch === epoch) return stored.chosen
|
|
119
|
+
return charterSingletonMandate(world)
|
|
120
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {Checksum256, type Checksum256Type} from '@wharfkit/antelope'
|
|
2
|
+
import {Coordinates, type CoordinatesType, LocationType} from '../types'
|
|
3
|
+
import {hash512, uint16} from '../utils/hash'
|
|
4
|
+
import {deriveLocationStatic, getSystemName} from '../utils/system'
|
|
5
|
+
import citizenryAdjectives from '../data/citizenry-adjectives.json'
|
|
6
|
+
import citizenryNouns from '../data/citizenry-nouns.json'
|
|
7
|
+
|
|
8
|
+
const CITIZENRY_FORMS: ((world: string, noun: string, adjective: string) => string)[] = [
|
|
9
|
+
(w, n) => `${w} ${n}`,
|
|
10
|
+
(w, n, a) => `${a} ${w} ${n}`,
|
|
11
|
+
(w, n, a) => `${a} ${n} of ${w}`,
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const FORMS_WITH_ADJECTIVE = 2
|
|
15
|
+
|
|
16
|
+
export function citizenryName(
|
|
17
|
+
gameSeed: Checksum256Type,
|
|
18
|
+
coordinates: CoordinatesType
|
|
19
|
+
): string | undefined {
|
|
20
|
+
const coords = Coordinates.from(coordinates)
|
|
21
|
+
const location = deriveLocationStatic(gameSeed, coords)
|
|
22
|
+
if (Number(location.type) !== LocationType.PLANET) return undefined
|
|
23
|
+
|
|
24
|
+
const world = getSystemName(gameSeed, coords)
|
|
25
|
+
const roll = hash512(
|
|
26
|
+
Checksum256.from(gameSeed),
|
|
27
|
+
`citizenry-${coords.x.toString()}-${coords.y.toString()}`
|
|
28
|
+
)
|
|
29
|
+
const form = CITIZENRY_FORMS[roll.array[0] % CITIZENRY_FORMS.length]
|
|
30
|
+
const noun = citizenryNouns[uint16(roll, 1) % citizenryNouns.length]
|
|
31
|
+
const adjective = citizenryAdjectives[uint16(roll, 3) % citizenryAdjectives.length]
|
|
32
|
+
return form(world, noun, adjective)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function citizenryPatternCount(): number {
|
|
36
|
+
const plainForms = CITIZENRY_FORMS.length - FORMS_WITH_ADJECTIVE
|
|
37
|
+
return citizenryNouns.length * (plainForms + FORMS_WITH_ADJECTIVE * citizenryAdjectives.length)
|
|
38
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const INFLUENCE_ATOMIC_PER_POINT = 10_000
|
|
2
|
+
export const NEED_FP_SCALE = 10_000
|
|
3
|
+
export const WEIGHT_FP_SCALE = 10_000
|
|
4
|
+
export const MASS_KG_PER_TONNE = 1_000
|
|
5
|
+
|
|
6
|
+
export const INFLUENCE_WEIGHT_FP_MAX = 1_000_000n
|
|
7
|
+
export const INFLUENCE_NEED_FP_MAX = 1_000_000n
|
|
8
|
+
export const INFLUENCE_MASS_KG_MAX = 1n << 48n
|
|
9
|
+
|
|
10
|
+
export const DECAY_RETAIN_NUM = 19n
|
|
11
|
+
export const DECAY_RETAIN_DEN = 20n
|
|
12
|
+
export const DECAY_MAX_ITERATIONS = 1_024
|
|
13
|
+
|
|
14
|
+
export const W_EFF_ATOMIC_PER_KG = BigInt(INFLUENCE_ATOMIC_PER_POINT / MASS_KG_PER_TONNE)
|
|
15
|
+
|
|
16
|
+
export const DECOMP_MAX_BUCKETS = 12
|
|
17
|
+
export const DECOMP_MAX_DEPTH = 8
|
|
18
|
+
export const DECOMP_RAW_KG_MAX = 100_000_000
|
|
19
|
+
export const DECOMP_PROCESSED_KG_MAX = 100_000_000
|
|
20
|
+
|
|
21
|
+
export const RESOURCE_ORE = 0
|
|
22
|
+
export const RESOURCE_GAS = 1
|
|
23
|
+
export const RESOURCE_REGOLITH = 2
|
|
24
|
+
export const RESOURCE_BIOMASS = 3
|
|
25
|
+
export const RESOURCE_CRYSTAL = 4
|
|
26
|
+
export const RESOURCE_CATEGORY_COUNT = 5
|
|
27
|
+
|
|
28
|
+
export const CIVIC_LOADER_STAT = 213
|
|
29
|
+
export const CIVIC_LOADER_TIER = 1
|
|
30
|
+
export const DEFAULT_ORBITAL_Z = 800
|
|
31
|
+
|
|
32
|
+
export const GRADE_STANDARD = 0
|
|
33
|
+
export const GRADE_PREMIUM = 1
|
|
34
|
+
export const MINTREADY_DEFAULT_CAP = 10
|
|
35
|
+
|
|
36
|
+
export const BAR_SEED = 102_363
|
|
37
|
+
export const BAR_STEP_UNIT = 20
|
|
38
|
+
export const BAR_STEP_UP = BAR_STEP_UNIT * 9
|
|
39
|
+
export const BAR_STEP_DOWN = BAR_STEP_UNIT
|
|
40
|
+
|
|
41
|
+
export const CHARTER_NONE = 0
|
|
42
|
+
export const CHARTER_WORKSHOP = 1
|
|
43
|
+
export const CHARTER_EFFECT_SPAWN_ENTITY = 1
|
|
44
|
+
export const CHARTER_EFFECT_REFIT_MODULES = 2
|
|
45
|
+
export const VOTE_READY_DEFAULT_MAX = 20
|
|
46
|
+
export const CHARTER_WORKSHOP_COST = 2_000_000_000n
|
|
47
|
+
export const CHARTER_BASELINE_STAT = 213
|
|
48
|
+
export const CHARTER_MAX_PREREQS = 4
|
|
49
|
+
|
|
50
|
+
export const INFLUENCE_D1_SEED = 45_369
|
|
51
|
+
export const DEMAND_TRIPLE_SEED = {peak: 20_000n, base: 10_000n, floor: 1_000n}
|
|
52
|
+
|
|
53
|
+
export function poolKey(category: number, tier: number, grade: number): bigint {
|
|
54
|
+
return (BigInt(category) << 16n) | (BigInt(tier) << 8n) | BigInt(grade)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function influenceWeightKey(category: number, tier: number): bigint {
|
|
58
|
+
return (BigInt(category) << 8n) | BigInt(tier)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function atomicToPoints(atomic: bigint | number | string): number {
|
|
62
|
+
return Math.floor(Number(BigInt(atomic.toString())) / INFLUENCE_ATOMIC_PER_POINT)
|
|
63
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {DECAY_MAX_ITERATIONS, DECAY_RETAIN_DEN, DECAY_RETAIN_NUM} from './constants'
|
|
2
|
+
|
|
3
|
+
export function decayActive(active: bigint, epochsElapsed: number): bigint {
|
|
4
|
+
const iterations = epochsElapsed > DECAY_MAX_ITERATIONS ? DECAY_MAX_ITERATIONS : epochsElapsed
|
|
5
|
+
let value = active
|
|
6
|
+
for (let i = 0; i < iterations && value > 0n; i++) {
|
|
7
|
+
value = (value * DECAY_RETAIN_NUM) / DECAY_RETAIN_DEN
|
|
8
|
+
}
|
|
9
|
+
return value
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DecayableStanding {
|
|
13
|
+
active: bigint
|
|
14
|
+
lastUpdateEpoch: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function normalizeActive(standing: DecayableStanding, epoch: number): bigint {
|
|
18
|
+
const elapsed = epoch > standing.lastUpdateEpoch ? epoch - standing.lastUpdateEpoch : 0
|
|
19
|
+
return decayActive(standing.active, elapsed)
|
|
20
|
+
}
|