@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.
Files changed (62) hide show
  1. package/lib/shipload.d.ts +1829 -501
  2. package/lib/shipload.js +4325 -1324
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +4286 -1389
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +391 -13
  7. package/lib/testing.js +1692 -107
  8. package/lib/testing.js.map +1 -1
  9. package/lib/testing.m.js +1693 -108
  10. package/lib/testing.m.js.map +1 -1
  11. package/package.json +2 -2
  12. package/src/contracts/fund.ts +201 -0
  13. package/src/contracts/platform.ts +73 -1
  14. package/src/contracts/server.ts +946 -26
  15. package/src/contracts/token.ts +170 -0
  16. package/src/contracts.ts +2 -0
  17. package/src/data/catalog.test.ts +19 -0
  18. package/src/data/catalog.ts +6 -0
  19. package/src/data/charters.json +198 -0
  20. package/src/data/citizenry-adjectives.json +42 -0
  21. package/src/data/citizenry-nouns.json +50 -0
  22. package/src/data/entities.json +36 -0
  23. package/src/data/item-ids.ts +2 -0
  24. package/src/data/items.json +12 -0
  25. package/src/data/kind-registry.json +20 -3
  26. package/src/data/kind-registry.ts +5 -0
  27. package/src/data/metadata.ts +14 -0
  28. package/src/data/recipes.json +0 -48
  29. package/src/derivation/capabilities.ts +14 -0
  30. package/src/derivation/recipe-usage.test.ts +0 -2
  31. package/src/format.ts +9 -0
  32. package/src/index-module.ts +32 -2
  33. package/src/influence/categories.ts +32 -0
  34. package/src/influence/charters.test.ts +126 -0
  35. package/src/influence/charters.ts +120 -0
  36. package/src/influence/citizenry.ts +38 -0
  37. package/src/influence/constants.ts +63 -0
  38. package/src/influence/decay.ts +20 -0
  39. package/src/influence/decomp.ts +95 -0
  40. package/src/influence/demand.ts +106 -0
  41. package/src/influence/duration.ts +36 -0
  42. package/src/influence/index.ts +63 -0
  43. package/src/influence/influence.test.ts +317 -0
  44. package/src/influence/quality.ts +25 -0
  45. package/src/influence/valuation.ts +154 -0
  46. package/src/managers/actions.ts +227 -5
  47. package/src/managers/balances.test.ts +161 -0
  48. package/src/managers/balances.ts +93 -0
  49. package/src/managers/construction.ts +11 -1
  50. package/src/managers/context.ts +18 -0
  51. package/src/managers/entities.ts +11 -0
  52. package/src/managers/index.ts +15 -1
  53. package/src/managers/influence.test.ts +58 -0
  54. package/src/managers/influence.ts +347 -0
  55. package/src/managers/nft.test.ts +8 -1
  56. package/src/managers/nft.ts +56 -16
  57. package/src/nft/description.ts +7 -0
  58. package/src/scheduling/task-cargo.ts +1 -0
  59. package/src/shipload.ts +10 -0
  60. package/src/types.ts +23 -4
  61. package/src/utils/hash.ts +4 -0
  62. package/src/utils/system.ts +1 -5
@@ -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']},
@@ -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
+ }
@@ -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 {formatMass, formatMassDelta, formatMassScaled, formatLocation} from './format'
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
+ }