@xgamesfun/xcore 0.2.1 → 0.4.0

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.
@@ -0,0 +1,101 @@
1
+ // The numbers xgames.fun's platform holds a published game to, as `xcore dev` imitates them: one table, so a change
2
+ // on xgames.fun is one edit here. The source is xgames.fun's own plan table, the one its platform enforces (README
3
+ // "Plans and limits"; `xcore plan` shows a published game's own numbers). PLANS changes with the game's plan; RULES
4
+ // are the same on every plan. `xcore dev --plan <name>` picks the plan the dev host imitates (Free by default).
5
+ const deepFreeze = o => {
6
+ for (const v of Object.values(o)) if (v && typeof v === 'object') deepFreeze(v);
7
+ return Object.freeze(o);
8
+ };
9
+ const KB = 1024,
10
+ MB = 1024 * KB,
11
+ GB = 1024 * MB;
12
+
13
+ export const PLANS = deepFreeze({
14
+ free: {
15
+ name: 'Free',
16
+ cpuMs: 50,
17
+ rate: { player: 120 },
18
+ saves: { valueBytes: 64 * KB, keysPerPlayer: 16, gameBytes: 10 * MB, writesPerMinute: 600 },
19
+ scores: { boards: 8, rules: 16, entriesPerBoard: 10_000, submitsPerMinute: 600 },
20
+ reads: { perMinute: 1_200 },
21
+ files: { bytes: 100 * MB, fileBytes: 25 * MB, opsPerMinute: 120 },
22
+ },
23
+ creator: {
24
+ name: 'Creator',
25
+ cpuMs: 200,
26
+ rate: { player: 600 },
27
+ saves: { valueBytes: 64 * KB, keysPerPlayer: 16, gameBytes: GB, writesPerMinute: 12_000 },
28
+ scores: { boards: 16, rules: 32, entriesPerBoard: 1_000_000, submitsPerMinute: 6_000 },
29
+ reads: { perMinute: 12_000 },
30
+ files: { bytes: 10 * GB, fileBytes: 25 * MB, opsPerMinute: 1_200 },
31
+ },
32
+ scale: {
33
+ name: 'Scale',
34
+ cpuMs: 1_000,
35
+ rate: { player: 1_200 },
36
+ saves: { valueBytes: 64 * KB, keysPerPlayer: 16, gameBytes: 10 * GB, writesPerMinute: 60_000 },
37
+ scores: { boards: 16, rules: 32, entriesPerBoard: 1_000_000, submitsPerMinute: 30_000 },
38
+ reads: { perMinute: 60_000 },
39
+ files: { bytes: 100 * GB, fileBytes: 25 * MB, opsPerMinute: 6_000 },
40
+ },
41
+ });
42
+ export const PLAN_NAMES = Object.freeze(Object.keys(PLANS));
43
+ export const DEFAULT_PLAN = 'free';
44
+
45
+ export const RULES = deepFreeze({
46
+ saves: {
47
+ playerChars: 128,
48
+ // a page's writes may keep at most 1/128 of the game's save bytes for one player (never less than one save)
49
+ playerShareDivisor: 128,
50
+ // with the game's saves full, a player whose saves stay within 4 KB still writes, up to an eighth past the total
51
+ floorBytes: 4 * KB,
52
+ floorPoolDivisor: 8,
53
+ },
54
+ scores: { nameChars: 32, perPlayerEveryMs: 1_000, topMax: 100, offsetMax: 10_000, rankMax: 10_000 },
55
+ // the page's own calls (/platform/v1/*): the same on every plan
56
+ page: {
57
+ bodyBytes: 96 * KB,
58
+ saveWritesPerPlayerMinute: 60,
59
+ scoreSubmitsPerPlayerMinute: 30,
60
+ // once pages have spent the game's minute, a player with at most this many this minute still writes
61
+ saveWritesFloorPerPlayerMinute: 6,
62
+ scoreSubmitsFloorPerPlayerMinute: 3,
63
+ // boards a page makes: "high" only, two a player, and half the plan's boards for all pages together
64
+ boardsPerPlayer: 2,
65
+ boardsDivisor: 2,
66
+ // a board read with no good player token: the top 100 only, an identical read within 5 s from a cache, and a
67
+ // minute of its own, a tenth of the plan's reads
68
+ anonTopDepth: 100,
69
+ anonReadsDivisor: 10,
70
+ anonReadCacheMs: 5_000,
71
+ },
72
+ // the play edge's own limit on reads of /platform/v1 from one address (xcore dev only says it: see limits.mjs)
73
+ edge: { platformReadsPerCallerMinute: 120 },
74
+ // what GF.files.put holds in memory: a string or ArrayBuffer body, or a stream with no declared length
75
+ files: { unsizedBytes: MB },
76
+ });
77
+
78
+ /** The plan `xcore dev` imitates: 'free', 'creator' or 'scale' (any case). → {key, ...PLANS[key]}; throws on another. */
79
+ export function devPlan(name = DEFAULT_PLAN) {
80
+ const key = String(name ?? DEFAULT_PLAN)
81
+ .trim()
82
+ .toLowerCase();
83
+ if (!Object.hasOwn(PLANS, key)) throw new Error(`there is no plan "${name}": ${PLAN_NAMES.join(', ')}`);
84
+ return { key, ...PLANS[key] };
85
+ }
86
+
87
+ const count = n => n.toLocaleString('en-US');
88
+ /** A size in words: 10 MB, 1 GB, 80 KB. */
89
+ export const size = b =>
90
+ b >= GB ? `${count(b / GB)} GB` : b >= MB ? `${count(b / MB)} MB` : `${count(Math.round(b / KB))} KB`;
91
+ export { count };
92
+
93
+ /** The dev host's first line about the plan it imitates. */
94
+ export function planLine(plan) {
95
+ const others = PLAN_NAMES.filter(k => k !== plan.key).join('|');
96
+ return (
97
+ `platform: imitating the ${plan.name} plan (--plan ${others} for another): ${plan.scores.boards} boards, ` +
98
+ `${size(plan.saves.gameBytes)} of saves, ${count(plan.reads.perMinute)} reads and ` +
99
+ `${count(plan.saves.writesPerMinute)} saves a minute, ${size(plan.files.bytes)} of files`
100
+ );
101
+ }