volleyballsimtypes 0.0.496 → 0.0.497

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.
@@ -31,10 +31,14 @@ function getMultipliers(statName, performanceStats) {
31
31
  };
32
32
  case StatsEnum.SET:
33
33
  return {
34
- setting: 0.75,
34
+ setting: 0.73,
35
+ // fast-tempo-gates: a setter's `quick` is what lets them run a fast offence, so it belongs in the SET
36
+ // rating AND in the SET training pool. Without it a setter on SET focus can never raise `quick` (it has
37
+ // weight 0 in every role too), and their whole tempo game would be fixed forever by a generation roll.
38
+ quick: 0.07,
35
39
  overhand: 0.1,
36
- awareness: 0.075,
37
- attack: 0.075
40
+ awareness: 0.05,
41
+ attack: 0.05
38
42
  };
39
43
  case StatsEnum.RECEIVE:
40
44
  return {
@@ -13,123 +13,123 @@
13
13
  },
14
14
  "measuredFrom": "prod read-only 2026-07-24: 434 Wishlist rows, 162 with roles; role listing shares"
15
15
  },
16
- "sourceHash": "ec4620ca048d05741b9c42ce6f24a4e7bd04f047c5a420bb93f5a5e711d7adeb",
16
+ "sourceHash": "162d3c45b2932f91e599ed9fa652caa4a0206f8b823340892418778c9247d1a3",
17
17
  "floors": [
18
18
  {
19
19
  "rarity": "COMMON",
20
20
  "role": "SETTER",
21
- "samples": 104658,
21
+ "samples": 104493,
22
22
  "roleBranchShare": 0,
23
- "all": 540.2,
23
+ "all": 540,
24
24
  "focus": {
25
25
  "ATTACK": 172.3,
26
- "SET": 136.7,
26
+ "SET": 166.6,
27
27
  "RECEIVE": 172.6,
28
28
  "SERVE": 93.9,
29
29
  "BLOCK": 175.8,
30
- "STAMINA": 28.6
30
+ "STAMINA": 28.7
31
31
  }
32
32
  },
33
33
  {
34
34
  "rarity": "COMMON",
35
35
  "role": "OUTSIDE_HITTER",
36
- "samples": 1886,
36
+ "samples": 1875,
37
37
  "roleBranchShare": 0,
38
- "all": 545.7,
38
+ "all": 547.1,
39
39
  "focus": {
40
- "ATTACK": 183.8,
41
- "SET": 100,
42
- "RECEIVE": 183.7,
43
- "SERVE": 102.4,
44
- "BLOCK": 181.6,
40
+ "ATTACK": 185.2,
41
+ "SET": 132,
42
+ "RECEIVE": 184.3,
43
+ "SERVE": 102.7,
44
+ "BLOCK": 182.1,
45
45
  "STAMINA": 32.7
46
46
  }
47
47
  },
48
48
  {
49
49
  "rarity": "COMMON",
50
50
  "role": "MIDDLE_BLOCKER",
51
- "samples": 99825,
51
+ "samples": 100149,
52
52
  "roleBranchShare": 0,
53
- "all": 541,
53
+ "all": 540.8,
54
54
  "focus": {
55
- "ATTACK": 177.9,
56
- "SET": 113,
55
+ "ATTACK": 178.1,
56
+ "SET": 143,
57
57
  "RECEIVE": 174.5,
58
58
  "SERVE": 86.9,
59
- "BLOCK": 198.3,
59
+ "BLOCK": 198.1,
60
60
  "STAMINA": 30.4
61
61
  }
62
62
  },
63
63
  {
64
64
  "rarity": "COMMON",
65
65
  "role": "OPPOSITE_HITTER",
66
- "samples": 85837,
66
+ "samples": 85147,
67
67
  "roleBranchShare": 0,
68
68
  "all": 537,
69
69
  "focus": {
70
- "ATTACK": 200.9,
71
- "SET": 112,
70
+ "ATTACK": 200.7,
71
+ "SET": 141.7,
72
72
  "RECEIVE": 172.9,
73
- "SERVE": 92.6,
74
- "BLOCK": 170.4,
73
+ "SERVE": 92.3,
74
+ "BLOCK": 170.6,
75
75
  "STAMINA": 28.5
76
76
  }
77
77
  },
78
78
  {
79
79
  "rarity": "COMMON",
80
80
  "role": "LIBERO",
81
- "samples": 107794,
81
+ "samples": 108336,
82
82
  "roleBranchShare": 0,
83
- "all": 541.4,
83
+ "all": 541.3,
84
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
85
+ "ATTACK": 173,
86
+ "SET": 147.3,
87
+ "RECEIVE": 197.6,
88
+ "SERVE": 87,
89
+ "BLOCK": 174.5,
90
+ "STAMINA": 32
91
91
  }
92
92
  },
93
93
  {
94
94
  "rarity": "RARE",
95
95
  "role": "SETTER",
96
- "samples": 92252,
97
- "roleBranchShare": 0.0888,
98
- "all": 865.3,
96
+ "samples": 92057,
97
+ "roleBranchShare": 0.0889,
98
+ "all": 884.7,
99
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
100
+ "ATTACK": 288.5,
101
+ "SET": 296.8,
102
+ "RECEIVE": 299.8,
103
+ "SERVE": 176.1,
104
+ "BLOCK": 299.8,
105
+ "STAMINA": 40.1
106
106
  }
107
107
  },
108
108
  {
109
109
  "rarity": "RARE",
110
110
  "role": "OUTSIDE_HITTER",
111
- "samples": 18916,
112
- "roleBranchShare": 0.8573,
113
- "all": 691.4,
111
+ "samples": 18711,
112
+ "roleBranchShare": 0.8516,
113
+ "all": 692.4,
114
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
115
+ "ATTACK": 238.7,
116
+ "SET": 144.1,
117
+ "RECEIVE": 211.3,
118
+ "SERVE": 119.9,
119
+ "BLOCK": 208.5,
120
+ "STAMINA": 42.5
121
121
  }
122
122
  },
123
123
  {
124
124
  "rarity": "RARE",
125
125
  "role": "MIDDLE_BLOCKER",
126
- "samples": 92272,
127
- "roleBranchShare": 0.1525,
128
- "all": 899.1,
126
+ "samples": 91948,
127
+ "roleBranchShare": 0.152,
128
+ "all": 904.7,
129
129
  "focus": {
130
- "ATTACK": 306.8,
131
- "SET": 201.7,
132
- "RECEIVE": 303.5,
130
+ "ATTACK": 312.4,
131
+ "SET": 254.2,
132
+ "RECEIVE": 303.6,
133
133
  "SERVE": 149.3,
134
134
  "BLOCK": 339.8,
135
135
  "STAMINA": 42.6
@@ -138,104 +138,104 @@
138
138
  {
139
139
  "rarity": "RARE",
140
140
  "role": "OPPOSITE_HITTER",
141
- "samples": 100116,
142
- "roleBranchShare": 0.1141,
143
- "all": 895.9,
141
+ "samples": 100430,
142
+ "roleBranchShare": 0.1131,
143
+ "all": 897.4,
144
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
145
+ "ATTACK": 353.7,
146
+ "SET": 265.6,
147
+ "RECEIVE": 293.2,
148
+ "SERVE": 173.2,
149
+ "BLOCK": 273.8,
150
+ "STAMINA": 40.4
151
151
  }
152
152
  },
153
153
  {
154
154
  "rarity": "RARE",
155
155
  "role": "LIBERO",
156
- "samples": 96444,
157
- "roleBranchShare": 0.1067,
158
- "all": 886.5,
156
+ "samples": 96854,
157
+ "roleBranchShare": 0.1072,
158
+ "all": 899.9,
159
159
  "focus": {
160
- "ATTACK": 272,
161
- "SET": 225.8,
162
- "RECEIVE": 349.9,
163
- "SERVE": 154.5,
164
- "BLOCK": 301.7,
160
+ "ATTACK": 285.7,
161
+ "SET": 279.1,
162
+ "RECEIVE": 349.8,
163
+ "SERVE": 154.4,
164
+ "BLOCK": 301.6,
165
165
  "STAMINA": 44.8
166
166
  }
167
167
  },
168
168
  {
169
169
  "rarity": "LEGENDARY",
170
170
  "role": "SETTER",
171
- "samples": 68986,
172
- "roleBranchShare": 0.119,
173
- "all": 1077.5,
171
+ "samples": 69381,
172
+ "roleBranchShare": 0.1187,
173
+ "all": 1090.8,
174
174
  "focus": {
175
- "ATTACK": 368.2,
176
- "SET": 285.7,
177
- "RECEIVE": 362.8,
178
- "SERVE": 186.9,
179
- "BLOCK": 355.3,
175
+ "ATTACK": 381.4,
176
+ "SET": 355.9,
177
+ "RECEIVE": 363,
178
+ "SERVE": 187.1,
179
+ "BLOCK": 355.4,
180
180
  "STAMINA": 48.7
181
181
  }
182
182
  },
183
183
  {
184
184
  "rarity": "LEGENDARY",
185
185
  "role": "OUTSIDE_HITTER",
186
- "samples": 16108,
187
- "roleBranchShare": 0.9988,
188
- "all": 845.7,
186
+ "samples": 16102,
187
+ "roleBranchShare": 0.9987,
188
+ "all": 845.2,
189
189
  "focus": {
190
- "ATTACK": 290.2,
191
- "SET": 140.7,
192
- "RECEIVE": 250.7,
193
- "SERVE": 145.4,
194
- "BLOCK": 250.5,
190
+ "ATTACK": 290.4,
191
+ "SET": 175.5,
192
+ "RECEIVE": 250.4,
193
+ "SERVE": 145.1,
194
+ "BLOCK": 250.2,
195
195
  "STAMINA": 49.5
196
196
  }
197
197
  },
198
198
  {
199
199
  "rarity": "LEGENDARY",
200
200
  "role": "MIDDLE_BLOCKER",
201
- "samples": 116110,
202
- "roleBranchShare": 0.1202,
203
- "all": 1090,
201
+ "samples": 116362,
202
+ "roleBranchShare": 0.1214,
203
+ "all": 1092.9,
204
204
  "focus": {
205
- "ATTACK": 361.5,
206
- "SET": 242.2,
207
- "RECEIVE": 379.6,
208
- "SERVE": 155.6,
209
- "BLOCK": 414.7,
205
+ "ATTACK": 364.9,
206
+ "SET": 303.7,
207
+ "RECEIVE": 379.4,
208
+ "SERVE": 155.5,
209
+ "BLOCK": 414.4,
210
210
  "STAMINA": 49.7
211
211
  }
212
212
  },
213
213
  {
214
214
  "rarity": "LEGENDARY",
215
215
  "role": "OPPOSITE_HITTER",
216
- "samples": 83884,
217
- "roleBranchShare": 0.1336,
218
- "all": 1098.1,
216
+ "samples": 83611,
217
+ "roleBranchShare": 0.1347,
218
+ "all": 1097.5,
219
219
  "focus": {
220
- "ATTACK": 428.8,
221
- "SET": 255.3,
222
- "RECEIVE": 350.8,
220
+ "ATTACK": 428.7,
221
+ "SET": 324.7,
222
+ "RECEIVE": 350.5,
223
223
  "SERVE": 182.8,
224
- "BLOCK": 347.6,
224
+ "BLOCK": 347.3,
225
225
  "STAMINA": 49
226
226
  }
227
227
  },
228
228
  {
229
229
  "rarity": "LEGENDARY",
230
230
  "role": "LIBERO",
231
- "samples": 114912,
232
- "roleBranchShare": 0.089,
233
- "all": 1098.7,
231
+ "samples": 114544,
232
+ "roleBranchShare": 0.0892,
233
+ "all": 1103.1,
234
234
  "focus": {
235
- "ATTACK": 358.7,
236
- "SET": 260.7,
235
+ "ATTACK": 363.1,
236
+ "SET": 323.3,
237
237
  "RECEIVE": 421.5,
238
- "SERVE": 157.8,
238
+ "SERVE": 157.7,
239
239
  "BLOCK": 381.3,
240
240
  "STAMINA": 50.2
241
241
  }
@@ -243,57 +243,57 @@
243
243
  {
244
244
  "rarity": "MYTHIC",
245
245
  "role": "SETTER",
246
- "samples": 99645,
247
- "roleBranchShare": 0.0824,
248
- "all": 1246.4,
246
+ "samples": 99611,
247
+ "roleBranchShare": 0.0828,
248
+ "all": 1263.9,
249
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
250
+ "ATTACK": 427.9,
251
+ "SET": 404.8,
252
+ "RECEIVE": 413.4,
253
+ "SERVE": 236.3,
254
+ "BLOCK": 404.2,
255
+ "STAMINA": 66.6
256
256
  }
257
257
  },
258
258
  {
259
259
  "rarity": "MYTHIC",
260
260
  "role": "OUTSIDE_HITTER",
261
- "samples": 15991,
262
- "roleBranchShare": 1,
263
- "all": 962.4,
261
+ "samples": 16110,
262
+ "roleBranchShare": 0.9999,
263
+ "all": 962.8,
264
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
265
+ "ATTACK": 327,
266
+ "SET": 197.6,
267
+ "RECEIVE": 281.4,
268
+ "SERVE": 163.5,
269
+ "BLOCK": 281,
270
+ "STAMINA": 67
271
271
  }
272
272
  },
273
273
  {
274
274
  "rarity": "MYTHIC",
275
275
  "role": "MIDDLE_BLOCKER",
276
- "samples": 92022,
277
- "roleBranchShare": 0.1535,
278
- "all": 1251.8,
276
+ "samples": 92045,
277
+ "roleBranchShare": 0.152,
278
+ "all": 1257.1,
279
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
280
+ "ATTACK": 398.4,
281
+ "SET": 333.1,
282
+ "RECEIVE": 421.8,
283
+ "SERVE": 210.4,
284
+ "BLOCK": 472.9,
285
+ "STAMINA": 67.1
286
286
  }
287
287
  },
288
288
  {
289
289
  "rarity": "MYTHIC",
290
290
  "role": "OPPOSITE_HITTER",
291
- "samples": 99024,
292
- "roleBranchShare": 0.1126,
293
- "all": 1272.8,
291
+ "samples": 99007,
292
+ "roleBranchShare": 0.1123,
293
+ "all": 1272.7,
294
294
  "focus": {
295
295
  "ATTACK": 485.3,
296
- "SET": 288.2,
296
+ "SET": 367.3,
297
297
  "RECEIVE": 396.8,
298
298
  "SERVE": 234.5,
299
299
  "BLOCK": 389.7,
@@ -303,90 +303,90 @@
303
303
  {
304
304
  "rarity": "MYTHIC",
305
305
  "role": "LIBERO",
306
- "samples": 93318,
307
- "roleBranchShare": 0.1088,
308
- "all": 1267.3,
306
+ "samples": 93227,
307
+ "roleBranchShare": 0.1095,
308
+ "all": 1272.3,
309
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
310
+ "ATTACK": 397,
311
+ "SET": 358.3,
312
+ "RECEIVE": 482,
313
+ "SERVE": 215,
314
+ "BLOCK": 425.9,
315
+ "STAMINA": 67.6
316
316
  }
317
317
  },
318
318
  {
319
319
  "rarity": "SPECIAL",
320
320
  "role": "SETTER",
321
- "samples": 102997,
322
- "roleBranchShare": 0.0804,
323
- "all": 1457.3,
321
+ "samples": 102669,
322
+ "roleBranchShare": 0.0806,
323
+ "all": 1468.8,
324
324
  "focus": {
325
- "ATTACK": 485.2,
326
- "SET": 360.5,
327
- "RECEIVE": 481.4,
325
+ "ATTACK": 496.8,
326
+ "SET": 448.2,
327
+ "RECEIVE": 481.3,
328
328
  "SERVE": 265.7,
329
- "BLOCK": 475.5,
329
+ "BLOCK": 475.4,
330
330
  "STAMINA": 79
331
331
  }
332
332
  },
333
333
  {
334
334
  "rarity": "SPECIAL",
335
335
  "role": "OUTSIDE_HITTER",
336
- "samples": 16546,
337
- "roleBranchShare": 0.9652,
338
- "all": 1100.6,
336
+ "samples": 16703,
337
+ "roleBranchShare": 0.9649,
338
+ "all": 1100.5,
339
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
340
+ "ATTACK": 371.8,
341
+ "SET": 229.6,
342
+ "RECEIVE": 323.3,
343
+ "SERVE": 187,
344
+ "BLOCK": 323.1,
345
+ "STAMINA": 79.5
346
346
  }
347
347
  },
348
348
  {
349
349
  "rarity": "SPECIAL",
350
350
  "role": "MIDDLE_BLOCKER",
351
- "samples": 87715,
352
- "roleBranchShare": 0.1592,
353
- "all": 1440.2,
351
+ "samples": 87563,
352
+ "roleBranchShare": 0.1597,
353
+ "all": 1442.9,
354
354
  "focus": {
355
- "ATTACK": 463.7,
356
- "SET": 309.9,
357
- "RECEIVE": 482.8,
355
+ "ATTACK": 466.6,
356
+ "SET": 386.9,
357
+ "RECEIVE": 482.7,
358
358
  "SERVE": 234.3,
359
- "BLOCK": 522.8,
360
- "STAMINA": 79.5
359
+ "BLOCK": 522.3,
360
+ "STAMINA": 79.7
361
361
  }
362
362
  },
363
363
  {
364
364
  "rarity": "SPECIAL",
365
365
  "role": "OPPOSITE_HITTER",
366
- "samples": 102830,
366
+ "samples": 103053,
367
367
  "roleBranchShare": 0.1096,
368
- "all": 1469.1,
368
+ "all": 1469.5,
369
369
  "focus": {
370
- "ATTACK": 536.1,
371
- "SET": 331.8,
372
- "RECEIVE": 467.1,
370
+ "ATTACK": 536.2,
371
+ "SET": 418.7,
372
+ "RECEIVE": 467.2,
373
373
  "SERVE": 262.7,
374
- "BLOCK": 463.9,
374
+ "BLOCK": 464.2,
375
375
  "STAMINA": 79.2
376
376
  }
377
377
  },
378
378
  {
379
379
  "rarity": "SPECIAL",
380
380
  "role": "LIBERO",
381
- "samples": 89912,
382
- "roleBranchShare": 0.1147,
383
- "all": 1462.7,
381
+ "samples": 90012,
382
+ "roleBranchShare": 0.1131,
383
+ "all": 1466.8,
384
384
  "focus": {
385
- "ATTACK": 464.9,
386
- "SET": 332.7,
387
- "RECEIVE": 531.3,
388
- "SERVE": 240.9,
389
- "BLOCK": 490.4,
385
+ "ATTACK": 468.6,
386
+ "SET": 411.6,
387
+ "RECEIVE": 531.6,
388
+ "SERVE": 241,
389
+ "BLOCK": 490.6,
390
390
  "STAMINA": 80.3
391
391
  }
392
392
  }
@@ -13,19 +13,26 @@ const improvement_1 = require("../service/player/improvement");
13
13
  //
14
14
  // The drift guard is a HASH OF THE PARSED VALUES the floors depend on, not of any source text: hashing source
15
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.
16
+ // It therefore catches a retuned role weight, stat cap, rarity range or COMPOSITE MULTIPLIER, and does NOT catch
17
+ // a pure logic change inside player-generator.ts. That case is covered by re-running the generator deliberately.
18
+ //
19
+ // The multipliers are in the hash because they DEFINE the focus pools (focusPoolColumns is literally
20
+ // Object.keys(getMultipliers(focus))), so adding a stat to a composite changes both the band divisor and which
21
+ // stats the focus can raise. Observed 2026-07-24: without them, adding `quick` to SET left every test passing on
22
+ // a stale file, because the "below ceiling" assertion reads pool size live and a bigger pool only raises the
23
+ // ceiling the old floor was already under.
18
24
  const RARITIES = [rarity_1.RarityEnum.COMMON, rarity_1.RarityEnum.RARE, rarity_1.RarityEnum.LEGENDARY, rarity_1.RarityEnum.MYTHIC, rarity_1.RarityEnum.SPECIAL];
19
25
  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
26
  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
27
  const REGENERATE = 'improvement-floors.json is stale. Run: yarn build && yarn floors';
22
28
  (0, globals_1.describe)('improvement-floors.json', () => {
23
- (0, globals_1.it)('was generated from the current roles, caps and rarity ranges', () => {
29
+ (0, globals_1.it)('was generated from the current roles, caps, rarity ranges and composite multipliers', () => {
24
30
  const canonical = JSON.stringify({
25
31
  roles: index_1.rolesJSON,
26
32
  statCaps: index_1.statCapsJSON,
27
33
  rarityRanges: rarity_1.RarityRanges,
28
- staminaRanges: rarity_1.StaminaRanges
34
+ staminaRanges: rarity_1.StaminaRanges,
35
+ multipliers: FOCI.map(focus => [focus, (0, stats_1.getMultipliers)(focus)])
29
36
  });
30
37
  const expected = (0, crypto_1.createHash)('sha256').update(canonical).digest('hex');
31
38
  (0, globals_1.expect)(`${index_1.improvementFloorsJSON.sourceHash} ${REGENERATE}`).toBe(`${expected} ${REGENERATE}`);
@@ -26,10 +26,14 @@ export function getMultipliers(statName, performanceStats) {
26
26
  };
27
27
  case StatsEnum.SET:
28
28
  return {
29
- setting: 0.75,
29
+ setting: 0.73,
30
+ // fast-tempo-gates: a setter's `quick` is what lets them run a fast offence, so it belongs in the SET
31
+ // rating AND in the SET training pool. Without it a setter on SET focus can never raise `quick` (it has
32
+ // weight 0 in every role too), and their whole tempo game would be fixed forever by a generation roll.
33
+ quick: 0.07,
30
34
  overhand: 0.1,
31
- awareness: 0.075,
32
- attack: 0.075
35
+ awareness: 0.05,
36
+ attack: 0.05
33
37
  };
34
38
  case StatsEnum.RECEIVE:
35
39
  return {
@@ -13,123 +13,123 @@
13
13
  },
14
14
  "measuredFrom": "prod read-only 2026-07-24: 434 Wishlist rows, 162 with roles; role listing shares"
15
15
  },
16
- "sourceHash": "ec4620ca048d05741b9c42ce6f24a4e7bd04f047c5a420bb93f5a5e711d7adeb",
16
+ "sourceHash": "162d3c45b2932f91e599ed9fa652caa4a0206f8b823340892418778c9247d1a3",
17
17
  "floors": [
18
18
  {
19
19
  "rarity": "COMMON",
20
20
  "role": "SETTER",
21
- "samples": 104658,
21
+ "samples": 104493,
22
22
  "roleBranchShare": 0,
23
- "all": 540.2,
23
+ "all": 540,
24
24
  "focus": {
25
25
  "ATTACK": 172.3,
26
- "SET": 136.7,
26
+ "SET": 166.6,
27
27
  "RECEIVE": 172.6,
28
28
  "SERVE": 93.9,
29
29
  "BLOCK": 175.8,
30
- "STAMINA": 28.6
30
+ "STAMINA": 28.7
31
31
  }
32
32
  },
33
33
  {
34
34
  "rarity": "COMMON",
35
35
  "role": "OUTSIDE_HITTER",
36
- "samples": 1886,
36
+ "samples": 1875,
37
37
  "roleBranchShare": 0,
38
- "all": 545.7,
38
+ "all": 547.1,
39
39
  "focus": {
40
- "ATTACK": 183.8,
41
- "SET": 100,
42
- "RECEIVE": 183.7,
43
- "SERVE": 102.4,
44
- "BLOCK": 181.6,
40
+ "ATTACK": 185.2,
41
+ "SET": 132,
42
+ "RECEIVE": 184.3,
43
+ "SERVE": 102.7,
44
+ "BLOCK": 182.1,
45
45
  "STAMINA": 32.7
46
46
  }
47
47
  },
48
48
  {
49
49
  "rarity": "COMMON",
50
50
  "role": "MIDDLE_BLOCKER",
51
- "samples": 99825,
51
+ "samples": 100149,
52
52
  "roleBranchShare": 0,
53
- "all": 541,
53
+ "all": 540.8,
54
54
  "focus": {
55
- "ATTACK": 177.9,
56
- "SET": 113,
55
+ "ATTACK": 178.1,
56
+ "SET": 143,
57
57
  "RECEIVE": 174.5,
58
58
  "SERVE": 86.9,
59
- "BLOCK": 198.3,
59
+ "BLOCK": 198.1,
60
60
  "STAMINA": 30.4
61
61
  }
62
62
  },
63
63
  {
64
64
  "rarity": "COMMON",
65
65
  "role": "OPPOSITE_HITTER",
66
- "samples": 85837,
66
+ "samples": 85147,
67
67
  "roleBranchShare": 0,
68
68
  "all": 537,
69
69
  "focus": {
70
- "ATTACK": 200.9,
71
- "SET": 112,
70
+ "ATTACK": 200.7,
71
+ "SET": 141.7,
72
72
  "RECEIVE": 172.9,
73
- "SERVE": 92.6,
74
- "BLOCK": 170.4,
73
+ "SERVE": 92.3,
74
+ "BLOCK": 170.6,
75
75
  "STAMINA": 28.5
76
76
  }
77
77
  },
78
78
  {
79
79
  "rarity": "COMMON",
80
80
  "role": "LIBERO",
81
- "samples": 107794,
81
+ "samples": 108336,
82
82
  "roleBranchShare": 0,
83
- "all": 541.4,
83
+ "all": 541.3,
84
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
85
+ "ATTACK": 173,
86
+ "SET": 147.3,
87
+ "RECEIVE": 197.6,
88
+ "SERVE": 87,
89
+ "BLOCK": 174.5,
90
+ "STAMINA": 32
91
91
  }
92
92
  },
93
93
  {
94
94
  "rarity": "RARE",
95
95
  "role": "SETTER",
96
- "samples": 92252,
97
- "roleBranchShare": 0.0888,
98
- "all": 865.3,
96
+ "samples": 92057,
97
+ "roleBranchShare": 0.0889,
98
+ "all": 884.7,
99
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
100
+ "ATTACK": 288.5,
101
+ "SET": 296.8,
102
+ "RECEIVE": 299.8,
103
+ "SERVE": 176.1,
104
+ "BLOCK": 299.8,
105
+ "STAMINA": 40.1
106
106
  }
107
107
  },
108
108
  {
109
109
  "rarity": "RARE",
110
110
  "role": "OUTSIDE_HITTER",
111
- "samples": 18916,
112
- "roleBranchShare": 0.8573,
113
- "all": 691.4,
111
+ "samples": 18711,
112
+ "roleBranchShare": 0.8516,
113
+ "all": 692.4,
114
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
115
+ "ATTACK": 238.7,
116
+ "SET": 144.1,
117
+ "RECEIVE": 211.3,
118
+ "SERVE": 119.9,
119
+ "BLOCK": 208.5,
120
+ "STAMINA": 42.5
121
121
  }
122
122
  },
123
123
  {
124
124
  "rarity": "RARE",
125
125
  "role": "MIDDLE_BLOCKER",
126
- "samples": 92272,
127
- "roleBranchShare": 0.1525,
128
- "all": 899.1,
126
+ "samples": 91948,
127
+ "roleBranchShare": 0.152,
128
+ "all": 904.7,
129
129
  "focus": {
130
- "ATTACK": 306.8,
131
- "SET": 201.7,
132
- "RECEIVE": 303.5,
130
+ "ATTACK": 312.4,
131
+ "SET": 254.2,
132
+ "RECEIVE": 303.6,
133
133
  "SERVE": 149.3,
134
134
  "BLOCK": 339.8,
135
135
  "STAMINA": 42.6
@@ -138,104 +138,104 @@
138
138
  {
139
139
  "rarity": "RARE",
140
140
  "role": "OPPOSITE_HITTER",
141
- "samples": 100116,
142
- "roleBranchShare": 0.1141,
143
- "all": 895.9,
141
+ "samples": 100430,
142
+ "roleBranchShare": 0.1131,
143
+ "all": 897.4,
144
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
145
+ "ATTACK": 353.7,
146
+ "SET": 265.6,
147
+ "RECEIVE": 293.2,
148
+ "SERVE": 173.2,
149
+ "BLOCK": 273.8,
150
+ "STAMINA": 40.4
151
151
  }
152
152
  },
153
153
  {
154
154
  "rarity": "RARE",
155
155
  "role": "LIBERO",
156
- "samples": 96444,
157
- "roleBranchShare": 0.1067,
158
- "all": 886.5,
156
+ "samples": 96854,
157
+ "roleBranchShare": 0.1072,
158
+ "all": 899.9,
159
159
  "focus": {
160
- "ATTACK": 272,
161
- "SET": 225.8,
162
- "RECEIVE": 349.9,
163
- "SERVE": 154.5,
164
- "BLOCK": 301.7,
160
+ "ATTACK": 285.7,
161
+ "SET": 279.1,
162
+ "RECEIVE": 349.8,
163
+ "SERVE": 154.4,
164
+ "BLOCK": 301.6,
165
165
  "STAMINA": 44.8
166
166
  }
167
167
  },
168
168
  {
169
169
  "rarity": "LEGENDARY",
170
170
  "role": "SETTER",
171
- "samples": 68986,
172
- "roleBranchShare": 0.119,
173
- "all": 1077.5,
171
+ "samples": 69381,
172
+ "roleBranchShare": 0.1187,
173
+ "all": 1090.8,
174
174
  "focus": {
175
- "ATTACK": 368.2,
176
- "SET": 285.7,
177
- "RECEIVE": 362.8,
178
- "SERVE": 186.9,
179
- "BLOCK": 355.3,
175
+ "ATTACK": 381.4,
176
+ "SET": 355.9,
177
+ "RECEIVE": 363,
178
+ "SERVE": 187.1,
179
+ "BLOCK": 355.4,
180
180
  "STAMINA": 48.7
181
181
  }
182
182
  },
183
183
  {
184
184
  "rarity": "LEGENDARY",
185
185
  "role": "OUTSIDE_HITTER",
186
- "samples": 16108,
187
- "roleBranchShare": 0.9988,
188
- "all": 845.7,
186
+ "samples": 16102,
187
+ "roleBranchShare": 0.9987,
188
+ "all": 845.2,
189
189
  "focus": {
190
- "ATTACK": 290.2,
191
- "SET": 140.7,
192
- "RECEIVE": 250.7,
193
- "SERVE": 145.4,
194
- "BLOCK": 250.5,
190
+ "ATTACK": 290.4,
191
+ "SET": 175.5,
192
+ "RECEIVE": 250.4,
193
+ "SERVE": 145.1,
194
+ "BLOCK": 250.2,
195
195
  "STAMINA": 49.5
196
196
  }
197
197
  },
198
198
  {
199
199
  "rarity": "LEGENDARY",
200
200
  "role": "MIDDLE_BLOCKER",
201
- "samples": 116110,
202
- "roleBranchShare": 0.1202,
203
- "all": 1090,
201
+ "samples": 116362,
202
+ "roleBranchShare": 0.1214,
203
+ "all": 1092.9,
204
204
  "focus": {
205
- "ATTACK": 361.5,
206
- "SET": 242.2,
207
- "RECEIVE": 379.6,
208
- "SERVE": 155.6,
209
- "BLOCK": 414.7,
205
+ "ATTACK": 364.9,
206
+ "SET": 303.7,
207
+ "RECEIVE": 379.4,
208
+ "SERVE": 155.5,
209
+ "BLOCK": 414.4,
210
210
  "STAMINA": 49.7
211
211
  }
212
212
  },
213
213
  {
214
214
  "rarity": "LEGENDARY",
215
215
  "role": "OPPOSITE_HITTER",
216
- "samples": 83884,
217
- "roleBranchShare": 0.1336,
218
- "all": 1098.1,
216
+ "samples": 83611,
217
+ "roleBranchShare": 0.1347,
218
+ "all": 1097.5,
219
219
  "focus": {
220
- "ATTACK": 428.8,
221
- "SET": 255.3,
222
- "RECEIVE": 350.8,
220
+ "ATTACK": 428.7,
221
+ "SET": 324.7,
222
+ "RECEIVE": 350.5,
223
223
  "SERVE": 182.8,
224
- "BLOCK": 347.6,
224
+ "BLOCK": 347.3,
225
225
  "STAMINA": 49
226
226
  }
227
227
  },
228
228
  {
229
229
  "rarity": "LEGENDARY",
230
230
  "role": "LIBERO",
231
- "samples": 114912,
232
- "roleBranchShare": 0.089,
233
- "all": 1098.7,
231
+ "samples": 114544,
232
+ "roleBranchShare": 0.0892,
233
+ "all": 1103.1,
234
234
  "focus": {
235
- "ATTACK": 358.7,
236
- "SET": 260.7,
235
+ "ATTACK": 363.1,
236
+ "SET": 323.3,
237
237
  "RECEIVE": 421.5,
238
- "SERVE": 157.8,
238
+ "SERVE": 157.7,
239
239
  "BLOCK": 381.3,
240
240
  "STAMINA": 50.2
241
241
  }
@@ -243,57 +243,57 @@
243
243
  {
244
244
  "rarity": "MYTHIC",
245
245
  "role": "SETTER",
246
- "samples": 99645,
247
- "roleBranchShare": 0.0824,
248
- "all": 1246.4,
246
+ "samples": 99611,
247
+ "roleBranchShare": 0.0828,
248
+ "all": 1263.9,
249
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
250
+ "ATTACK": 427.9,
251
+ "SET": 404.8,
252
+ "RECEIVE": 413.4,
253
+ "SERVE": 236.3,
254
+ "BLOCK": 404.2,
255
+ "STAMINA": 66.6
256
256
  }
257
257
  },
258
258
  {
259
259
  "rarity": "MYTHIC",
260
260
  "role": "OUTSIDE_HITTER",
261
- "samples": 15991,
262
- "roleBranchShare": 1,
263
- "all": 962.4,
261
+ "samples": 16110,
262
+ "roleBranchShare": 0.9999,
263
+ "all": 962.8,
264
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
265
+ "ATTACK": 327,
266
+ "SET": 197.6,
267
+ "RECEIVE": 281.4,
268
+ "SERVE": 163.5,
269
+ "BLOCK": 281,
270
+ "STAMINA": 67
271
271
  }
272
272
  },
273
273
  {
274
274
  "rarity": "MYTHIC",
275
275
  "role": "MIDDLE_BLOCKER",
276
- "samples": 92022,
277
- "roleBranchShare": 0.1535,
278
- "all": 1251.8,
276
+ "samples": 92045,
277
+ "roleBranchShare": 0.152,
278
+ "all": 1257.1,
279
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
280
+ "ATTACK": 398.4,
281
+ "SET": 333.1,
282
+ "RECEIVE": 421.8,
283
+ "SERVE": 210.4,
284
+ "BLOCK": 472.9,
285
+ "STAMINA": 67.1
286
286
  }
287
287
  },
288
288
  {
289
289
  "rarity": "MYTHIC",
290
290
  "role": "OPPOSITE_HITTER",
291
- "samples": 99024,
292
- "roleBranchShare": 0.1126,
293
- "all": 1272.8,
291
+ "samples": 99007,
292
+ "roleBranchShare": 0.1123,
293
+ "all": 1272.7,
294
294
  "focus": {
295
295
  "ATTACK": 485.3,
296
- "SET": 288.2,
296
+ "SET": 367.3,
297
297
  "RECEIVE": 396.8,
298
298
  "SERVE": 234.5,
299
299
  "BLOCK": 389.7,
@@ -303,90 +303,90 @@
303
303
  {
304
304
  "rarity": "MYTHIC",
305
305
  "role": "LIBERO",
306
- "samples": 93318,
307
- "roleBranchShare": 0.1088,
308
- "all": 1267.3,
306
+ "samples": 93227,
307
+ "roleBranchShare": 0.1095,
308
+ "all": 1272.3,
309
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
310
+ "ATTACK": 397,
311
+ "SET": 358.3,
312
+ "RECEIVE": 482,
313
+ "SERVE": 215,
314
+ "BLOCK": 425.9,
315
+ "STAMINA": 67.6
316
316
  }
317
317
  },
318
318
  {
319
319
  "rarity": "SPECIAL",
320
320
  "role": "SETTER",
321
- "samples": 102997,
322
- "roleBranchShare": 0.0804,
323
- "all": 1457.3,
321
+ "samples": 102669,
322
+ "roleBranchShare": 0.0806,
323
+ "all": 1468.8,
324
324
  "focus": {
325
- "ATTACK": 485.2,
326
- "SET": 360.5,
327
- "RECEIVE": 481.4,
325
+ "ATTACK": 496.8,
326
+ "SET": 448.2,
327
+ "RECEIVE": 481.3,
328
328
  "SERVE": 265.7,
329
- "BLOCK": 475.5,
329
+ "BLOCK": 475.4,
330
330
  "STAMINA": 79
331
331
  }
332
332
  },
333
333
  {
334
334
  "rarity": "SPECIAL",
335
335
  "role": "OUTSIDE_HITTER",
336
- "samples": 16546,
337
- "roleBranchShare": 0.9652,
338
- "all": 1100.6,
336
+ "samples": 16703,
337
+ "roleBranchShare": 0.9649,
338
+ "all": 1100.5,
339
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
340
+ "ATTACK": 371.8,
341
+ "SET": 229.6,
342
+ "RECEIVE": 323.3,
343
+ "SERVE": 187,
344
+ "BLOCK": 323.1,
345
+ "STAMINA": 79.5
346
346
  }
347
347
  },
348
348
  {
349
349
  "rarity": "SPECIAL",
350
350
  "role": "MIDDLE_BLOCKER",
351
- "samples": 87715,
352
- "roleBranchShare": 0.1592,
353
- "all": 1440.2,
351
+ "samples": 87563,
352
+ "roleBranchShare": 0.1597,
353
+ "all": 1442.9,
354
354
  "focus": {
355
- "ATTACK": 463.7,
356
- "SET": 309.9,
357
- "RECEIVE": 482.8,
355
+ "ATTACK": 466.6,
356
+ "SET": 386.9,
357
+ "RECEIVE": 482.7,
358
358
  "SERVE": 234.3,
359
- "BLOCK": 522.8,
360
- "STAMINA": 79.5
359
+ "BLOCK": 522.3,
360
+ "STAMINA": 79.7
361
361
  }
362
362
  },
363
363
  {
364
364
  "rarity": "SPECIAL",
365
365
  "role": "OPPOSITE_HITTER",
366
- "samples": 102830,
366
+ "samples": 103053,
367
367
  "roleBranchShare": 0.1096,
368
- "all": 1469.1,
368
+ "all": 1469.5,
369
369
  "focus": {
370
- "ATTACK": 536.1,
371
- "SET": 331.8,
372
- "RECEIVE": 467.1,
370
+ "ATTACK": 536.2,
371
+ "SET": 418.7,
372
+ "RECEIVE": 467.2,
373
373
  "SERVE": 262.7,
374
- "BLOCK": 463.9,
374
+ "BLOCK": 464.2,
375
375
  "STAMINA": 79.2
376
376
  }
377
377
  },
378
378
  {
379
379
  "rarity": "SPECIAL",
380
380
  "role": "LIBERO",
381
- "samples": 89912,
382
- "roleBranchShare": 0.1147,
383
- "all": 1462.7,
381
+ "samples": 90012,
382
+ "roleBranchShare": 0.1131,
383
+ "all": 1466.8,
384
384
  "focus": {
385
- "ATTACK": 464.9,
386
- "SET": 332.7,
387
- "RECEIVE": 531.3,
388
- "SERVE": 240.9,
389
- "BLOCK": 490.4,
385
+ "ATTACK": 468.6,
386
+ "SET": 411.6,
387
+ "RECEIVE": 531.6,
388
+ "SERVE": 241,
389
+ "BLOCK": 490.6,
390
390
  "STAMINA": 80.3
391
391
  }
392
392
  }
@@ -11,19 +11,26 @@ import { getStatCap } from '../service/player/improvement';
11
11
  //
12
12
  // The drift guard is a HASH OF THE PARSED VALUES the floors depend on, not of any source text: hashing source
13
13
  // would fail on a comment edit and force a regeneration that rewrites every number with fresh sampling noise.
14
- // It therefore catches a retuned role weight, stat cap or rarity range, and does NOT catch a pure logic change
15
- // inside player-generator.ts. That case is covered by re-running the generator deliberately.
14
+ // It therefore catches a retuned role weight, stat cap, rarity range or COMPOSITE MULTIPLIER, and does NOT catch
15
+ // a pure logic change inside player-generator.ts. That case is covered by re-running the generator deliberately.
16
+ //
17
+ // The multipliers are in the hash because they DEFINE the focus pools (focusPoolColumns is literally
18
+ // Object.keys(getMultipliers(focus))), so adding a stat to a composite changes both the band divisor and which
19
+ // stats the focus can raise. Observed 2026-07-24: without them, adding `quick` to SET left every test passing on
20
+ // a stale file, because the "below ceiling" assertion reads pool size live and a bigger pool only raises the
21
+ // ceiling the old floor was already under.
16
22
  const RARITIES = [RarityEnum.COMMON, RarityEnum.RARE, RarityEnum.LEGENDARY, RarityEnum.MYTHIC, RarityEnum.SPECIAL];
17
23
  const ROLES = [RoleEnum.SETTER, RoleEnum.OUTSIDE_HITTER, RoleEnum.MIDDLE_BLOCKER, RoleEnum.OPPOSITE_HITTER, RoleEnum.LIBERO];
18
24
  const FOCI = [StatsEnum.ATTACK, StatsEnum.SET, StatsEnum.RECEIVE, StatsEnum.SERVE, StatsEnum.BLOCK, StatsEnum.STAMINA];
19
25
  const REGENERATE = 'improvement-floors.json is stale. Run: yarn build && yarn floors';
20
26
  describe('improvement-floors.json', () => {
21
- it('was generated from the current roles, caps and rarity ranges', () => {
27
+ it('was generated from the current roles, caps, rarity ranges and composite multipliers', () => {
22
28
  const canonical = JSON.stringify({
23
29
  roles: rolesJSON,
24
30
  statCaps: statCapsJSON,
25
31
  rarityRanges: RarityRanges,
26
- staminaRanges: StaminaRanges
32
+ staminaRanges: StaminaRanges,
33
+ multipliers: FOCI.map(focus => [focus, getMultipliers(focus)])
27
34
  });
28
35
  const expected = createHash('sha256').update(canonical).digest('hex');
29
36
  expect(`${improvementFloorsJSON.sourceHash} ${REGENERATE}`).toBe(`${expected} ${REGENERATE}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volleyballsimtypes",
3
- "version": "0.0.496",
3
+ "version": "0.0.497",
4
4
  "description": "vbsim types",
5
5
  "main": "./dist/cjs/src/index.js",
6
6
  "module": "./dist/esm/src/index.js",