@shipload/sdk 1.0.0-next.34 → 1.0.0-next.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/lib/shipload.d.ts +398 -51
  2. package/lib/shipload.js +1481 -400
  3. package/lib/shipload.js.map +1 -1
  4. package/lib/shipload.m.js +1442 -401
  5. package/lib/shipload.m.js.map +1 -1
  6. package/lib/testing.d.ts +101 -20
  7. package/lib/testing.js +201 -57
  8. package/lib/testing.js.map +1 -1
  9. package/lib/testing.m.js +201 -57
  10. package/lib/testing.m.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/capabilities/crafting.ts +2 -3
  13. package/src/capabilities/gathering.test.ts +16 -0
  14. package/src/capabilities/gathering.ts +8 -11
  15. package/src/contracts/server.ts +147 -29
  16. package/src/coordinates/address.ts +88 -0
  17. package/src/coordinates/constants.test.ts +15 -0
  18. package/src/coordinates/constants.ts +23 -0
  19. package/src/coordinates/index.ts +15 -0
  20. package/src/coordinates/memo.test.ts +47 -0
  21. package/src/coordinates/memo.ts +20 -0
  22. package/src/coordinates/permutation.ts +77 -0
  23. package/src/coordinates/regions.ts +48 -0
  24. package/src/coordinates/sectors.ts +115 -0
  25. package/src/data/capability-formulas.ts +0 -1
  26. package/src/data/entities.json +4 -4
  27. package/src/data/items.json +5 -5
  28. package/src/data/recipes.json +39 -65
  29. package/src/derivation/capabilities.test.ts +133 -0
  30. package/src/derivation/capabilities.ts +66 -14
  31. package/src/derivation/rollups.test.ts +55 -0
  32. package/src/derivation/rollups.ts +56 -0
  33. package/src/derivation/wormhole.ts +115 -0
  34. package/src/entities/makers.ts +30 -3
  35. package/src/errors.ts +2 -0
  36. package/src/index-module.ts +38 -2
  37. package/src/managers/actions.ts +79 -5
  38. package/src/managers/construction.ts +6 -4
  39. package/src/managers/context.ts +9 -0
  40. package/src/managers/coordinates.ts +14 -0
  41. package/src/managers/plot.ts +2 -4
  42. package/src/nft/description.ts +25 -6
  43. package/src/planner/index.ts +127 -0
  44. package/src/planner/planner.test.ts +319 -0
  45. package/src/resolution/resolve-item.ts +4 -1
  46. package/src/scheduling/availability.ts +1 -1
  47. package/src/scheduling/cancel.test.ts +348 -0
  48. package/src/scheduling/cancel.ts +209 -0
  49. package/src/scheduling/lanes.test.ts +249 -0
  50. package/src/scheduling/lanes.ts +140 -2
  51. package/src/scheduling/projection.ts +75 -16
  52. package/src/scheduling/schedule.ts +3 -1
  53. package/src/shipload.ts +5 -0
  54. package/src/testing/projection-parity.ts +26 -2
  55. package/src/travel/travel.ts +116 -105
  56. package/src/types/capabilities.ts +23 -6
  57. package/src/types/entity.ts +3 -3
  58. package/src/types.ts +2 -1
  59. package/src/utils/system.ts +11 -0
@@ -0,0 +1,88 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {
3
+ COORD_MAX,
4
+ COORD_MIN,
5
+ COORD_OFFSET,
6
+ LOCAL_HALF,
7
+ LOCAL_MAX,
8
+ LOCAL_MIN,
9
+ REGION_DIV,
10
+ REGION_PER_AXIS,
11
+ SECTOR_DIV,
12
+ } from './constants'
13
+ import {decodeRegion, encodeRegion} from './regions'
14
+ import {decodeSector, encodeSector} from './sectors'
15
+
16
+ export interface CoordinateAddress {
17
+ sector: string
18
+ region: string
19
+ localX: number
20
+ localY: number
21
+ }
22
+
23
+ interface AxisSlices {
24
+ sector: number
25
+ region: number
26
+ local: number
27
+ }
28
+
29
+ function sliceAxis(coord: number): AxisSlices {
30
+ if (!Number.isInteger(coord) || coord < COORD_MIN || coord > COORD_MAX) {
31
+ throw new RangeError(`coordinate out of range: ${coord}`)
32
+ }
33
+ const u = coord + COORD_OFFSET
34
+ return {
35
+ sector: Math.floor(u / SECTOR_DIV),
36
+ region: Math.floor(u / REGION_DIV) % REGION_PER_AXIS,
37
+ local: (u % REGION_DIV) - LOCAL_HALF,
38
+ }
39
+ }
40
+
41
+ export function encodeAddress(seed: Checksum256Type, x: number, y: number): CoordinateAddress {
42
+ const ax = sliceAxis(x)
43
+ const ay = sliceAxis(y)
44
+ return {
45
+ sector: encodeSector(seed, ax.sector, ay.sector),
46
+ region: encodeRegion(seed, ax.region, ay.region),
47
+ localX: ax.local,
48
+ localY: ay.local,
49
+ }
50
+ }
51
+
52
+ export function decodeAddress(
53
+ seed: Checksum256Type,
54
+ addr: CoordinateAddress
55
+ ): {x: number; y: number} {
56
+ if (
57
+ !Number.isInteger(addr.localX) ||
58
+ !Number.isInteger(addr.localY) ||
59
+ addr.localX < LOCAL_MIN ||
60
+ addr.localX > LOCAL_MAX ||
61
+ addr.localY < LOCAL_MIN ||
62
+ addr.localY > LOCAL_MAX
63
+ ) {
64
+ throw new RangeError(`local position out of range: ${addr.localX}, ${addr.localY}`)
65
+ }
66
+ const sector = decodeSector(seed, addr.sector)
67
+ const region = decodeRegion(seed, addr.region)
68
+ const x =
69
+ sector.sx * SECTOR_DIV + region.rx * REGION_DIV + (addr.localX + LOCAL_HALF) - COORD_OFFSET
70
+ const y =
71
+ sector.sy * SECTOR_DIV + region.ry * REGION_DIV + (addr.localY + LOCAL_HALF) - COORD_OFFSET
72
+ if (x < COORD_MIN || x > COORD_MAX || y < COORD_MIN || y > COORD_MAX) {
73
+ throw new RangeError(`address decodes outside the coordinate range: ${x}, ${y}`)
74
+ }
75
+ return {x, y}
76
+ }
77
+
78
+ export function addressFromCoordinates(
79
+ seed: Checksum256Type,
80
+ coords: {
81
+ x: number | {toNumber(): number}
82
+ y: number | {toNumber(): number}
83
+ }
84
+ ): CoordinateAddress {
85
+ const x = typeof coords.x === 'number' ? coords.x : coords.x.toNumber()
86
+ const y = typeof coords.y === 'number' ? coords.y : coords.y.toNumber()
87
+ return encodeAddress(seed, x, y)
88
+ }
@@ -0,0 +1,15 @@
1
+ import {describe, expect, test} from 'bun:test'
2
+ import * as sdk from '../index-module'
3
+
4
+ describe('coordinate constants re-export', () => {
5
+ test('frozen spatial constants are reachable from the package root', () => {
6
+ expect(sdk.REGION_DIV).toBe(10_000)
7
+ expect(sdk.SECTOR_DIV).toBe(100_000_000)
8
+ expect(sdk.COORD_OFFSET).toBe(2_147_485_000)
9
+ expect(sdk.SECTORS_PER_AXIS).toBe(43)
10
+ expect(sdk.REGION_PER_AXIS).toBe(10_000)
11
+ expect(sdk.LOCAL_HALF).toBe(5_000)
12
+ expect(sdk.COORD_MIN).toBe(-2_147_483_648)
13
+ expect(sdk.COORD_MAX).toBe(2_147_483_647)
14
+ })
15
+ })
@@ -0,0 +1,23 @@
1
+ import type {FeistelConfig} from './permutation'
2
+
3
+ // FROZEN INTERFACE — these values define the on-the-wire address format.
4
+ export const COORD_MIN = -2_147_483_648
5
+ export const COORD_MAX = 2_147_483_647
6
+ // Centers the region grid on the origin: raw (0,0) sits at a region center (local 0,0), not a corner; u stays >= 0 across int32.
7
+ export const COORD_OFFSET = 2_147_485_000
8
+ export const AXIS_SPAN = 4_294_967_296 // 2^32 distinct values per axis
9
+
10
+ export const SECTOR_DIV = 100_000_000 // 10^8 tiles per sector side
11
+ export const REGION_DIV = 10_000 // 10^4 tiles per region side
12
+ export const SECTORS_PER_AXIS = 43 // floor((2^32 - 1) / 10^8) + 1
13
+ export const REGION_PER_AXIS = 10_000
14
+ export const LOCAL_HALF = 5_000 // half a region; local is signed, measured from the region center
15
+ export const LOCAL_MIN = -5_000
16
+ export const LOCAL_MAX = 4_999
17
+
18
+ export const SECTOR_COUNT = SECTORS_PER_AXIS * SECTORS_PER_AXIS // 1849
19
+ export const REGION_COUNT = REGION_PER_AXIS * REGION_PER_AXIS // 100,000,000
20
+
21
+ // 2*halfBits must give a block domain >= the tier's count.
22
+ export const SECTOR_FEISTEL: FeistelConfig = {n: SECTOR_COUNT, halfBits: 6, label: 'sector'} // 2^12 = 4096
23
+ export const REGION_FEISTEL: FeistelConfig = {n: REGION_COUNT, halfBits: 14, label: 'region'} // 2^28 ≈ 2.68e8
@@ -0,0 +1,15 @@
1
+ export type {CoordinateAddress} from './address'
2
+ export {addressFromCoordinates, decodeAddress, encodeAddress} from './address'
3
+ export {encodeSector, decodeSector} from './sectors'
4
+ export {encodeRegion, decodeRegion} from './regions'
5
+ export {encodeAddressMemo} from './memo'
6
+ export {
7
+ COORD_MIN,
8
+ COORD_MAX,
9
+ COORD_OFFSET,
10
+ REGION_DIV,
11
+ SECTOR_DIV,
12
+ SECTORS_PER_AXIS,
13
+ REGION_PER_AXIS,
14
+ LOCAL_HALF,
15
+ } from './constants'
@@ -0,0 +1,47 @@
1
+ import {describe, expect, test} from 'bun:test'
2
+ import {Checksum256} from '@wharfkit/antelope'
3
+ import {encodeAddress, decodeAddress} from './address'
4
+ import {encodeAddressMemo} from './memo'
5
+
6
+ const SEED = Checksum256.from('73e4385a2708e6d704883c670c50b0aa502ba1098072ed100ae1920eee3597d9')
7
+
8
+ describe('encodeAddressMemo', () => {
9
+ test('matches encodeAddress', () => {
10
+ for (const [x, y] of [
11
+ [0, 0],
12
+ [12, -9],
13
+ [2_147_483_647, -2_147_483_648],
14
+ ] as const) {
15
+ expect(encodeAddressMemo(SEED, x, y)).toEqual(encodeAddress(SEED, x, y))
16
+ }
17
+ })
18
+
19
+ test('returns the same cached object reference for repeat calls', () => {
20
+ const a = encodeAddressMemo(SEED, 4827, 1190)
21
+ const b = encodeAddressMemo(SEED, 4827, 1190)
22
+ expect(a).toBe(b)
23
+ })
24
+
25
+ test('round-trips through decodeAddress', () => {
26
+ const addr = encodeAddressMemo(SEED, -5, 7)
27
+ expect(decodeAddress(SEED, addr)).toEqual({x: -5, y: 7})
28
+ })
29
+
30
+ test('origin is centered: raw (0,0) is local (0,0)', () => {
31
+ const addr = encodeAddressMemo(SEED, 0, 0)
32
+ expect([addr.localX, addr.localY]).toEqual([0, 0])
33
+ })
34
+
35
+ test('origin neighbors share the origin region', () => {
36
+ const origin = encodeAddressMemo(SEED, 0, 0)
37
+ for (const [x, y] of [
38
+ [-1, -1],
39
+ [4999, 4999],
40
+ [-5000, -5000],
41
+ ] as const) {
42
+ expect(encodeAddressMemo(SEED, x, y).region).toBe(origin.region)
43
+ }
44
+ // first tile outside the origin region on each axis falls into a different region
45
+ expect(encodeAddressMemo(SEED, 5000, 0).region).not.toBe(origin.region)
46
+ })
47
+ })
@@ -0,0 +1,20 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {Checksum256} from '@wharfkit/antelope'
3
+ import {type CoordinateAddress, encodeAddress} from './address'
4
+
5
+ const cache = new Map<string, CoordinateAddress>()
6
+ const CACHE_MAX = 4096
7
+
8
+ export function encodeAddressMemo(seed: Checksum256Type, x: number, y: number): CoordinateAddress {
9
+ const key = `${Checksum256.from(seed).toString()}:${x},${y}`
10
+ let hit = cache.get(key)
11
+ if (!hit) {
12
+ hit = encodeAddress(seed, x, y)
13
+ if (cache.size >= CACHE_MAX) {
14
+ const oldest = cache.keys().next().value
15
+ if (oldest !== undefined) cache.delete(oldest)
16
+ }
17
+ cache.set(key, hit)
18
+ }
19
+ return hit
20
+ }
@@ -0,0 +1,77 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {hash512} from '../utils/hash'
3
+
4
+ // FROZEN INTERFACE — round count, key-derivation strings, and mixing constants must never change; per-game variation comes from the seed.
5
+ const ROUNDS = 4
6
+
7
+ export interface FeistelConfig {
8
+ n: number
9
+ halfBits: number
10
+ label: string
11
+ }
12
+
13
+ const keyCache = new Map<string, number[]>()
14
+
15
+ function deriveRoundKeys(seed: Checksum256Type, label: string): number[] {
16
+ const cacheKey = `${seed}:${label}`
17
+ const cached = keyCache.get(cacheKey)
18
+ if (cached) return cached
19
+ const h = hash512(seed, `coord-keys-${label}`).array
20
+ const keys: number[] = []
21
+ for (let i = 0; i < ROUNDS; i++) {
22
+ const o = i * 4
23
+ keys.push(((h[o] << 24) | (h[o + 1] << 16) | (h[o + 2] << 8) | h[o + 3]) >>> 0)
24
+ }
25
+ keyCache.set(cacheKey, keys)
26
+ return keys
27
+ }
28
+
29
+ function roundFn(r: number, key: number, halfBits: number): number {
30
+ let x = (r ^ key) >>> 0
31
+ x = Math.imul(x ^ (x >>> 16), 0x9e3779b1) >>> 0
32
+ x = Math.imul(x ^ (x >>> 13), 0x7feb352d) >>> 0
33
+ x = (x ^ (x >>> 16)) >>> 0
34
+ return x & ((1 << halfBits) - 1)
35
+ }
36
+
37
+ function encryptBlock(x: number, halfBits: number, keys: number[]): number {
38
+ const mask = (1 << halfBits) - 1
39
+ let L = (x >>> halfBits) & mask
40
+ let R = x & mask
41
+ for (let i = 0; i < ROUNDS; i++) {
42
+ const F = roundFn(R, keys[i], halfBits)
43
+ const nL = R
44
+ const nR = (L ^ F) & mask
45
+ L = nL
46
+ R = nR
47
+ }
48
+ return ((L << halfBits) | R) >>> 0
49
+ }
50
+
51
+ function decryptBlock(y: number, halfBits: number, keys: number[]): number {
52
+ const mask = (1 << halfBits) - 1
53
+ let L = (y >>> halfBits) & mask
54
+ let R = y & mask
55
+ for (let i = ROUNDS - 1; i >= 0; i--) {
56
+ const F = roundFn(L, keys[i], halfBits)
57
+ const nR = L
58
+ const nL = (R ^ F) & mask
59
+ L = nL
60
+ R = nR
61
+ }
62
+ return ((L << halfBits) | R) >>> 0
63
+ }
64
+
65
+ export function permute(seed: Checksum256Type, x: number, cfg: FeistelConfig): number {
66
+ const keys = deriveRoundKeys(seed, cfg.label)
67
+ let v = encryptBlock(x, cfg.halfBits, keys)
68
+ while (v >= cfg.n) v = encryptBlock(v, cfg.halfBits, keys)
69
+ return v
70
+ }
71
+
72
+ export function unpermute(seed: Checksum256Type, y: number, cfg: FeistelConfig): number {
73
+ const keys = deriveRoundKeys(seed, cfg.label)
74
+ let v = decryptBlock(y, cfg.halfBits, keys)
75
+ while (v >= cfg.n) v = decryptBlock(v, cfg.halfBits, keys)
76
+ return v
77
+ }
@@ -0,0 +1,48 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {REGION_FEISTEL, REGION_PER_AXIS} from './constants'
3
+ import {permute, unpermute} from './permutation'
4
+
5
+ // FROZEN INTERFACE — phoneme tables; tune aesthetics before launch, then never reorder.
6
+ const ONSETS = ['b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't'] // 12
7
+ const VOWELS = ['a', 'e', 'i', 'o', 'u'] // 5
8
+ const CODAS = ['n', 'r', 'l', 's', 'k', 'm', 't', 'x'] // 8
9
+ const SYL_BASE = ONSETS.length * VOWELS.length * CODAS.length // 480
10
+
11
+ function syllable(digit: number): string {
12
+ const onset = Math.floor(digit / (VOWELS.length * CODAS.length))
13
+ const rem = digit % (VOWELS.length * CODAS.length)
14
+ const vowel = Math.floor(rem / CODAS.length)
15
+ const coda = rem % CODAS.length
16
+ return ONSETS[onset] + VOWELS[vowel] + CODAS[coda]
17
+ }
18
+
19
+ function unsyllable(chunk: string): number {
20
+ const onset = ONSETS.indexOf(chunk[0])
21
+ const vowel = VOWELS.indexOf(chunk[1])
22
+ const coda = CODAS.indexOf(chunk[2])
23
+ if (onset < 0 || vowel < 0 || coda < 0) throw new Error(`invalid region token chunk: ${chunk}`)
24
+ return onset * (VOWELS.length * CODAS.length) + vowel * CODAS.length + coda
25
+ }
26
+
27
+ export function encodeRegion(seed: Checksum256Type, rx: number, ry: number): string {
28
+ const index = rx * REGION_PER_AXIS + ry
29
+ let n = permute(seed, index, REGION_FEISTEL)
30
+ const d0 = n % SYL_BASE
31
+ n = Math.floor(n / SYL_BASE)
32
+ const d1 = n % SYL_BASE
33
+ const d2 = Math.floor(n / SYL_BASE)
34
+ const token = syllable(d2) + syllable(d1) + syllable(d0)
35
+ return token.charAt(0).toUpperCase() + token.slice(1)
36
+ }
37
+
38
+ export function decodeRegion(seed: Checksum256Type, token: string): {rx: number; ry: number} {
39
+ if (token.length !== 9) throw new Error(`invalid region token length: ${token}`)
40
+ const lower = token.toLowerCase()
41
+ const d2 = unsyllable(lower.slice(0, 3))
42
+ const d1 = unsyllable(lower.slice(3, 6))
43
+ const d0 = unsyllable(lower.slice(6, 9))
44
+ const scrambled = (d2 * SYL_BASE + d1) * SYL_BASE + d0
45
+ if (scrambled >= REGION_FEISTEL.n) throw new Error(`invalid region token: ${token}`)
46
+ const index = unpermute(seed, scrambled, REGION_FEISTEL)
47
+ return {rx: Math.floor(index / REGION_PER_AXIS), ry: index % REGION_PER_AXIS}
48
+ }
@@ -0,0 +1,115 @@
1
+ import type {Checksum256Type} from '@wharfkit/antelope'
2
+ import {SECTOR_FEISTEL, SECTORS_PER_AXIS} from './constants'
3
+ import {permute, unpermute} from './permutation'
4
+
5
+ // FROZEN INTERFACE — curation seed; review before launch, then never reorder.
6
+ export const SECTOR_ADJECTIVES: readonly string[] = [
7
+ 'Amber',
8
+ 'Azure',
9
+ 'Brass',
10
+ 'Cinder',
11
+ 'Cobalt',
12
+ 'Copper',
13
+ 'Coral',
14
+ 'Crimson',
15
+ 'Crystal',
16
+ 'Dusk',
17
+ 'Ember',
18
+ 'Emerald',
19
+ 'Frost',
20
+ 'Glimmer',
21
+ 'Golden',
22
+ 'Hazy',
23
+ 'Indigo',
24
+ 'Iron',
25
+ 'Ivory',
26
+ 'Jade',
27
+ 'Lunar',
28
+ 'Misty',
29
+ 'Neon',
30
+ 'Onyx',
31
+ 'Opal',
32
+ 'Pearl',
33
+ 'Plasma',
34
+ 'Quartz',
35
+ 'Rusty',
36
+ 'Saffron',
37
+ 'Scarlet',
38
+ 'Silver',
39
+ 'Solar',
40
+ 'Static',
41
+ 'Stormy',
42
+ 'Sunny',
43
+ 'Teal',
44
+ 'Umber',
45
+ 'Velvet',
46
+ 'Verdant',
47
+ 'Vermilion',
48
+ 'Violet',
49
+ 'Wispy',
50
+ ]
51
+
52
+ export const SECTOR_NOUNS: readonly string[] = [
53
+ 'Belt',
54
+ 'Bluff',
55
+ 'Cluster',
56
+ 'Coil',
57
+ 'Crest',
58
+ 'Drift',
59
+ 'Expanse',
60
+ 'Fathom',
61
+ 'Flare',
62
+ 'Gulf',
63
+ 'Halo',
64
+ 'Haven',
65
+ 'Hollow',
66
+ 'Maw',
67
+ 'Mesa',
68
+ 'Mire',
69
+ 'Notch',
70
+ 'Nook',
71
+ 'Oasis',
72
+ 'Lagoon',
73
+ 'Peak',
74
+ 'Pocket',
75
+ 'Reach',
76
+ 'Reef',
77
+ 'Ridge',
78
+ 'Rift',
79
+ 'Run',
80
+ 'Shoal',
81
+ 'Shroud',
82
+ 'Span',
83
+ 'Spire',
84
+ 'Spur',
85
+ 'Stretch',
86
+ 'Sprawl',
87
+ 'Tangle',
88
+ 'Trace',
89
+ 'Trench',
90
+ 'Vale',
91
+ 'Vault',
92
+ 'Verge',
93
+ 'Vortex',
94
+ 'Ward',
95
+ 'Wisp',
96
+ ]
97
+
98
+ export function encodeSector(seed: Checksum256Type, sx: number, sy: number): string {
99
+ const index = sx * SECTORS_PER_AXIS + sy
100
+ const scrambled = permute(seed, index, SECTOR_FEISTEL)
101
+ const adj = Math.floor(scrambled / SECTORS_PER_AXIS)
102
+ const noun = scrambled % SECTORS_PER_AXIS
103
+ return `${SECTOR_ADJECTIVES[adj]} ${SECTOR_NOUNS[noun]}`
104
+ }
105
+
106
+ export function decodeSector(seed: Checksum256Type, name: string): {sx: number; sy: number} {
107
+ const parts = name.trim().split(/\s+/)
108
+ if (parts.length !== 2) throw new Error(`invalid sector name: ${name}`)
109
+ const adj = SECTOR_ADJECTIVES.indexOf(parts[0])
110
+ const noun = SECTOR_NOUNS.indexOf(parts[1])
111
+ if (adj < 0 || noun < 0) throw new Error(`unknown sector name: ${name}`)
112
+ const scrambled = adj * SECTORS_PER_AXIS + noun
113
+ const index = unpermute(seed, scrambled, SECTOR_FEISTEL)
114
+ return {sx: Math.floor(index / SECTORS_PER_AXIS), sy: index % SECTORS_PER_AXIS}
115
+ }
@@ -23,7 +23,6 @@ const ENTITY_HULL_SLOTS: Record<number, SlotConsumer> = {
23
23
  0: {capability: 'Storage', attribute: 'capacity'},
24
24
  1: {capability: 'Hull', attribute: 'mass'},
25
25
  2: {capability: 'Storage', attribute: 'capacity'},
26
- 3: {capability: 'Storage', attribute: 'capacity'},
27
26
  }
28
27
 
29
28
  export const SLOT_FORMULAS: Record<SlotConsumerKind, Record<number, SlotConsumer>> = {
@@ -58,11 +58,11 @@
58
58
  "slots": [
59
59
  {
60
60
  "type": "generator",
61
- "outputPct": 100
61
+ "outputPct": 200
62
62
  },
63
63
  {
64
64
  "type": "gatherer",
65
- "outputPct": 100
65
+ "outputPct": 200
66
66
  }
67
67
  ]
68
68
  },
@@ -71,11 +71,11 @@
71
71
  "slots": [
72
72
  {
73
73
  "type": "generator",
74
- "outputPct": 100
74
+ "outputPct": 200
75
75
  },
76
76
  {
77
77
  "type": "crafter",
78
- "outputPct": 100
78
+ "outputPct": 200
79
79
  }
80
80
  ]
81
81
  },
@@ -474,31 +474,31 @@
474
474
  },
475
475
  {
476
476
  "id": 10200,
477
- "mass": 1300000,
477
+ "mass": 1900000,
478
478
  "type": "entity",
479
479
  "tier": 1
480
480
  },
481
481
  {
482
482
  "id": 10201,
483
- "mass": 1900000,
483
+ "mass": 2400000,
484
484
  "type": "entity",
485
485
  "tier": 1
486
486
  },
487
487
  {
488
488
  "id": 10202,
489
- "mass": 4800000,
489
+ "mass": 3200000,
490
490
  "type": "entity",
491
491
  "tier": 1
492
492
  },
493
493
  {
494
494
  "id": 10203,
495
- "mass": 3700000,
495
+ "mass": 1900000,
496
496
  "type": "entity",
497
497
  "tier": 1
498
498
  },
499
499
  {
500
500
  "id": 10204,
501
- "mass": 4600000,
501
+ "mass": 1900000,
502
502
  "type": "entity",
503
503
  "tier": 1
504
504
  },