volleyballsimtypes 0.0.481 → 0.0.483

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 (73) hide show
  1. package/dist/cjs/src/api/index.d.ts +10 -18
  2. package/dist/cjs/src/data/models/tactics.d.ts +17 -19
  3. package/dist/cjs/src/data/models/tactics.js +9 -0
  4. package/dist/cjs/src/data/transformers/match-preset-override.test.js +10 -1
  5. package/dist/cjs/src/data/transformers/rally-event.d.ts +2 -1
  6. package/dist/cjs/src/data/transformers/rally-event.js +12 -0
  7. package/dist/cjs/src/data/transformers/rally.js +2 -0
  8. package/dist/cjs/src/data/transformers/rally.test.d.ts +1 -0
  9. package/dist/cjs/src/data/transformers/rally.test.js +62 -0
  10. package/dist/cjs/src/data/transformers/tactics.js +38 -58
  11. package/dist/cjs/src/data/transformers/tactics.test.js +39 -20
  12. package/dist/cjs/src/service/team/blocking-preference.d.ts +5 -0
  13. package/dist/cjs/src/service/team/blocking-preference.js +2 -0
  14. package/dist/cjs/src/service/team/designated-sub.d.ts +2 -4
  15. package/dist/cjs/src/service/team/index.d.ts +2 -0
  16. package/dist/cjs/src/service/team/index.js +2 -0
  17. package/dist/cjs/src/service/team/libero-sub.d.ts +0 -4
  18. package/dist/cjs/src/service/team/offensive-preference.d.ts +1 -2
  19. package/dist/cjs/src/service/team/pinch-condition.d.ts +11 -3
  20. package/dist/cjs/src/service/team/pinch-condition.js +9 -4
  21. package/dist/cjs/src/service/team/schemas/designated-sub.z.d.ts +30 -32
  22. package/dist/cjs/src/service/team/schemas/designated-sub.z.js +27 -23
  23. package/dist/cjs/src/service/team/schemas/designated-sub.z.test.js +4 -2
  24. package/dist/cjs/src/service/team/schemas/libero-sub.z.d.ts +15 -34
  25. package/dist/cjs/src/service/team/schemas/libero-sub.z.js +10 -26
  26. package/dist/cjs/src/service/team/schemas/libero-sub.z.test.js +74 -35
  27. package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +39 -39
  28. package/dist/cjs/src/service/team/schemas/tactics.z.js +47 -13
  29. package/dist/cjs/src/service/team/schemas/tactics.z.test.js +81 -8
  30. package/dist/cjs/src/service/team/schemas/team.z.d.ts +38 -38
  31. package/dist/cjs/src/service/team/sub-config-convert.d.ts +13 -0
  32. package/dist/cjs/src/service/team/sub-config-convert.js +91 -0
  33. package/dist/cjs/src/service/team/sub-config-convert.test.d.ts +1 -0
  34. package/dist/cjs/src/service/team/sub-config-convert.test.js +84 -0
  35. package/dist/cjs/src/service/team/tactics.d.ts +5 -0
  36. package/dist/cjs/src/service/team/tactics.js +3 -1
  37. package/dist/esm/src/api/index.d.ts +10 -18
  38. package/dist/esm/src/data/models/tactics.d.ts +17 -19
  39. package/dist/esm/src/data/models/tactics.js +9 -0
  40. package/dist/esm/src/data/transformers/match-preset-override.test.js +10 -1
  41. package/dist/esm/src/data/transformers/rally-event.d.ts +2 -1
  42. package/dist/esm/src/data/transformers/rally-event.js +12 -1
  43. package/dist/esm/src/data/transformers/rally.js +4 -2
  44. package/dist/esm/src/data/transformers/rally.test.d.ts +1 -0
  45. package/dist/esm/src/data/transformers/rally.test.js +60 -0
  46. package/dist/esm/src/data/transformers/tactics.js +39 -59
  47. package/dist/esm/src/data/transformers/tactics.test.js +40 -21
  48. package/dist/esm/src/service/team/blocking-preference.d.ts +5 -0
  49. package/dist/esm/src/service/team/blocking-preference.js +1 -0
  50. package/dist/esm/src/service/team/designated-sub.d.ts +2 -4
  51. package/dist/esm/src/service/team/index.d.ts +2 -0
  52. package/dist/esm/src/service/team/index.js +2 -0
  53. package/dist/esm/src/service/team/libero-sub.d.ts +0 -4
  54. package/dist/esm/src/service/team/offensive-preference.d.ts +1 -2
  55. package/dist/esm/src/service/team/pinch-condition.d.ts +11 -3
  56. package/dist/esm/src/service/team/pinch-condition.js +9 -4
  57. package/dist/esm/src/service/team/schemas/designated-sub.z.d.ts +30 -32
  58. package/dist/esm/src/service/team/schemas/designated-sub.z.js +26 -22
  59. package/dist/esm/src/service/team/schemas/designated-sub.z.test.js +4 -2
  60. package/dist/esm/src/service/team/schemas/libero-sub.z.d.ts +15 -34
  61. package/dist/esm/src/service/team/schemas/libero-sub.z.js +9 -25
  62. package/dist/esm/src/service/team/schemas/libero-sub.z.test.js +75 -36
  63. package/dist/esm/src/service/team/schemas/tactics.z.d.ts +39 -39
  64. package/dist/esm/src/service/team/schemas/tactics.z.js +48 -14
  65. package/dist/esm/src/service/team/schemas/tactics.z.test.js +81 -8
  66. package/dist/esm/src/service/team/schemas/team.z.d.ts +38 -38
  67. package/dist/esm/src/service/team/sub-config-convert.d.ts +13 -0
  68. package/dist/esm/src/service/team/sub-config-convert.js +85 -0
  69. package/dist/esm/src/service/team/sub-config-convert.test.d.ts +1 -0
  70. package/dist/esm/src/service/team/sub-config-convert.test.js +82 -0
  71. package/dist/esm/src/service/team/tactics.d.ts +5 -0
  72. package/dist/esm/src/service/team/tactics.js +3 -1
  73. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { Player } from '../../player';
2
+ import { Player, RoleEnum } from '../../player';
3
3
  import { EnergyBand } from '../energy-band';
4
4
  import { RotationSystemEnum } from '../rotation-system';
5
5
  export declare const TacticsInputSchema: z.ZodObject<{
@@ -15,16 +15,9 @@ export declare const TacticsInputSchema: z.ZodObject<{
15
15
  }, z.core.$strip>;
16
16
  liberoReplacements: z.ZodArray<z.ZodCustom<Player, Player>>;
17
17
  receiveRotationOffset: z.ZodDefault<z.ZodBoolean>;
18
- designatedSubs: z.ZodDefault<z.ZodArray<z.ZodObject<{
19
- sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
20
- mode: z.ZodEnum<{
21
- NEVER: "NEVER";
22
- FATIGUE: "FATIGUE";
23
- PINCH: "PINCH";
24
- }>;
18
+ designatedSubs: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
19
+ sets: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
25
20
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
26
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
27
- benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
28
21
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
29
22
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
30
23
  }, z.core.$strip>, z.ZodObject<{
@@ -56,9 +49,13 @@ export declare const TacticsInputSchema: z.ZodObject<{
56
49
  }, z.core.$strip>, z.ZodObject<{
57
50
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
58
51
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
52
+ }, z.core.$strip>, z.ZodObject<{
53
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
54
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
59
55
  }, z.core.$strip>], "type">>>;
60
56
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
61
57
  pinchServer: z.ZodOptional<z.ZodBoolean>;
58
+ benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
62
59
  subBackMode: z.ZodOptional<z.ZodEnum<{
63
60
  FATIGUE: "FATIGUE";
64
61
  CONDITIONS: "CONDITIONS";
@@ -94,17 +91,13 @@ export declare const TacticsInputSchema: z.ZodObject<{
94
91
  }, z.core.$strip>, z.ZodObject<{
95
92
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
96
93
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
94
+ }, z.core.$strip>, z.ZodObject<{
95
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
96
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
97
97
  }, z.core.$strip>], "type">>>;
98
98
  subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
99
- }, z.core.$strip>>>;
100
- mode: z.ZodEnum<{
101
- NEVER: "NEVER";
102
- FATIGUE: "FATIGUE";
103
- PINCH: "PINCH";
104
- }>;
99
+ }, z.core.$strip>>>>;
105
100
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
106
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
107
- benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
108
101
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
109
102
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
110
103
  }, z.core.$strip>, z.ZodObject<{
@@ -136,9 +129,13 @@ export declare const TacticsInputSchema: z.ZodObject<{
136
129
  }, z.core.$strip>, z.ZodObject<{
137
130
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
138
131
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
132
+ }, z.core.$strip>, z.ZodObject<{
133
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
134
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
139
135
  }, z.core.$strip>], "type">>>;
140
136
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
141
137
  pinchServer: z.ZodOptional<z.ZodBoolean>;
138
+ benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
142
139
  subBackMode: z.ZodOptional<z.ZodEnum<{
143
140
  FATIGUE: "FATIGUE";
144
141
  CONDITIONS: "CONDITIONS";
@@ -174,21 +171,17 @@ export declare const TacticsInputSchema: z.ZodObject<{
174
171
  }, z.core.$strip>, z.ZodObject<{
175
172
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
176
173
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
174
+ }, z.core.$strip>, z.ZodObject<{
175
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
176
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
177
177
  }, z.core.$strip>], "type">>>;
178
178
  subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
179
179
  starter: z.ZodCustom<Player, Player>;
180
- }, z.core.$strip>>>;
180
+ }, z.core.$strip>>>>;
181
181
  substitutionBand: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<typeof EnergyBand>, z.ZodLiteral<"NEVER">]>>;
182
182
  secondLibero: z.ZodOptional<z.ZodCustom<Player, Player>>;
183
- liberoSub: z.ZodOptional<z.ZodObject<{
184
- sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
185
- mode: z.ZodEnum<{
186
- NEVER: "NEVER";
187
- FATIGUE: "FATIGUE";
188
- CONDITIONS: "CONDITIONS";
189
- ALWAYS: "ALWAYS";
190
- }>;
191
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
183
+ liberoSub: z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
184
+ sets: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
192
185
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
193
186
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
194
187
  }, z.core.$strip>, z.ZodObject<{
@@ -220,16 +213,12 @@ export declare const TacticsInputSchema: z.ZodObject<{
220
213
  }, z.core.$strip>, z.ZodObject<{
221
214
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
222
215
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
216
+ }, z.core.$strip>, z.ZodObject<{
217
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
218
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
223
219
  }, z.core.$strip>], "type">>>;
224
220
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
225
- }, z.core.$strip>>>;
226
- mode: z.ZodEnum<{
227
- NEVER: "NEVER";
228
- FATIGUE: "FATIGUE";
229
- CONDITIONS: "CONDITIONS";
230
- ALWAYS: "ALWAYS";
231
- }>;
232
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
221
+ }, z.core.$strip>>>>;
233
222
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
234
223
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
235
224
  }, z.core.$strip>, z.ZodObject<{
@@ -261,15 +250,18 @@ export declare const TacticsInputSchema: z.ZodObject<{
261
250
  }, z.core.$strip>, z.ZodObject<{
262
251
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
263
252
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
253
+ }, z.core.$strip>, z.ZodObject<{
254
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
255
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
264
256
  }, z.core.$strip>], "type">>>;
265
257
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
266
- }, z.core.$strip>>;
258
+ }, z.core.$strip>>>;
267
259
  liberoReplacementSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodCustom<Player, Player>>>>;
268
260
  rotationSystem: z.ZodDefault<z.ZodEnum<typeof RotationSystemEnum>>;
269
261
  designatedSetters: z.ZodDefault<z.ZodArray<z.ZodCustom<Player, Player>>>;
270
262
  offensivePreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
271
263
  rotation: z.ZodNumber;
272
- order: z.ZodArray<z.ZodCustom<Player, Player>>;
264
+ weights: z.ZodRecord<z.ZodString, z.ZodNumber>;
273
265
  tempos: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
274
266
  FAST: "FAST";
275
267
  HIGH: "HIGH";
@@ -281,12 +273,20 @@ export declare const TacticsInputSchema: z.ZodObject<{
281
273
  }, z.core.$strip>>>;
282
274
  offensivePreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
283
275
  rotation: z.ZodNumber;
284
- order: z.ZodArray<z.ZodCustom<Player, Player>>;
276
+ weights: z.ZodRecord<z.ZodString, z.ZodNumber>;
285
277
  tempos: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
286
278
  FAST: "FAST";
287
279
  HIGH: "HIGH";
288
280
  }>>>;
289
281
  }, z.core.$strip>>>>;
282
+ blockingPreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
283
+ rotation: z.ZodNumber;
284
+ targets: z.ZodRecord<z.ZodString, z.ZodEnum<typeof RoleEnum> & z.ZodType<RoleEnum.SETTER | RoleEnum.OUTSIDE_HITTER | RoleEnum.OPPOSITE_HITTER | RoleEnum.MIDDLE_BLOCKER, RoleEnum, z.core.$ZodTypeInternals<RoleEnum.SETTER | RoleEnum.OUTSIDE_HITTER | RoleEnum.OPPOSITE_HITTER | RoleEnum.MIDDLE_BLOCKER, RoleEnum>>>;
285
+ }, z.core.$strip>>>;
286
+ blockingPreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
287
+ rotation: z.ZodNumber;
288
+ targets: z.ZodRecord<z.ZodString, z.ZodEnum<typeof RoleEnum> & z.ZodType<RoleEnum.SETTER | RoleEnum.OUTSIDE_HITTER | RoleEnum.OPPOSITE_HITTER | RoleEnum.MIDDLE_BLOCKER, RoleEnum, z.core.$ZodTypeInternals<RoleEnum.SETTER | RoleEnum.OUTSIDE_HITTER | RoleEnum.OPPOSITE_HITTER | RoleEnum.MIDDLE_BLOCKER, RoleEnum>>>;
289
+ }, z.core.$strip>>>>;
290
290
  replaceKnockedImmediately: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
291
291
  injuryReplacements: z.ZodDefault<z.ZodArray<z.ZodObject<{
292
292
  starter: z.ZodCustom<Player, Player>;
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { Player } from '../../player';
2
+ import { Player, RoleEnum } from '../../player';
3
3
  import { EnergyBand } from '../energy-band';
4
4
  import { RotationSystemEnum, setterCountFor } from '../rotation-system';
5
5
  import { DesignatedSubSchema, SubBandSchema } from './designated-sub.z';
@@ -47,14 +47,23 @@ const lineupSchema = z.object({
47
47
  });
48
48
  // Per-player tempo preference (set-tempo rework): absent key = Auto.
49
49
  const tempoPreferenceSchema = z.enum(['FAST', 'HIGH']);
50
- // One rotation's ranked attacker priority. `rotation` is the main setter's rotational slot (1..6); `order` lists
51
- // the attackers ranked best-first. `tempos` optionally forces a per-starter tempo (Auto when absent). Cross-field
52
- // checks (starters only in both order and tempos, no dupes) live in the superRefine below.
50
+ // One rotation's attacker weighting. `rotation` is the main setter's rotational slot (1..6); `weights` maps each
51
+ // attacker's starter id to an integer percentage share (values sum to 100; 0 removes an attacker). `tempos`
52
+ // optionally forces a per-starter tempo (Auto when absent). Cross-field checks (keys are starters, weights sum to
53
+ // 100) live in the superRefine below.
53
54
  const offensivePreferenceSchema = z.object({
54
55
  rotation: z.number().int().min(1).max(6),
55
- order: z.array(playerInstanceSchema),
56
+ weights: z.record(z.string(), z.number().int().min(0)),
56
57
  tempos: z.record(z.string(), tempoPreferenceSchema).optional()
57
58
  });
59
+ // The opposing attacking functions a dedicated blocker can commit to: every RoleEnum except the libero.
60
+ const blockingFunctionSchema = z.nativeEnum(RoleEnum).refine((v) => v !== RoleEnum.LIBERO, { message: 'BLOCKING_TARGET_CANNOT_BE_LIBERO' });
61
+ // One rotation's dedicated-blocking assignments: `targets` maps my front-row starter ids to the opposing function
62
+ // each commits to block. Cross-field checks (keys are starters) live in the superRefine below.
63
+ const blockingPreferenceSchema = z.object({
64
+ rotation: z.number().int().min(1).max(6),
65
+ targets: z.record(z.string(), blockingFunctionSchema)
66
+ });
58
67
  // One set's rotation-system override (per-set tactics): system + its designated setters travel together.
59
68
  const systemSetSchema = z.object({
60
69
  rotationSystem: z.nativeEnum(RotationSystemEnum),
@@ -82,6 +91,9 @@ export const TacticsInputSchema = z.object({
82
91
  // validated with the same cross-field rules as the flat fields (see superRefine below).
83
92
  systemSets: z.array(systemSetSchema).max(5).optional(),
84
93
  offensivePreferenceSets: z.array(z.array(offensivePreferenceSchema)).max(5).optional(),
94
+ // Blocking preferences (dedicated blocking). Defaults keep every existing Tactics.create({...}) call valid.
95
+ blockingPreferences: z.array(blockingPreferenceSchema).default([]),
96
+ blockingPreferenceSets: z.array(z.array(blockingPreferenceSchema)).max(5).optional(),
85
97
  // Injuries. Defaults keep every existing Tactics.create({...}) call valid. replaceKnockedImmediately is
86
98
  // PER STARTER (owner order 2026-07-05): keyed by starter id, missing key = ON; keys validated against the
87
99
  // lineup in the superRefine below. injuryReplacements is the per-starter explicit replacement mapping
@@ -150,16 +162,17 @@ export const TacticsInputSchema = z.object({
150
162
  ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_DUPLICATE_ROTATION', path });
151
163
  }
152
164
  seenRotations.add(pref.rotation);
153
- const ids = pref.order.map(p => p.id);
154
- if (new Set(ids).size !== ids.length) {
155
- ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_DUPLICATE_ATTACKER', path });
156
- }
157
- for (const id of ids) {
165
+ // Weight keys are on-court starters, and the percentages must sum to exactly 100 (0 removes an attacker).
166
+ for (const id of Object.keys(pref.weights)) {
158
167
  if (!starterIds.has(id)) {
159
168
  ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_ATTACKER_NOT_A_STARTER', path });
160
169
  }
161
170
  }
162
- // Tempo preference keys are starter ids too (same rule as order).
171
+ const total = Object.values(pref.weights).reduce((sum, w) => sum + w, 0);
172
+ if (total !== 100) {
173
+ ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_WEIGHTS_MUST_SUM_TO_100', path });
174
+ }
175
+ // Tempo preference keys are starter ids too.
163
176
  for (const id of Object.keys(pref.tempos ?? {})) {
164
177
  if (!starterIds.has(id)) {
165
178
  ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_TEMPO_NOT_A_STARTER', path });
@@ -167,6 +180,22 @@ export const TacticsInputSchema = z.object({
167
180
  }
168
181
  }
169
182
  };
183
+ // Blocking preferences: unique rotations; each assignment keys an on-court starter (the UI only offers front-row
184
+ // players, the schema checks starters). The target function is validated by blockingFunctionSchema.
185
+ const validateBlockingPreferences = (preferences, path) => {
186
+ const seenRotations = new Set();
187
+ for (const pref of preferences) {
188
+ if (seenRotations.has(pref.rotation)) {
189
+ ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_DUPLICATE_ROTATION', path });
190
+ }
191
+ seenRotations.add(pref.rotation);
192
+ for (const id of Object.keys(pref.targets)) {
193
+ if (!starterIds.has(id)) {
194
+ ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER', path });
195
+ }
196
+ }
197
+ }
198
+ };
170
199
  validateSystem(data.rotationSystem, data.designatedSetters, ['designatedSetters']);
171
200
  for (const [i, systemSet] of (data.systemSets ?? []).entries()) {
172
201
  validateSystem(systemSet.rotationSystem, systemSet.designatedSetters, ['systemSets', i]);
@@ -175,6 +204,10 @@ export const TacticsInputSchema = z.object({
175
204
  for (const [i, preferences] of (data.offensivePreferenceSets ?? []).entries()) {
176
205
  validateOffensivePreferences(preferences, ['offensivePreferenceSets', i]);
177
206
  }
207
+ validateBlockingPreferences(data.blockingPreferences, ['blockingPreferences']);
208
+ for (const [i, preferences] of (data.blockingPreferenceSets ?? []).entries()) {
209
+ validateBlockingPreferences(preferences, ['blockingPreferenceSets', i]);
210
+ }
178
211
  // Second libero (L2). The second libero is a reserve (not a starter, the libero, or a bench player). A swap
179
212
  // rule (FATIGUE/ALWAYS in any set) needs someone to swap in, so it requires a second libero, and a second
180
213
  // libero needs a starting libero to replace ("no L2 without an L"). Per-set libero replacements, like the
@@ -185,10 +218,11 @@ export const TacticsInputSchema = z.object({
185
218
  ...(libero != null ? [libero.id] : []),
186
219
  ...data.lineup.bench.map(p => p.id)
187
220
  ]);
188
- const liberoSubModes = data.liberoSub != null
189
- ? [data.liberoSub.mode, ...(data.liberoSub.sets ?? []).map(s => s.mode)]
221
+ // Unified condition engine: the rule wants a swap when ANY config (flat or per-set) has conditions.
222
+ const liberoConditionSets = data.liberoSub != null
223
+ ? [data.liberoSub.conditions ?? [], ...(data.liberoSub.sets ?? []).map(s => s.conditions ?? [])]
190
224
  : [];
191
- const wantsLiberoSwap = liberoSubModes.some(mode => mode !== 'NEVER');
225
+ const wantsLiberoSwap = liberoConditionSets.some(conditions => conditions.length > 0);
192
226
  if (wantsLiberoSwap && data.secondLibero == null) {
193
227
  ctx.addIssue({ code: 'custom', message: 'LIBERO_SWAP_REQUIRES_SECOND_LIBERO', path: ['secondLibero'] });
194
228
  }
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect } from '@jest/globals';
2
2
  import { TacticsInputSchema } from './tactics.z';
3
+ import { RoleEnum } from '../../player';
3
4
  import { makeCountry, makePlayer } from '../../test-helpers';
4
5
  describe('TacticsInputSchema — second libero rules', () => {
5
6
  const country = makeCountry();
@@ -93,7 +94,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
93
94
  { rotationSystem: '4-2', designatedSetters: [p1, p4] }
94
95
  ],
95
96
  offensivePreferenceSets: [
96
- [{ rotation: 1, order: [p2, p3] }],
97
+ [{ rotation: 1, weights: { [p2.id]: 60, [p3.id]: 40 } }],
97
98
  []
98
99
  ]
99
100
  }));
@@ -102,7 +103,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
102
103
  it('carries the per-set fields through Tactics.create parsing', () => {
103
104
  const res = TacticsInputSchema.safeParse(baseInput({
104
105
  systemSets: [{ rotationSystem: '5-1', designatedSetters: [p1] }],
105
- offensivePreferenceSets: [[{ rotation: 2, order: [p3] }]]
106
+ offensivePreferenceSets: [[{ rotation: 2, weights: { [p3.id]: 100 } }]]
106
107
  }));
107
108
  expect(res.success).toBe(true);
108
109
  if (res.success) {
@@ -151,7 +152,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
151
152
  });
152
153
  it('rejects a per-set preferences list with a duplicate rotation', () => {
153
154
  const res = TacticsInputSchema.safeParse(baseInput({
154
- offensivePreferenceSets: [[{ rotation: 1, order: [p2] }, { rotation: 1, order: [p3] }]]
155
+ offensivePreferenceSets: [[{ rotation: 1, weights: { [p2.id]: 100 } }, { rotation: 1, weights: { [p3.id]: 100 } }]]
155
156
  }));
156
157
  expect(res.success).toBe(false);
157
158
  if (!res.success) {
@@ -159,18 +160,27 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
159
160
  expect(issue?.path).toEqual(['offensivePreferenceSets', 0]);
160
161
  }
161
162
  });
162
- it('rejects a per-set preferences order that lists a non-starter', () => {
163
+ it('rejects a per-set preferences map that keys a non-starter', () => {
163
164
  const res = TacticsInputSchema.safeParse(baseInput({
164
- offensivePreferenceSets: [[{ rotation: 1, order: [reserve] }]]
165
+ offensivePreferenceSets: [[{ rotation: 1, weights: { [reserve.id]: 100 } }]]
165
166
  }));
166
167
  expect(res.success).toBe(false);
167
168
  if (!res.success) {
168
169
  expect(res.error.issues.some(i => i.message === 'OFFENSIVE_PREFERENCE_ATTACKER_NOT_A_STARTER')).toBe(true);
169
170
  }
170
171
  });
172
+ it('rejects offensive preference weights that do not sum to 100', () => {
173
+ const res = TacticsInputSchema.safeParse(baseInput({
174
+ offensivePreferences: [{ rotation: 1, weights: { [p2.id]: 50, [p3.id]: 40 } }]
175
+ }));
176
+ expect(res.success).toBe(false);
177
+ if (!res.success) {
178
+ expect(res.error.issues.some(i => i.message === 'OFFENSIVE_PREFERENCE_WEIGHTS_MUST_SUM_TO_100')).toBe(true);
179
+ }
180
+ });
171
181
  it('accepts offensive preferences with a starter-keyed tempos map', () => {
172
182
  const res = TacticsInputSchema.safeParse(baseInput({
173
- offensivePreferences: [{ rotation: 1, order: [p2, p3], tempos: { [p2.id]: 'FAST', [p3.id]: 'HIGH' } }]
183
+ offensivePreferences: [{ rotation: 1, weights: { [p2.id]: 60, [p3.id]: 40 }, tempos: { [p2.id]: 'FAST', [p3.id]: 'HIGH' } }]
174
184
  }));
175
185
  expect(res.success).toBe(true);
176
186
  if (res.success) {
@@ -179,7 +189,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
179
189
  });
180
190
  it('rejects a tempos map keyed by a non-starter', () => {
181
191
  const res = TacticsInputSchema.safeParse(baseInput({
182
- offensivePreferences: [{ rotation: 1, order: [p2], tempos: { [reserve.id]: 'FAST' } }]
192
+ offensivePreferences: [{ rotation: 1, weights: { [p2.id]: 100 }, tempos: { [reserve.id]: 'FAST' } }]
183
193
  }));
184
194
  expect(res.success).toBe(false);
185
195
  if (!res.success) {
@@ -188,7 +198,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
188
198
  });
189
199
  it('rejects an invalid tempo value', () => {
190
200
  const res = TacticsInputSchema.safeParse(baseInput({
191
- offensivePreferences: [{ rotation: 1, order: [p2], tempos: { [p2.id]: 'SLOW' } }]
201
+ offensivePreferences: [{ rotation: 1, weights: { [p2.id]: 100 }, tempos: { [p2.id]: 'SLOW' } }]
192
202
  }));
193
203
  expect(res.success).toBe(false);
194
204
  });
@@ -206,3 +216,66 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
206
216
  }
207
217
  });
208
218
  });
219
+ describe('TacticsInputSchema — blocking preferences', () => {
220
+ const country = makeCountry();
221
+ const p1 = makePlayer(country);
222
+ const p2 = makePlayer(country);
223
+ const p3 = makePlayer(country);
224
+ const p4 = makePlayer(country);
225
+ const p5 = makePlayer(country);
226
+ const p6 = makePlayer(country);
227
+ const reserve = makePlayer(country);
228
+ // Front-row starters (zones 2/3/4) are p2, p3, p4; the validator only requires target keys to be starters.
229
+ function baseInput(overrides = {}) {
230
+ return {
231
+ lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
232
+ liberoReplacements: [],
233
+ ...overrides
234
+ };
235
+ }
236
+ it('accepts a blocking preference that keys starters to attacking functions', () => {
237
+ const res = TacticsInputSchema.safeParse(baseInput({
238
+ blockingPreferences: [{ rotation: 1, targets: { [p4.id]: RoleEnum.OUTSIDE_HITTER, [p3.id]: RoleEnum.MIDDLE_BLOCKER } }]
239
+ }));
240
+ expect(res.success).toBe(true);
241
+ if (res.success) {
242
+ expect(res.data.blockingPreferences[0].targets[p4.id]).toBe(RoleEnum.OUTSIDE_HITTER);
243
+ }
244
+ });
245
+ it('rejects a blocking target that is not a starter', () => {
246
+ const res = TacticsInputSchema.safeParse(baseInput({
247
+ blockingPreferences: [{ rotation: 1, targets: { [reserve.id]: RoleEnum.OUTSIDE_HITTER } }]
248
+ }));
249
+ expect(res.success).toBe(false);
250
+ if (!res.success) {
251
+ expect(res.error.issues.some(i => i.message === 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER')).toBe(true);
252
+ }
253
+ });
254
+ it('rejects a blocking target function of libero', () => {
255
+ const res = TacticsInputSchema.safeParse(baseInput({
256
+ blockingPreferences: [{ rotation: 1, targets: { [p4.id]: RoleEnum.LIBERO } }]
257
+ }));
258
+ expect(res.success).toBe(false);
259
+ if (!res.success) {
260
+ expect(res.error.issues.some(i => i.message === 'BLOCKING_TARGET_CANNOT_BE_LIBERO')).toBe(true);
261
+ }
262
+ });
263
+ it('rejects a duplicate rotation in blocking preferences', () => {
264
+ const res = TacticsInputSchema.safeParse(baseInput({
265
+ blockingPreferences: [
266
+ { rotation: 1, targets: { [p4.id]: RoleEnum.OUTSIDE_HITTER } },
267
+ { rotation: 1, targets: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } }
268
+ ]
269
+ }));
270
+ expect(res.success).toBe(false);
271
+ if (!res.success) {
272
+ expect(res.error.issues.some(i => i.message === 'BLOCKING_PREFERENCE_DUPLICATE_ROTATION')).toBe(true);
273
+ }
274
+ });
275
+ it('accepts per-set blocking preference overrides', () => {
276
+ const res = TacticsInputSchema.safeParse(baseInput({
277
+ blockingPreferenceSets: [[{ rotation: 1, targets: { [p4.id]: RoleEnum.OPPOSITE_HITTER } }], []]
278
+ }));
279
+ expect(res.success).toBe(true);
280
+ });
281
+ });
@@ -23,16 +23,9 @@ export declare const TeamInputSchema: z.ZodObject<{
23
23
  }, z.core.$strip>;
24
24
  liberoReplacements: z.ZodArray<z.ZodCustom<Player, Player>>;
25
25
  receiveRotationOffset: z.ZodDefault<z.ZodBoolean>;
26
- designatedSubs: z.ZodDefault<z.ZodArray<z.ZodObject<{
27
- sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
28
- mode: z.ZodEnum<{
29
- NEVER: "NEVER";
30
- FATIGUE: "FATIGUE";
31
- PINCH: "PINCH";
32
- }>;
26
+ designatedSubs: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
27
+ sets: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
33
28
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
34
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
35
- benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
36
29
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
37
30
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
38
31
  }, z.core.$strip>, z.ZodObject<{
@@ -64,9 +57,13 @@ export declare const TeamInputSchema: z.ZodObject<{
64
57
  }, z.core.$strip>, z.ZodObject<{
65
58
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
66
59
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
60
+ }, z.core.$strip>, z.ZodObject<{
61
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
62
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
67
63
  }, z.core.$strip>], "type">>>;
68
64
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
69
65
  pinchServer: z.ZodOptional<z.ZodBoolean>;
66
+ benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
70
67
  subBackMode: z.ZodOptional<z.ZodEnum<{
71
68
  FATIGUE: "FATIGUE";
72
69
  CONDITIONS: "CONDITIONS";
@@ -102,17 +99,13 @@ export declare const TeamInputSchema: z.ZodObject<{
102
99
  }, z.core.$strip>, z.ZodObject<{
103
100
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
104
101
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
102
+ }, z.core.$strip>, z.ZodObject<{
103
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
104
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
105
105
  }, z.core.$strip>], "type">>>;
106
106
  subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
107
- }, z.core.$strip>>>;
108
- mode: z.ZodEnum<{
109
- NEVER: "NEVER";
110
- FATIGUE: "FATIGUE";
111
- PINCH: "PINCH";
112
- }>;
107
+ }, z.core.$strip>>>>;
113
108
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
114
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
115
- benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
116
109
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
117
110
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
118
111
  }, z.core.$strip>, z.ZodObject<{
@@ -144,9 +137,13 @@ export declare const TeamInputSchema: z.ZodObject<{
144
137
  }, z.core.$strip>, z.ZodObject<{
145
138
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
146
139
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
140
+ }, z.core.$strip>, z.ZodObject<{
141
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
142
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
147
143
  }, z.core.$strip>], "type">>>;
148
144
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
149
145
  pinchServer: z.ZodOptional<z.ZodBoolean>;
146
+ benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
150
147
  subBackMode: z.ZodOptional<z.ZodEnum<{
151
148
  FATIGUE: "FATIGUE";
152
149
  CONDITIONS: "CONDITIONS";
@@ -182,21 +179,17 @@ export declare const TeamInputSchema: z.ZodObject<{
182
179
  }, z.core.$strip>, z.ZodObject<{
183
180
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
184
181
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
182
+ }, z.core.$strip>, z.ZodObject<{
183
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
184
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
185
185
  }, z.core.$strip>], "type">>>;
186
186
  subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
187
187
  starter: z.ZodCustom<Player, Player>;
188
- }, z.core.$strip>>>;
188
+ }, z.core.$strip>>>>;
189
189
  substitutionBand: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<typeof import("..").EnergyBand>, z.ZodLiteral<"NEVER">]>>;
190
190
  secondLibero: z.ZodOptional<z.ZodCustom<Player, Player>>;
191
- liberoSub: z.ZodOptional<z.ZodObject<{
192
- sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
193
- mode: z.ZodEnum<{
194
- NEVER: "NEVER";
195
- FATIGUE: "FATIGUE";
196
- CONDITIONS: "CONDITIONS";
197
- ALWAYS: "ALWAYS";
198
- }>;
199
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
191
+ liberoSub: z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
192
+ sets: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
200
193
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
201
194
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
202
195
  }, z.core.$strip>, z.ZodObject<{
@@ -228,16 +221,12 @@ export declare const TeamInputSchema: z.ZodObject<{
228
221
  }, z.core.$strip>, z.ZodObject<{
229
222
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
230
223
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
224
+ }, z.core.$strip>, z.ZodObject<{
225
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
226
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
231
227
  }, z.core.$strip>], "type">>>;
232
228
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
233
- }, z.core.$strip>>>;
234
- mode: z.ZodEnum<{
235
- NEVER: "NEVER";
236
- FATIGUE: "FATIGUE";
237
- CONDITIONS: "CONDITIONS";
238
- ALWAYS: "ALWAYS";
239
- }>;
240
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
229
+ }, z.core.$strip>>>>;
241
230
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
242
231
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
243
232
  }, z.core.$strip>, z.ZodObject<{
@@ -269,15 +258,18 @@ export declare const TeamInputSchema: z.ZodObject<{
269
258
  }, z.core.$strip>, z.ZodObject<{
270
259
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
271
260
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
261
+ }, z.core.$strip>, z.ZodObject<{
262
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
263
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
272
264
  }, z.core.$strip>], "type">>>;
273
265
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
274
- }, z.core.$strip>>;
266
+ }, z.core.$strip>>>;
275
267
  liberoReplacementSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodCustom<Player, Player>>>>;
276
268
  rotationSystem: z.ZodDefault<z.ZodEnum<typeof import("..").RotationSystemEnum>>;
277
269
  designatedSetters: z.ZodDefault<z.ZodArray<z.ZodCustom<Player, Player>>>;
278
270
  offensivePreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
279
271
  rotation: z.ZodNumber;
280
- order: z.ZodArray<z.ZodCustom<Player, Player>>;
272
+ weights: z.ZodRecord<z.ZodString, z.ZodNumber>;
281
273
  tempos: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
282
274
  FAST: "FAST";
283
275
  HIGH: "HIGH";
@@ -289,12 +281,20 @@ export declare const TeamInputSchema: z.ZodObject<{
289
281
  }, z.core.$strip>>>;
290
282
  offensivePreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
291
283
  rotation: z.ZodNumber;
292
- order: z.ZodArray<z.ZodCustom<Player, Player>>;
284
+ weights: z.ZodRecord<z.ZodString, z.ZodNumber>;
293
285
  tempos: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
294
286
  FAST: "FAST";
295
287
  HIGH: "HIGH";
296
288
  }>>>;
297
289
  }, z.core.$strip>>>>;
290
+ blockingPreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
291
+ rotation: z.ZodNumber;
292
+ targets: z.ZodRecord<z.ZodString, z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>>;
293
+ }, z.core.$strip>>>;
294
+ blockingPreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
295
+ rotation: z.ZodNumber;
296
+ targets: z.ZodRecord<z.ZodString, z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>>;
297
+ }, z.core.$strip>>>>;
298
298
  replaceKnockedImmediately: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
299
299
  injuryReplacements: z.ZodDefault<z.ZodArray<z.ZodObject<{
300
300
  starter: z.ZodCustom<Player, Player>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Convert ONE legacy set-sub config (flat or per-set entry). Handles BOTH legacy generations:
3
+ * mode-era rows ({mode: NEVER|FATIGUE|PINCH}) and the oldest isPinchServer-era rows (no mode;
4
+ * isPinchServer flag + a 'NEVER' fatigueBand for the old never-sub state). Non-records and
5
+ * already-unified shapes (neither mode nor isPinchServer nor fatigueBand keys) pass through.
6
+ */
7
+ export declare function convertLegacySetSubConfig(raw: unknown): unknown;
8
+ /** Convert a legacy DesignatedSub (flat fields + per-set entries); unified shapes pass through. */
9
+ export declare function convertLegacyDesignatedSub(raw: unknown): unknown;
10
+ /** Convert ONE legacy libero set-sub config; unified shapes pass through. */
11
+ export declare function convertLegacyLiberoSetSubConfig(raw: unknown): unknown;
12
+ /** Convert a legacy LiberoSubConfig (flat fields + per-set entries); unified shapes pass through. */
13
+ export declare function convertLegacyLiberoSubConfig(raw: unknown): unknown;