volleyballsimtypes 0.0.493 → 0.0.495
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/dist/cjs/src/data/models/competition.d.ts +7 -1
- package/dist/cjs/src/service/player/improvement.d.ts +30 -2
- package/dist/cjs/src/service/player/improvement.js +113 -48
- package/dist/cjs/src/service/player/improvement.test.js +118 -41
- package/dist/cjs/src/service/team/schemas/designated-sub.z.js +5 -1
- package/dist/cjs/src/service/team/schemas/designated-sub.z.test.js +3 -1
- package/dist/cjs/src/stat-config/improvement-floors.json +394 -0
- package/dist/cjs/src/stat-config/improvement-floors.test.d.ts +1 -0
- package/dist/cjs/src/stat-config/improvement-floors.test.js +78 -0
- package/dist/cjs/src/stat-config/index.d.ts +2 -1
- package/dist/cjs/src/stat-config/index.js +5 -1
- package/dist/esm/src/data/models/competition.d.ts +7 -1
- package/dist/esm/src/service/player/improvement.d.ts +30 -2
- package/dist/esm/src/service/player/improvement.js +111 -48
- package/dist/esm/src/service/player/improvement.test.js +120 -43
- package/dist/esm/src/service/team/schemas/designated-sub.z.js +5 -1
- package/dist/esm/src/service/team/schemas/designated-sub.z.test.js +3 -1
- package/dist/esm/src/stat-config/improvement-floors.json +394 -0
- package/dist/esm/src/stat-config/improvement-floors.test.d.ts +1 -0
- package/dist/esm/src/stat-config/improvement-floors.test.js +76 -0
- package/dist/esm/src/stat-config/index.d.ts +2 -1
- package/dist/esm/src/stat-config/index.js +4 -1
- package/package.json +7 -2
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "GENERATED FILE, do not hand-edit. Regenerate with: yarn build && yarn floors",
|
|
3
|
+
"samplesPerRarity": 400000,
|
|
4
|
+
"inputs": {
|
|
5
|
+
"wishlistAdoption": 0.3733,
|
|
6
|
+
"wishlistProcRate": 0.4,
|
|
7
|
+
"rolePickWeights": {
|
|
8
|
+
"OUTSIDE_HITTER": 0.269,
|
|
9
|
+
"MIDDLE_BLOCKER": 0.235,
|
|
10
|
+
"OPPOSITE_HITTER": 0.188,
|
|
11
|
+
"LIBERO": 0.171,
|
|
12
|
+
"SETTER": 0.137
|
|
13
|
+
},
|
|
14
|
+
"measuredFrom": "prod read-only 2026-07-24: 434 Wishlist rows, 162 with roles; role listing shares"
|
|
15
|
+
},
|
|
16
|
+
"sourceHash": "ec4620ca048d05741b9c42ce6f24a4e7bd04f047c5a420bb93f5a5e711d7adeb",
|
|
17
|
+
"floors": [
|
|
18
|
+
{
|
|
19
|
+
"rarity": "COMMON",
|
|
20
|
+
"role": "SETTER",
|
|
21
|
+
"samples": 104658,
|
|
22
|
+
"roleBranchShare": 0,
|
|
23
|
+
"all": 540.2,
|
|
24
|
+
"focus": {
|
|
25
|
+
"ATTACK": 172.3,
|
|
26
|
+
"SET": 136.7,
|
|
27
|
+
"RECEIVE": 172.6,
|
|
28
|
+
"SERVE": 93.9,
|
|
29
|
+
"BLOCK": 175.8,
|
|
30
|
+
"STAMINA": 28.6
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"rarity": "COMMON",
|
|
35
|
+
"role": "OUTSIDE_HITTER",
|
|
36
|
+
"samples": 1886,
|
|
37
|
+
"roleBranchShare": 0,
|
|
38
|
+
"all": 545.7,
|
|
39
|
+
"focus": {
|
|
40
|
+
"ATTACK": 183.8,
|
|
41
|
+
"SET": 100,
|
|
42
|
+
"RECEIVE": 183.7,
|
|
43
|
+
"SERVE": 102.4,
|
|
44
|
+
"BLOCK": 181.6,
|
|
45
|
+
"STAMINA": 32.7
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"rarity": "COMMON",
|
|
50
|
+
"role": "MIDDLE_BLOCKER",
|
|
51
|
+
"samples": 99825,
|
|
52
|
+
"roleBranchShare": 0,
|
|
53
|
+
"all": 541,
|
|
54
|
+
"focus": {
|
|
55
|
+
"ATTACK": 177.9,
|
|
56
|
+
"SET": 113,
|
|
57
|
+
"RECEIVE": 174.5,
|
|
58
|
+
"SERVE": 86.9,
|
|
59
|
+
"BLOCK": 198.3,
|
|
60
|
+
"STAMINA": 30.4
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"rarity": "COMMON",
|
|
65
|
+
"role": "OPPOSITE_HITTER",
|
|
66
|
+
"samples": 85837,
|
|
67
|
+
"roleBranchShare": 0,
|
|
68
|
+
"all": 537,
|
|
69
|
+
"focus": {
|
|
70
|
+
"ATTACK": 200.9,
|
|
71
|
+
"SET": 112,
|
|
72
|
+
"RECEIVE": 172.9,
|
|
73
|
+
"SERVE": 92.6,
|
|
74
|
+
"BLOCK": 170.4,
|
|
75
|
+
"STAMINA": 28.5
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"rarity": "COMMON",
|
|
80
|
+
"role": "LIBERO",
|
|
81
|
+
"samples": 107794,
|
|
82
|
+
"roleBranchShare": 0,
|
|
83
|
+
"all": 541.4,
|
|
84
|
+
"focus": {
|
|
85
|
+
"ATTACK": 173.1,
|
|
86
|
+
"SET": 117.3,
|
|
87
|
+
"RECEIVE": 197.8,
|
|
88
|
+
"SERVE": 86.9,
|
|
89
|
+
"BLOCK": 174.4,
|
|
90
|
+
"STAMINA": 32.1
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"rarity": "RARE",
|
|
95
|
+
"role": "SETTER",
|
|
96
|
+
"samples": 92252,
|
|
97
|
+
"roleBranchShare": 0.0888,
|
|
98
|
+
"all": 865.3,
|
|
99
|
+
"focus": {
|
|
100
|
+
"ATTACK": 269,
|
|
101
|
+
"SET": 239.4,
|
|
102
|
+
"RECEIVE": 299.7,
|
|
103
|
+
"SERVE": 176.2,
|
|
104
|
+
"BLOCK": 299.9,
|
|
105
|
+
"STAMINA": 40.2
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"rarity": "RARE",
|
|
110
|
+
"role": "OUTSIDE_HITTER",
|
|
111
|
+
"samples": 18916,
|
|
112
|
+
"roleBranchShare": 0.8573,
|
|
113
|
+
"all": 691.4,
|
|
114
|
+
"focus": {
|
|
115
|
+
"ATTACK": 237.8,
|
|
116
|
+
"SET": 114.5,
|
|
117
|
+
"RECEIVE": 210.9,
|
|
118
|
+
"SERVE": 119.5,
|
|
119
|
+
"BLOCK": 207.7,
|
|
120
|
+
"STAMINA": 42.7
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"rarity": "RARE",
|
|
125
|
+
"role": "MIDDLE_BLOCKER",
|
|
126
|
+
"samples": 92272,
|
|
127
|
+
"roleBranchShare": 0.1525,
|
|
128
|
+
"all": 899.1,
|
|
129
|
+
"focus": {
|
|
130
|
+
"ATTACK": 306.8,
|
|
131
|
+
"SET": 201.7,
|
|
132
|
+
"RECEIVE": 303.5,
|
|
133
|
+
"SERVE": 149.3,
|
|
134
|
+
"BLOCK": 339.8,
|
|
135
|
+
"STAMINA": 42.6
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"rarity": "RARE",
|
|
140
|
+
"role": "OPPOSITE_HITTER",
|
|
141
|
+
"samples": 100116,
|
|
142
|
+
"roleBranchShare": 0.1141,
|
|
143
|
+
"all": 895.9,
|
|
144
|
+
"focus": {
|
|
145
|
+
"ATTACK": 352.3,
|
|
146
|
+
"SET": 208.1,
|
|
147
|
+
"RECEIVE": 293.3,
|
|
148
|
+
"SERVE": 173.1,
|
|
149
|
+
"BLOCK": 273.9,
|
|
150
|
+
"STAMINA": 40.3
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"rarity": "RARE",
|
|
155
|
+
"role": "LIBERO",
|
|
156
|
+
"samples": 96444,
|
|
157
|
+
"roleBranchShare": 0.1067,
|
|
158
|
+
"all": 886.5,
|
|
159
|
+
"focus": {
|
|
160
|
+
"ATTACK": 272,
|
|
161
|
+
"SET": 225.8,
|
|
162
|
+
"RECEIVE": 349.9,
|
|
163
|
+
"SERVE": 154.5,
|
|
164
|
+
"BLOCK": 301.7,
|
|
165
|
+
"STAMINA": 44.8
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"rarity": "LEGENDARY",
|
|
170
|
+
"role": "SETTER",
|
|
171
|
+
"samples": 68986,
|
|
172
|
+
"roleBranchShare": 0.119,
|
|
173
|
+
"all": 1077.5,
|
|
174
|
+
"focus": {
|
|
175
|
+
"ATTACK": 368.2,
|
|
176
|
+
"SET": 285.7,
|
|
177
|
+
"RECEIVE": 362.8,
|
|
178
|
+
"SERVE": 186.9,
|
|
179
|
+
"BLOCK": 355.3,
|
|
180
|
+
"STAMINA": 48.7
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"rarity": "LEGENDARY",
|
|
185
|
+
"role": "OUTSIDE_HITTER",
|
|
186
|
+
"samples": 16108,
|
|
187
|
+
"roleBranchShare": 0.9988,
|
|
188
|
+
"all": 845.7,
|
|
189
|
+
"focus": {
|
|
190
|
+
"ATTACK": 290.2,
|
|
191
|
+
"SET": 140.7,
|
|
192
|
+
"RECEIVE": 250.7,
|
|
193
|
+
"SERVE": 145.4,
|
|
194
|
+
"BLOCK": 250.5,
|
|
195
|
+
"STAMINA": 49.5
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"rarity": "LEGENDARY",
|
|
200
|
+
"role": "MIDDLE_BLOCKER",
|
|
201
|
+
"samples": 116110,
|
|
202
|
+
"roleBranchShare": 0.1202,
|
|
203
|
+
"all": 1090,
|
|
204
|
+
"focus": {
|
|
205
|
+
"ATTACK": 361.5,
|
|
206
|
+
"SET": 242.2,
|
|
207
|
+
"RECEIVE": 379.6,
|
|
208
|
+
"SERVE": 155.6,
|
|
209
|
+
"BLOCK": 414.7,
|
|
210
|
+
"STAMINA": 49.7
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"rarity": "LEGENDARY",
|
|
215
|
+
"role": "OPPOSITE_HITTER",
|
|
216
|
+
"samples": 83884,
|
|
217
|
+
"roleBranchShare": 0.1336,
|
|
218
|
+
"all": 1098.1,
|
|
219
|
+
"focus": {
|
|
220
|
+
"ATTACK": 428.8,
|
|
221
|
+
"SET": 255.3,
|
|
222
|
+
"RECEIVE": 350.8,
|
|
223
|
+
"SERVE": 182.8,
|
|
224
|
+
"BLOCK": 347.6,
|
|
225
|
+
"STAMINA": 49
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"rarity": "LEGENDARY",
|
|
230
|
+
"role": "LIBERO",
|
|
231
|
+
"samples": 114912,
|
|
232
|
+
"roleBranchShare": 0.089,
|
|
233
|
+
"all": 1098.7,
|
|
234
|
+
"focus": {
|
|
235
|
+
"ATTACK": 358.7,
|
|
236
|
+
"SET": 260.7,
|
|
237
|
+
"RECEIVE": 421.5,
|
|
238
|
+
"SERVE": 157.8,
|
|
239
|
+
"BLOCK": 381.3,
|
|
240
|
+
"STAMINA": 50.2
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"rarity": "MYTHIC",
|
|
245
|
+
"role": "SETTER",
|
|
246
|
+
"samples": 99645,
|
|
247
|
+
"roleBranchShare": 0.0824,
|
|
248
|
+
"all": 1246.4,
|
|
249
|
+
"focus": {
|
|
250
|
+
"ATTACK": 410.6,
|
|
251
|
+
"SET": 324.7,
|
|
252
|
+
"RECEIVE": 413.3,
|
|
253
|
+
"SERVE": 236.4,
|
|
254
|
+
"BLOCK": 404,
|
|
255
|
+
"STAMINA": 66.7
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"rarity": "MYTHIC",
|
|
260
|
+
"role": "OUTSIDE_HITTER",
|
|
261
|
+
"samples": 15991,
|
|
262
|
+
"roleBranchShare": 1,
|
|
263
|
+
"all": 962.4,
|
|
264
|
+
"focus": {
|
|
265
|
+
"ATTACK": 326.1,
|
|
266
|
+
"SET": 157,
|
|
267
|
+
"RECEIVE": 281.9,
|
|
268
|
+
"SERVE": 162.7,
|
|
269
|
+
"BLOCK": 282.2,
|
|
270
|
+
"STAMINA": 66.9
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"rarity": "MYTHIC",
|
|
275
|
+
"role": "MIDDLE_BLOCKER",
|
|
276
|
+
"samples": 92022,
|
|
277
|
+
"roleBranchShare": 0.1535,
|
|
278
|
+
"all": 1251.8,
|
|
279
|
+
"focus": {
|
|
280
|
+
"ATTACK": 393.6,
|
|
281
|
+
"SET": 266.9,
|
|
282
|
+
"RECEIVE": 421.4,
|
|
283
|
+
"SERVE": 210.1,
|
|
284
|
+
"BLOCK": 472.7,
|
|
285
|
+
"STAMINA": 67.2
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"rarity": "MYTHIC",
|
|
290
|
+
"role": "OPPOSITE_HITTER",
|
|
291
|
+
"samples": 99024,
|
|
292
|
+
"roleBranchShare": 0.1126,
|
|
293
|
+
"all": 1272.8,
|
|
294
|
+
"focus": {
|
|
295
|
+
"ATTACK": 485.3,
|
|
296
|
+
"SET": 288.2,
|
|
297
|
+
"RECEIVE": 396.8,
|
|
298
|
+
"SERVE": 234.5,
|
|
299
|
+
"BLOCK": 389.7,
|
|
300
|
+
"STAMINA": 66.7
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"rarity": "MYTHIC",
|
|
305
|
+
"role": "LIBERO",
|
|
306
|
+
"samples": 93318,
|
|
307
|
+
"roleBranchShare": 0.1088,
|
|
308
|
+
"all": 1267.3,
|
|
309
|
+
"focus": {
|
|
310
|
+
"ATTACK": 391.6,
|
|
311
|
+
"SET": 290.9,
|
|
312
|
+
"RECEIVE": 482.1,
|
|
313
|
+
"SERVE": 215.1,
|
|
314
|
+
"BLOCK": 426.2,
|
|
315
|
+
"STAMINA": 67.5
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"rarity": "SPECIAL",
|
|
320
|
+
"role": "SETTER",
|
|
321
|
+
"samples": 102997,
|
|
322
|
+
"roleBranchShare": 0.0804,
|
|
323
|
+
"all": 1457.3,
|
|
324
|
+
"focus": {
|
|
325
|
+
"ATTACK": 485.2,
|
|
326
|
+
"SET": 360.5,
|
|
327
|
+
"RECEIVE": 481.4,
|
|
328
|
+
"SERVE": 265.7,
|
|
329
|
+
"BLOCK": 475.5,
|
|
330
|
+
"STAMINA": 79
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"rarity": "SPECIAL",
|
|
335
|
+
"role": "OUTSIDE_HITTER",
|
|
336
|
+
"samples": 16546,
|
|
337
|
+
"roleBranchShare": 0.9652,
|
|
338
|
+
"all": 1100.6,
|
|
339
|
+
"focus": {
|
|
340
|
+
"ATTACK": 371.9,
|
|
341
|
+
"SET": 183.9,
|
|
342
|
+
"RECEIVE": 323.6,
|
|
343
|
+
"SERVE": 187.1,
|
|
344
|
+
"BLOCK": 323.7,
|
|
345
|
+
"STAMINA": 79.6
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
"rarity": "SPECIAL",
|
|
350
|
+
"role": "MIDDLE_BLOCKER",
|
|
351
|
+
"samples": 87715,
|
|
352
|
+
"roleBranchShare": 0.1592,
|
|
353
|
+
"all": 1440.2,
|
|
354
|
+
"focus": {
|
|
355
|
+
"ATTACK": 463.7,
|
|
356
|
+
"SET": 309.9,
|
|
357
|
+
"RECEIVE": 482.8,
|
|
358
|
+
"SERVE": 234.3,
|
|
359
|
+
"BLOCK": 522.8,
|
|
360
|
+
"STAMINA": 79.5
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"rarity": "SPECIAL",
|
|
365
|
+
"role": "OPPOSITE_HITTER",
|
|
366
|
+
"samples": 102830,
|
|
367
|
+
"roleBranchShare": 0.1096,
|
|
368
|
+
"all": 1469.1,
|
|
369
|
+
"focus": {
|
|
370
|
+
"ATTACK": 536.1,
|
|
371
|
+
"SET": 331.8,
|
|
372
|
+
"RECEIVE": 467.1,
|
|
373
|
+
"SERVE": 262.7,
|
|
374
|
+
"BLOCK": 463.9,
|
|
375
|
+
"STAMINA": 79.2
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"rarity": "SPECIAL",
|
|
380
|
+
"role": "LIBERO",
|
|
381
|
+
"samples": 89912,
|
|
382
|
+
"roleBranchShare": 0.1147,
|
|
383
|
+
"all": 1462.7,
|
|
384
|
+
"focus": {
|
|
385
|
+
"ATTACK": 464.9,
|
|
386
|
+
"SET": 332.7,
|
|
387
|
+
"RECEIVE": 531.3,
|
|
388
|
+
"SERVE": 240.9,
|
|
389
|
+
"BLOCK": 490.4,
|
|
390
|
+
"STAMINA": 80.3
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
]
|
|
394
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globals_1 = require("@jest/globals");
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const index_1 = require("./index");
|
|
6
|
+
const rarity_1 = require("../service/player/rarity");
|
|
7
|
+
const role_1 = require("../service/player/role");
|
|
8
|
+
const stats_1 = require("../service/player/stats");
|
|
9
|
+
const improvement_1 = require("../service/player/improvement");
|
|
10
|
+
// improvement-floors.json is GENERATED (scripts/generate-improvement-floors.mjs) by sampling PlayerGenerator.
|
|
11
|
+
// Nothing fails loudly if the generator's inputs change and the file is not regenerated, so the floors would
|
|
12
|
+
// silently misprice every improvement in the game. These are the guards.
|
|
13
|
+
//
|
|
14
|
+
// The drift guard is a HASH OF THE PARSED VALUES the floors depend on, not of any source text: hashing source
|
|
15
|
+
// would fail on a comment edit and force a regeneration that rewrites every number with fresh sampling noise.
|
|
16
|
+
// It therefore catches a retuned role weight, stat cap or rarity range, and does NOT catch a pure logic change
|
|
17
|
+
// inside player-generator.ts. That case is covered by re-running the generator deliberately.
|
|
18
|
+
const RARITIES = [rarity_1.RarityEnum.COMMON, rarity_1.RarityEnum.RARE, rarity_1.RarityEnum.LEGENDARY, rarity_1.RarityEnum.MYTHIC, rarity_1.RarityEnum.SPECIAL];
|
|
19
|
+
const ROLES = [role_1.RoleEnum.SETTER, role_1.RoleEnum.OUTSIDE_HITTER, role_1.RoleEnum.MIDDLE_BLOCKER, role_1.RoleEnum.OPPOSITE_HITTER, role_1.RoleEnum.LIBERO];
|
|
20
|
+
const FOCI = [stats_1.StatsEnum.ATTACK, stats_1.StatsEnum.SET, stats_1.StatsEnum.RECEIVE, stats_1.StatsEnum.SERVE, stats_1.StatsEnum.BLOCK, stats_1.StatsEnum.STAMINA];
|
|
21
|
+
const REGENERATE = 'improvement-floors.json is stale. Run: yarn build && yarn floors';
|
|
22
|
+
(0, globals_1.describe)('improvement-floors.json', () => {
|
|
23
|
+
(0, globals_1.it)('was generated from the current roles, caps and rarity ranges', () => {
|
|
24
|
+
const canonical = JSON.stringify({
|
|
25
|
+
roles: index_1.rolesJSON,
|
|
26
|
+
statCaps: index_1.statCapsJSON,
|
|
27
|
+
rarityRanges: rarity_1.RarityRanges,
|
|
28
|
+
staminaRanges: rarity_1.StaminaRanges
|
|
29
|
+
});
|
|
30
|
+
const expected = (0, crypto_1.createHash)('sha256').update(canonical).digest('hex');
|
|
31
|
+
(0, globals_1.expect)(`${index_1.improvementFloorsJSON.sourceHash} ${REGENERATE}`).toBe(`${expected} ${REGENERATE}`);
|
|
32
|
+
});
|
|
33
|
+
(0, globals_1.it)('has exactly one entry per rarity and role', () => {
|
|
34
|
+
(0, globals_1.expect)(index_1.improvementFloorsJSON.floors).toHaveLength(RARITIES.length * ROLES.length);
|
|
35
|
+
for (const rarity of RARITIES) {
|
|
36
|
+
for (const role of ROLES) {
|
|
37
|
+
const cell = index_1.improvementFloorsJSON.floors.filter(f => f.rarity === rarity && f.role === role);
|
|
38
|
+
(0, globals_1.expect)(`${rarity}/${role}: ${cell.length}`).toBe(`${rarity}/${role}: 1`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
(0, globals_1.it)('carries a floor for every training focus, matching the focus pools exactly', () => {
|
|
43
|
+
for (const entry of index_1.improvementFloorsJSON.floors) {
|
|
44
|
+
const keys = Object.keys(entry.focus).sort();
|
|
45
|
+
(0, globals_1.expect)(`${entry.rarity}/${entry.role}: ${keys.join(',')}`).toBe(`${entry.rarity}/${entry.role}: ${[...FOCI].sort().join(',')}`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
(0, globals_1.it)('keeps every floor strictly below its ceiling, so no band is degenerate', () => {
|
|
49
|
+
for (const entry of index_1.improvementFloorsJSON.floors) {
|
|
50
|
+
const cap = (0, improvement_1.getStatCap)(entry.rarity);
|
|
51
|
+
const label = `${entry.rarity}/${entry.role}`;
|
|
52
|
+
(0, globals_1.expect)(`${label} all: ${entry.all < 18 * cap ? 'below' : 'AT OR ABOVE'} ceiling`).toBe(`${label} all: below ceiling`);
|
|
53
|
+
(0, globals_1.expect)(entry.all).toBeGreaterThan(0);
|
|
54
|
+
for (const focus of FOCI) {
|
|
55
|
+
const poolSize = Object.keys((0, stats_1.getMultipliers)(focus)).length;
|
|
56
|
+
const floor = entry.focus[focus];
|
|
57
|
+
(0, globals_1.expect)(`${label} ${focus}: ${floor < poolSize * cap ? 'below' : 'AT OR ABOVE'} ceiling`).toBe(`${label} ${focus}: below ceiling`);
|
|
58
|
+
(0, globals_1.expect)(floor).toBeGreaterThan(0);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
(0, globals_1.it)('was sampled hard enough for every cell to be trustworthy', () => {
|
|
63
|
+
for (const entry of index_1.improvementFloorsJSON.floors) {
|
|
64
|
+
// 500 keeps the standard error of a cell mean well under a stat point; the generator warns below this.
|
|
65
|
+
(0, globals_1.expect)(`${entry.rarity}/${entry.role}: ${entry.samples >= 500 ? 'enough' : `only ${entry.samples}`} samples`)
|
|
66
|
+
.toBe(`${entry.rarity}/${entry.role}: enough samples`);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
(0, globals_1.it)('records the production inputs the mixture was built from', () => {
|
|
70
|
+
(0, globals_1.expect)(index_1.improvementFloorsJSON.inputs.wishlistProcRate).toBeGreaterThan(0);
|
|
71
|
+
(0, globals_1.expect)(index_1.improvementFloorsJSON.inputs.wishlistAdoption).toBeGreaterThan(0);
|
|
72
|
+
(0, globals_1.expect)(index_1.improvementFloorsJSON.inputs.wishlistAdoption).toBeLessThanOrEqual(1);
|
|
73
|
+
// The role branch only exists for RARE and above, so COMMON must be pure generic branch.
|
|
74
|
+
for (const entry of index_1.improvementFloorsJSON.floors.filter(f => f.rarity === rarity_1.RarityEnum.COMMON)) {
|
|
75
|
+
(0, globals_1.expect)(`COMMON/${entry.role} roleBranchShare: ${entry.roleBranchShare}`).toBe(`COMMON/${entry.role} roleBranchShare: 0`);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -2,4 +2,5 @@ import rolesJSON from './roles.json';
|
|
|
2
2
|
import traitsJSON from './traits.json';
|
|
3
3
|
import statCapsJSON from './stat-caps.json';
|
|
4
4
|
import declineProfilesJSON from './decline-profiles.json';
|
|
5
|
-
|
|
5
|
+
import improvementFloorsJSON from './improvement-floors.json';
|
|
6
|
+
export { rolesJSON, traitsJSON, statCapsJSON, declineProfilesJSON, improvementFloorsJSON };
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.declineProfilesJSON = exports.statCapsJSON = exports.traitsJSON = exports.rolesJSON = void 0;
|
|
6
|
+
exports.improvementFloorsJSON = exports.declineProfilesJSON = exports.statCapsJSON = exports.traitsJSON = exports.rolesJSON = void 0;
|
|
7
7
|
const roles_json_1 = __importDefault(require("./roles.json"));
|
|
8
8
|
exports.rolesJSON = roles_json_1.default;
|
|
9
9
|
const traits_json_1 = __importDefault(require("./traits.json"));
|
|
@@ -12,3 +12,7 @@ const stat_caps_json_1 = __importDefault(require("./stat-caps.json"));
|
|
|
12
12
|
exports.statCapsJSON = stat_caps_json_1.default;
|
|
13
13
|
const decline_profiles_json_1 = __importDefault(require("./decline-profiles.json"));
|
|
14
14
|
exports.declineProfilesJSON = decline_profiles_json_1.default;
|
|
15
|
+
// GENERATED, never hand-edited. Regenerate with `yarn build && yarn floors`; a stale file is caught by the
|
|
16
|
+
// sourceHash assertion in improvement-floors.test.ts.
|
|
17
|
+
const improvement_floors_json_1 = __importDefault(require("./improvement-floors.json"));
|
|
18
|
+
exports.improvementFloorsJSON = improvement_floors_json_1.default;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Sequelize from 'sequelize';
|
|
2
2
|
import { Model } from 'sequelize';
|
|
3
|
-
import { CompetitionChampionAttributes, CompetitionChampionId, CompetitionChampionModel, CompetitionMVPAttributes, CompetitionMVPId, CompetitionMVPModel, CompetitionMatchAttributes, CompetitionMatchId, CompetitionMatchModel, CompetitionStandingsAttributes, CompetitionStandingsId, CompetitionStandingsModel, CompetitionTeamsAttributes, CompetitionTeamsId, CompetitionTeamsModel, PromotionMatchAttributes, PromotionMatchId, PromotionMatchModel, RegionQualifierAttributes, RegionQualifierId, RegionQualifierModel, NationalCountryAttributes, NationalCountryId, NationalCountryModel, DivisionSeasonAttributes, DivisionSeasonId, DivisionSeasonModel, IterationId, IterationModel, TeamId, TeamModel } from '.';
|
|
3
|
+
import { CompetitionChampionAttributes, CompetitionChampionId, CompetitionChampionModel, CompetitionMVPAttributes, CompetitionMVPId, CompetitionMVPModel, CompetitionMatchAttributes, CompetitionMatchId, CompetitionMatchModel, CompetitionStandingsAttributes, CompetitionStandingsId, CompetitionStandingsModel, CompetitionTeamsAttributes, CompetitionTeamsId, CompetitionTeamsModel, PromotionMatchAttributes, PromotionMatchId, PromotionMatchModel, RegionQualifierAttributes, RegionQualifierId, RegionQualifierModel, NationalCountryAttributes, NationalCountryId, NationalCountryModel, EventCompetitionAttributes, EventCompetitionId, EventCompetitionModel, DivisionSeasonAttributes, DivisionSeasonId, DivisionSeasonModel, IterationId, IterationModel, TeamId, TeamModel } from '.';
|
|
4
4
|
import { Status } from '../common';
|
|
5
5
|
export interface CompetitionAttributes {
|
|
6
6
|
competition_id: string;
|
|
@@ -15,6 +15,8 @@ export interface CompetitionAttributes {
|
|
|
15
15
|
CompetitionTeams?: CompetitionTeamsAttributes[];
|
|
16
16
|
RegionQualifier?: RegionQualifierAttributes;
|
|
17
17
|
NationalCountry?: NationalCountryAttributes;
|
|
18
|
+
/** Set only on an EVENT competition: which event it belongs to, and which stage of it. */
|
|
19
|
+
EventCompetition?: EventCompetitionAttributes;
|
|
18
20
|
UpperPromotionMatches?: PromotionMatchAttributes[];
|
|
19
21
|
LowerPromotionMatches?: PromotionMatchAttributes[];
|
|
20
22
|
}
|
|
@@ -72,6 +74,10 @@ export declare class CompetitionModel extends Model<CompetitionAttributes, Compe
|
|
|
72
74
|
getNationalCountry: Sequelize.HasOneGetAssociationMixin<NationalCountryModel>;
|
|
73
75
|
setNationalCountry: Sequelize.HasOneSetAssociationMixin<NationalCountryModel, NationalCountryId>;
|
|
74
76
|
createNationalCountry: Sequelize.HasOneCreateAssociationMixin<NationalCountryModel>;
|
|
77
|
+
EventCompetition: EventCompetitionModel;
|
|
78
|
+
getEventCompetition: Sequelize.HasOneGetAssociationMixin<EventCompetitionModel>;
|
|
79
|
+
setEventCompetition: Sequelize.HasOneSetAssociationMixin<EventCompetitionModel, EventCompetitionId>;
|
|
80
|
+
createEventCompetition: Sequelize.HasOneCreateAssociationMixin<EventCompetitionModel>;
|
|
75
81
|
UpperPromotionMatches: PromotionMatchModel[];
|
|
76
82
|
getUpperPromotionMatches: Sequelize.HasManyGetAssociationsMixin<PromotionMatchModel>;
|
|
77
83
|
setUpperPromotionMatches: Sequelize.HasManySetAssociationsMixin<PromotionMatchModel, PromotionMatchId>;
|
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
import { Rarity } from './rarity';
|
|
2
|
+
import { Role } from './role';
|
|
2
3
|
import { Stats } from './stats';
|
|
3
4
|
import { DeclineProfile } from './decline';
|
|
4
5
|
export type TrainingFocus = Stats;
|
|
6
|
+
export declare const FOCUS_WEIGHT = 0.5;
|
|
7
|
+
interface ImprovementFloors {
|
|
8
|
+
/** Mean generated sum of all 18 performance stats. */
|
|
9
|
+
all: number;
|
|
10
|
+
/** Mean generated sum of each training focus's pool, keyed by focus name. */
|
|
11
|
+
focus: Record<string, number>;
|
|
12
|
+
}
|
|
5
13
|
export declare function getStatCap(rarity: Rarity): number;
|
|
6
|
-
|
|
7
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The DB column names of a training focus's pool: exactly the stats `selectImprovableStat` can raise under
|
|
16
|
+
* that focus, so the band and the pool are the same set by construction.
|
|
17
|
+
*/
|
|
18
|
+
export declare function focusPoolColumns(focus: TrainingFocus): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Where a freshly generated player of this rarity and role starts, in both bands.
|
|
21
|
+
*
|
|
22
|
+
* Roles matter: at LEGENDARY the mean generated 18-stat sum runs 846 for an outside hitter against 1099 for a
|
|
23
|
+
* libero, a fifth of the band, so one floor per rarity would hand some roles a free head start. An unknown
|
|
24
|
+
* role falls back to the mean across that rarity's roles rather than throwing, because a player whose role
|
|
25
|
+
* enum has changed underneath us should still be able to train.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getImprovementFloors(rarity: Rarity, role: Role): ImprovementFloors;
|
|
28
|
+
/**
|
|
29
|
+
* Exp needed for this player's next +1, before the coach multiplier.
|
|
30
|
+
*
|
|
31
|
+
* `stats` is keyed by DB COLUMN name (back_attack, not backAttack), the same shape `selectImprovableStat`
|
|
32
|
+
* takes, so a caller passes one object to both.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getImprovementThreshold(stats: Record<string, number>, rarity: Rarity, role: Role, focus: TrainingFocus): number;
|
|
8
35
|
export declare function getCoachMultiplier(rarity: Rarity): number;
|
|
9
36
|
export declare function matchExperience(pointsPlayed: number): number;
|
|
10
37
|
export declare function experienceGained(pointsPlayed: number, age: number, profile: DeclineProfile): number;
|
|
11
38
|
export declare function selectImprovableStat(rarity: Rarity, trainingFocus: TrainingFocus, currentStats: Record<string, number>): string | null;
|
|
39
|
+
export {};
|