volleyballsimtypes 0.0.473 → 0.0.475
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/api/index.d.ts +8 -0
- package/dist/cjs/src/data/models/tactics.d.ts +10 -1
- package/dist/cjs/src/data/models/tactics.js +4 -0
- package/dist/cjs/src/data/transformers/tactics.js +66 -2
- package/dist/cjs/src/data/transformers/tactics.test.js +44 -0
- package/dist/cjs/src/service/team/index.d.ts +1 -0
- package/dist/cjs/src/service/team/index.js +1 -0
- package/dist/cjs/src/service/team/libero-injury.d.ts +6 -0
- package/dist/cjs/src/service/team/libero-injury.js +2 -0
- package/dist/cjs/src/service/team/libero-sub.d.ts +4 -1
- package/dist/cjs/src/service/team/pinch-condition.d.ts +12 -2
- package/dist/cjs/src/service/team/pinch-condition.js +10 -2
- package/dist/cjs/src/service/team/schemas/designated-sub.z.d.ts +13 -1
- package/dist/cjs/src/service/team/schemas/designated-sub.z.js +3 -1
- package/dist/cjs/src/service/team/schemas/libero-injury.z.test.d.ts +1 -0
- package/dist/cjs/src/service/team/schemas/libero-injury.z.test.js +71 -0
- package/dist/cjs/src/service/team/schemas/libero-sub.z.d.ts +94 -0
- package/dist/cjs/src/service/team/schemas/libero-sub.z.js +23 -6
- package/dist/cjs/src/service/team/schemas/libero-sub.z.test.js +33 -2
- package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +73 -0
- package/dist/cjs/src/service/team/schemas/tactics.z.js +41 -1
- package/dist/cjs/src/service/team/schemas/team.z.d.ts +73 -0
- package/dist/cjs/src/service/team/tactics.d.ts +3 -0
- package/dist/cjs/src/service/team/tactics.js +2 -1
- package/dist/esm/src/api/index.d.ts +8 -0
- package/dist/esm/src/data/models/tactics.d.ts +10 -1
- package/dist/esm/src/data/models/tactics.js +4 -0
- package/dist/esm/src/data/transformers/tactics.js +66 -2
- package/dist/esm/src/data/transformers/tactics.test.js +44 -0
- package/dist/esm/src/service/team/index.d.ts +1 -0
- package/dist/esm/src/service/team/index.js +1 -0
- package/dist/esm/src/service/team/libero-injury.d.ts +6 -0
- package/dist/esm/src/service/team/libero-injury.js +1 -0
- package/dist/esm/src/service/team/libero-sub.d.ts +4 -1
- package/dist/esm/src/service/team/pinch-condition.d.ts +12 -2
- package/dist/esm/src/service/team/pinch-condition.js +9 -1
- package/dist/esm/src/service/team/schemas/designated-sub.z.d.ts +13 -1
- package/dist/esm/src/service/team/schemas/designated-sub.z.js +4 -2
- package/dist/esm/src/service/team/schemas/libero-injury.z.test.d.ts +1 -0
- package/dist/esm/src/service/team/schemas/libero-injury.z.test.js +69 -0
- package/dist/esm/src/service/team/schemas/libero-sub.z.d.ts +94 -0
- package/dist/esm/src/service/team/schemas/libero-sub.z.js +23 -6
- package/dist/esm/src/service/team/schemas/libero-sub.z.test.js +33 -2
- package/dist/esm/src/service/team/schemas/tactics.z.d.ts +73 -0
- package/dist/esm/src/service/team/schemas/tactics.z.js +41 -1
- package/dist/esm/src/service/team/schemas/team.z.d.ts +73 -0
- package/dist/esm/src/service/team/tactics.d.ts +3 -0
- package/dist/esm/src/service/team/tactics.js +2 -1
- package/package.json +1 -1
|
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const globals_1 = require("@jest/globals");
|
|
4
4
|
const libero_sub_z_1 = require("./libero-sub.z");
|
|
5
5
|
const energy_band_1 = require("../energy-band");
|
|
6
|
+
const pinch_condition_1 = require("../pinch-condition");
|
|
6
7
|
(0, globals_1.describe)('LiberoSubModeSchema', () => {
|
|
7
|
-
(0, globals_1.it)('accepts NEVER, FATIGUE and
|
|
8
|
-
for (const mode of ['NEVER', 'FATIGUE', 'ALWAYS']) {
|
|
8
|
+
(0, globals_1.it)('accepts NEVER, FATIGUE, ALWAYS and CONDITIONS', () => {
|
|
9
|
+
for (const mode of ['NEVER', 'FATIGUE', 'ALWAYS', 'CONDITIONS']) {
|
|
9
10
|
(0, globals_1.expect)(libero_sub_z_1.LiberoSubModeSchema.safeParse(mode).success).toBe(true);
|
|
10
11
|
}
|
|
11
12
|
});
|
|
@@ -23,6 +24,36 @@ const energy_band_1 = require("../energy-band");
|
|
|
23
24
|
(0, globals_1.it)('rejects an unknown band', () => {
|
|
24
25
|
(0, globals_1.expect)(libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: 'SLEEPY' }).success).toBe(false);
|
|
25
26
|
});
|
|
27
|
+
(0, globals_1.it)('accepts CONDITIONS with a PHASE condition (serve/receive split)', () => {
|
|
28
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({
|
|
29
|
+
mode: 'CONDITIONS',
|
|
30
|
+
conditionLogic: 'ALL',
|
|
31
|
+
conditions: [{ type: pinch_condition_1.PinchConditionType.PHASE, phase: pinch_condition_1.MatchPhase.RECEIVING }]
|
|
32
|
+
});
|
|
33
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
(0, globals_1.it)('accepts CONDITIONS mixing a pinch condition and a PHASE condition', () => {
|
|
36
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({
|
|
37
|
+
mode: 'CONDITIONS',
|
|
38
|
+
conditionLogic: 'ANY',
|
|
39
|
+
conditions: [
|
|
40
|
+
{ type: pinch_condition_1.PinchConditionType.MIN_OWN_SCORE, score: 20 },
|
|
41
|
+
{ type: pinch_condition_1.PinchConditionType.PHASE, phase: pinch_condition_1.MatchPhase.SERVING }
|
|
42
|
+
]
|
|
43
|
+
});
|
|
44
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
(0, globals_1.it)('rejects CONDITIONS mode with no conditions', () => {
|
|
47
|
+
(0, globals_1.expect)(libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'CONDITIONS' }).success).toBe(false);
|
|
48
|
+
(0, globals_1.expect)(libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'CONDITIONS', conditions: [] }).success).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
(0, globals_1.it)('rejects an invalid PHASE value', () => {
|
|
51
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({
|
|
52
|
+
mode: 'CONDITIONS',
|
|
53
|
+
conditions: [{ type: pinch_condition_1.PinchConditionType.PHASE, phase: 'ATTACKING' }]
|
|
54
|
+
});
|
|
55
|
+
(0, globals_1.expect)(res.success).toBe(false);
|
|
56
|
+
});
|
|
26
57
|
});
|
|
27
58
|
(0, globals_1.describe)('LiberoSubConfigSchema', () => {
|
|
28
59
|
(0, globals_1.it)('accepts a flat config with no per-set overrides', () => {
|
|
@@ -50,6 +50,9 @@ export declare const TacticsInputSchema: z.ZodObject<{
|
|
|
50
50
|
}, z.core.$strip>, z.ZodObject<{
|
|
51
51
|
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
52
52
|
score: z.ZodNumber;
|
|
53
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
54
|
+
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
55
|
+
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|
|
53
56
|
}, z.core.$strip>], "type">>>;
|
|
54
57
|
conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
|
|
55
58
|
}, z.core.$strip>>>;
|
|
@@ -86,6 +89,9 @@ export declare const TacticsInputSchema: z.ZodObject<{
|
|
|
86
89
|
}, z.core.$strip>, z.ZodObject<{
|
|
87
90
|
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
88
91
|
score: z.ZodNumber;
|
|
92
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
93
|
+
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
94
|
+
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|
|
89
95
|
}, z.core.$strip>], "type">>>;
|
|
90
96
|
conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
|
|
91
97
|
starter: z.ZodCustom<Player, Player>;
|
|
@@ -98,15 +104,77 @@ export declare const TacticsInputSchema: z.ZodObject<{
|
|
|
98
104
|
NEVER: "NEVER";
|
|
99
105
|
FATIGUE: "FATIGUE";
|
|
100
106
|
ALWAYS: "ALWAYS";
|
|
107
|
+
CONDITIONS: "CONDITIONS";
|
|
101
108
|
}>;
|
|
102
109
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
110
|
+
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
111
|
+
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
112
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
113
|
+
type: z.ZodLiteral<import("..").PinchConditionType.TEAM_SET_POINT>;
|
|
114
|
+
margin: z.ZodNumber;
|
|
115
|
+
opponent: z.ZodEnum<typeof import("..").OpponentRelation>;
|
|
116
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
117
|
+
type: z.ZodLiteral<import("..").PinchConditionType.OPPONENT_SET_POINT>;
|
|
118
|
+
margin: z.ZodNumber;
|
|
119
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
120
|
+
type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
|
|
121
|
+
direction: z.ZodEnum<typeof import("..").ScoreDirection>;
|
|
122
|
+
points: z.ZodNumber;
|
|
123
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
124
|
+
type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
|
|
125
|
+
rotations: z.ZodNumber;
|
|
126
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
127
|
+
type: z.ZodLiteral<import("..").PinchConditionType.FROM_SET>;
|
|
128
|
+
set: z.ZodNumber;
|
|
129
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
130
|
+
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
|
|
131
|
+
score: z.ZodNumber;
|
|
132
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
133
|
+
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
134
|
+
score: z.ZodNumber;
|
|
135
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
136
|
+
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
137
|
+
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|
|
138
|
+
}, z.core.$strip>], "type">>>;
|
|
139
|
+
conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
|
|
103
140
|
}, z.core.$strip>>>;
|
|
104
141
|
mode: z.ZodEnum<{
|
|
105
142
|
NEVER: "NEVER";
|
|
106
143
|
FATIGUE: "FATIGUE";
|
|
107
144
|
ALWAYS: "ALWAYS";
|
|
145
|
+
CONDITIONS: "CONDITIONS";
|
|
108
146
|
}>;
|
|
109
147
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
148
|
+
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
149
|
+
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
150
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
151
|
+
type: z.ZodLiteral<import("..").PinchConditionType.TEAM_SET_POINT>;
|
|
152
|
+
margin: z.ZodNumber;
|
|
153
|
+
opponent: z.ZodEnum<typeof import("..").OpponentRelation>;
|
|
154
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
155
|
+
type: z.ZodLiteral<import("..").PinchConditionType.OPPONENT_SET_POINT>;
|
|
156
|
+
margin: z.ZodNumber;
|
|
157
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
158
|
+
type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
|
|
159
|
+
direction: z.ZodEnum<typeof import("..").ScoreDirection>;
|
|
160
|
+
points: z.ZodNumber;
|
|
161
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
162
|
+
type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
|
|
163
|
+
rotations: z.ZodNumber;
|
|
164
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
165
|
+
type: z.ZodLiteral<import("..").PinchConditionType.FROM_SET>;
|
|
166
|
+
set: z.ZodNumber;
|
|
167
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
168
|
+
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
|
|
169
|
+
score: z.ZodNumber;
|
|
170
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
171
|
+
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
172
|
+
score: z.ZodNumber;
|
|
173
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
174
|
+
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
175
|
+
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|
|
176
|
+
}, z.core.$strip>], "type">>>;
|
|
177
|
+
conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
|
|
110
178
|
}, z.core.$strip>>;
|
|
111
179
|
liberoReplacementSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodCustom<Player, Player>>>>;
|
|
112
180
|
rotationSystem: z.ZodDefault<z.ZodEnum<typeof RotationSystemEnum>>;
|
|
@@ -129,5 +197,10 @@ export declare const TacticsInputSchema: z.ZodObject<{
|
|
|
129
197
|
replacement: z.ZodCustom<Player, Player>;
|
|
130
198
|
benchBackfill: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
131
199
|
}, z.core.$strip>>>;
|
|
200
|
+
liberoInjury: z.ZodOptional<z.ZodObject<{
|
|
201
|
+
preMatchReplacement: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
202
|
+
secondLiberoBackfill: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
203
|
+
inMatchDesignate: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
204
|
+
}, z.core.$strip>>;
|
|
132
205
|
}, z.core.$strip>;
|
|
133
206
|
export type TacticsInput = z.infer<typeof TacticsInputSchema>;
|
|
@@ -90,7 +90,17 @@ exports.TacticsInputSchema = zod_1.z.object({
|
|
|
90
90
|
starter: playerInstanceSchema,
|
|
91
91
|
replacement: playerInstanceSchema,
|
|
92
92
|
benchBackfill: playerInstanceSchema.optional()
|
|
93
|
-
})).default([])
|
|
93
|
+
})).default([]),
|
|
94
|
+
// Libero injuries (owner 2026-07-06). All optional => an unconfigured team behaves exactly as before.
|
|
95
|
+
// preMatchReplacement: the reserve or the L2 that takes an already-injured starting libero's zone at match
|
|
96
|
+
// load; secondLiberoBackfill: the reserve that becomes the new L2 (only when the L2 is the replacement);
|
|
97
|
+
// inMatchDesignate: a bench/reserve player re-designated as a full libero if the sole libero goes down
|
|
98
|
+
// in-match with no L2. Cross-field pool rules are in the superRefine below.
|
|
99
|
+
liberoInjury: zod_1.z.object({
|
|
100
|
+
preMatchReplacement: playerInstanceSchema.optional(),
|
|
101
|
+
secondLiberoBackfill: playerInstanceSchema.optional(),
|
|
102
|
+
inMatchDesignate: playerInstanceSchema.optional()
|
|
103
|
+
}).optional()
|
|
94
104
|
}).superRefine((data, ctx) => {
|
|
95
105
|
// Cross-field rules for the rotation system. These keep an invalid config from ever reaching the sim (the
|
|
96
106
|
// pinch-server outage was caused by an unvalidated tactics payload).
|
|
@@ -232,4 +242,34 @@ exports.TacticsInputSchema = zod_1.z.object({
|
|
|
232
242
|
seenIncoming.add(ir.benchBackfill.id);
|
|
233
243
|
}
|
|
234
244
|
}
|
|
245
|
+
// Libero injuries (owner 2026-07-06). preMatchReplacement is a RESERVE or the L2 (never a court starter, the
|
|
246
|
+
// libero, or a bench player) and needs a starting libero to replace. secondLiberoBackfill (the new L2) is a
|
|
247
|
+
// RESERVE, valid ONLY when the L2 itself is the replacement. inMatchDesignate is a bench or reserve player NOT
|
|
248
|
+
// on court (never a court starter, the libero, or the L2).
|
|
249
|
+
const liberoInjury = data.liberoInjury;
|
|
250
|
+
if (liberoInjury != null) {
|
|
251
|
+
const preMatch = liberoInjury.preMatchReplacement;
|
|
252
|
+
const backfill = liberoInjury.secondLiberoBackfill;
|
|
253
|
+
const designate = liberoInjury.inMatchDesignate;
|
|
254
|
+
const secondLiberoId = data.secondLibero?.id;
|
|
255
|
+
if (preMatch != null) {
|
|
256
|
+
if (libero == null) {
|
|
257
|
+
ctx.addIssue({ code: 'custom', message: 'LIBERO_INJURY_REQUIRES_LIBERO', path: ['liberoInjury', 'preMatchReplacement'] });
|
|
258
|
+
}
|
|
259
|
+
if (inLineupIds.has(preMatch.id)) {
|
|
260
|
+
ctx.addIssue({ code: 'custom', message: 'LIBERO_INJURY_REPLACEMENT_MUST_BE_RESERVE_OR_L2', path: ['liberoInjury', 'preMatchReplacement'] });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (backfill != null) {
|
|
264
|
+
if (preMatch == null || preMatch.id !== secondLiberoId) {
|
|
265
|
+
ctx.addIssue({ code: 'custom', message: 'LIBERO_BACKFILL_REQUIRES_L2_REPLACEMENT', path: ['liberoInjury', 'secondLiberoBackfill'] });
|
|
266
|
+
}
|
|
267
|
+
if (inLineupIds.has(backfill.id) || backfill.id === secondLiberoId) {
|
|
268
|
+
ctx.addIssue({ code: 'custom', message: 'LIBERO_BACKFILL_MUST_BE_RESERVE', path: ['liberoInjury', 'secondLiberoBackfill'] });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (designate != null && (starterIds.has(designate.id) || designate.id === liberoId || designate.id === secondLiberoId)) {
|
|
272
|
+
ctx.addIssue({ code: 'custom', message: 'LIBERO_DESIGNATE_MUST_BE_BENCH_OR_RESERVE', path: ['liberoInjury', 'inMatchDesignate'] });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
235
275
|
});
|
|
@@ -58,6 +58,9 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
58
58
|
}, z.core.$strip>, z.ZodObject<{
|
|
59
59
|
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
60
60
|
score: z.ZodNumber;
|
|
61
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
62
|
+
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
63
|
+
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|
|
61
64
|
}, z.core.$strip>], "type">>>;
|
|
62
65
|
conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
|
|
63
66
|
}, z.core.$strip>>>;
|
|
@@ -94,6 +97,9 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
94
97
|
}, z.core.$strip>, z.ZodObject<{
|
|
95
98
|
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
96
99
|
score: z.ZodNumber;
|
|
100
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
101
|
+
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
102
|
+
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|
|
97
103
|
}, z.core.$strip>], "type">>>;
|
|
98
104
|
conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
|
|
99
105
|
starter: z.ZodCustom<Player, Player>;
|
|
@@ -106,15 +112,77 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
106
112
|
NEVER: "NEVER";
|
|
107
113
|
FATIGUE: "FATIGUE";
|
|
108
114
|
ALWAYS: "ALWAYS";
|
|
115
|
+
CONDITIONS: "CONDITIONS";
|
|
109
116
|
}>;
|
|
110
117
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
118
|
+
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
119
|
+
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
120
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
121
|
+
type: z.ZodLiteral<import("..").PinchConditionType.TEAM_SET_POINT>;
|
|
122
|
+
margin: z.ZodNumber;
|
|
123
|
+
opponent: z.ZodEnum<typeof import("..").OpponentRelation>;
|
|
124
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
125
|
+
type: z.ZodLiteral<import("..").PinchConditionType.OPPONENT_SET_POINT>;
|
|
126
|
+
margin: z.ZodNumber;
|
|
127
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
128
|
+
type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
|
|
129
|
+
direction: z.ZodEnum<typeof import("..").ScoreDirection>;
|
|
130
|
+
points: z.ZodNumber;
|
|
131
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
132
|
+
type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
|
|
133
|
+
rotations: z.ZodNumber;
|
|
134
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
135
|
+
type: z.ZodLiteral<import("..").PinchConditionType.FROM_SET>;
|
|
136
|
+
set: z.ZodNumber;
|
|
137
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
138
|
+
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
|
|
139
|
+
score: z.ZodNumber;
|
|
140
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
141
|
+
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
142
|
+
score: z.ZodNumber;
|
|
143
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
144
|
+
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
145
|
+
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|
|
146
|
+
}, z.core.$strip>], "type">>>;
|
|
147
|
+
conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
|
|
111
148
|
}, z.core.$strip>>>;
|
|
112
149
|
mode: z.ZodEnum<{
|
|
113
150
|
NEVER: "NEVER";
|
|
114
151
|
FATIGUE: "FATIGUE";
|
|
115
152
|
ALWAYS: "ALWAYS";
|
|
153
|
+
CONDITIONS: "CONDITIONS";
|
|
116
154
|
}>;
|
|
117
155
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
156
|
+
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
157
|
+
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
158
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
159
|
+
type: z.ZodLiteral<import("..").PinchConditionType.TEAM_SET_POINT>;
|
|
160
|
+
margin: z.ZodNumber;
|
|
161
|
+
opponent: z.ZodEnum<typeof import("..").OpponentRelation>;
|
|
162
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
163
|
+
type: z.ZodLiteral<import("..").PinchConditionType.OPPONENT_SET_POINT>;
|
|
164
|
+
margin: z.ZodNumber;
|
|
165
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
166
|
+
type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
|
|
167
|
+
direction: z.ZodEnum<typeof import("..").ScoreDirection>;
|
|
168
|
+
points: z.ZodNumber;
|
|
169
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
170
|
+
type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
|
|
171
|
+
rotations: z.ZodNumber;
|
|
172
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
173
|
+
type: z.ZodLiteral<import("..").PinchConditionType.FROM_SET>;
|
|
174
|
+
set: z.ZodNumber;
|
|
175
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
176
|
+
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
|
|
177
|
+
score: z.ZodNumber;
|
|
178
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
179
|
+
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
180
|
+
score: z.ZodNumber;
|
|
181
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
182
|
+
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
183
|
+
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|
|
184
|
+
}, z.core.$strip>], "type">>>;
|
|
185
|
+
conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
|
|
118
186
|
}, z.core.$strip>>;
|
|
119
187
|
liberoReplacementSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodCustom<Player, Player>>>>;
|
|
120
188
|
rotationSystem: z.ZodDefault<z.ZodEnum<typeof import("..").RotationSystemEnum>>;
|
|
@@ -137,6 +205,11 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
137
205
|
replacement: z.ZodCustom<Player, Player>;
|
|
138
206
|
benchBackfill: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
139
207
|
}, z.core.$strip>>>;
|
|
208
|
+
liberoInjury: z.ZodOptional<z.ZodObject<{
|
|
209
|
+
preMatchReplacement: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
210
|
+
secondLiberoBackfill: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
211
|
+
inMatchDesignate: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
212
|
+
}, z.core.$strip>>;
|
|
140
213
|
}, z.core.$strip>>;
|
|
141
214
|
}, z.core.$strip>;
|
|
142
215
|
export type TeamInput = z.infer<typeof TeamInputSchema>;
|
|
@@ -6,6 +6,7 @@ import { SubBand } from './energy-band';
|
|
|
6
6
|
import { RotationSystemEnum } from './rotation-system';
|
|
7
7
|
import { OffensivePreference } from './offensive-preference';
|
|
8
8
|
import { InjuryReplacement } from './injury-replacement';
|
|
9
|
+
import { LiberoInjuryConfig } from './libero-injury';
|
|
9
10
|
export interface StartingLineup {
|
|
10
11
|
readonly [CourtPosition.LEFT_FRONT]: Player;
|
|
11
12
|
readonly [CourtPosition.MIDDLE_FRONT]: Player;
|
|
@@ -36,6 +37,7 @@ export interface TacticsOpts {
|
|
|
36
37
|
readonly offensivePreferenceSets?: OffensivePreference[][];
|
|
37
38
|
readonly replaceKnockedImmediately: Record<string, boolean>;
|
|
38
39
|
readonly injuryReplacements: InjuryReplacement[];
|
|
40
|
+
readonly liberoInjury?: LiberoInjuryConfig;
|
|
39
41
|
}
|
|
40
42
|
export declare class Tactics {
|
|
41
43
|
readonly lineup: StartingLineup;
|
|
@@ -53,6 +55,7 @@ export declare class Tactics {
|
|
|
53
55
|
readonly offensivePreferenceSets?: OffensivePreference[][];
|
|
54
56
|
readonly replaceKnockedImmediately: Record<string, boolean>;
|
|
55
57
|
readonly injuryReplacements: InjuryReplacement[];
|
|
58
|
+
readonly liberoInjury?: LiberoInjuryConfig;
|
|
56
59
|
static create(input: unknown): Tactics;
|
|
57
60
|
private constructor();
|
|
58
61
|
findPlayerInLineup(id: string): CourtPosition | undefined;
|
|
@@ -18,7 +18,7 @@ class Tactics {
|
|
|
18
18
|
}
|
|
19
19
|
return new Tactics(result.data);
|
|
20
20
|
}
|
|
21
|
-
constructor({ lineup, liberoReplacements, receiveRotationOffset = false, designatedSubs = [], substitutionBand = energy_band_1.EnergyBand.TIRED, secondLibero, liberoSub, liberoReplacementSets, rotationSystem = rotation_system_1.RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [], systemSets, offensivePreferenceSets, replaceKnockedImmediately = {}, injuryReplacements = [] }) {
|
|
21
|
+
constructor({ lineup, liberoReplacements, receiveRotationOffset = false, designatedSubs = [], substitutionBand = energy_band_1.EnergyBand.TIRED, secondLibero, liberoSub, liberoReplacementSets, rotationSystem = rotation_system_1.RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [], systemSets, offensivePreferenceSets, replaceKnockedImmediately = {}, injuryReplacements = [], liberoInjury }) {
|
|
22
22
|
this.lineup = lineup;
|
|
23
23
|
this.liberoReplacements = liberoReplacements;
|
|
24
24
|
this.receiveRotationOffset = receiveRotationOffset;
|
|
@@ -34,6 +34,7 @@ class Tactics {
|
|
|
34
34
|
this.offensivePreferenceSets = offensivePreferenceSets;
|
|
35
35
|
this.replaceKnockedImmediately = replaceKnockedImmediately;
|
|
36
36
|
this.injuryReplacements = injuryReplacements;
|
|
37
|
+
this.liberoInjury = liberoInjury;
|
|
37
38
|
}
|
|
38
39
|
findPlayerInLineup(id) {
|
|
39
40
|
if (this.lineup.bench.some(p => p.id === id))
|
|
@@ -111,9 +111,16 @@ export interface ApiInjuryReplacement {
|
|
|
111
111
|
replacementId: string;
|
|
112
112
|
benchBackfillId?: string;
|
|
113
113
|
}
|
|
114
|
+
export interface ApiLiberoInjury {
|
|
115
|
+
preMatchReplacementId?: string;
|
|
116
|
+
secondLiberoBackfillId?: string;
|
|
117
|
+
inMatchDesignateId?: string;
|
|
118
|
+
}
|
|
114
119
|
export interface ApiLiberoSetSubConfig {
|
|
115
120
|
mode: LiberoSubMode;
|
|
116
121
|
fatigueBand?: EnergyBand;
|
|
122
|
+
conditions?: PinchCondition[];
|
|
123
|
+
conditionLogic?: ConditionLogic;
|
|
117
124
|
}
|
|
118
125
|
export interface ApiLiberoSubConfig extends ApiLiberoSetSubConfig {
|
|
119
126
|
sets?: ApiLiberoSetSubConfig[];
|
|
@@ -134,6 +141,7 @@ export interface Tactics {
|
|
|
134
141
|
offensivePreferenceSets?: ApiOffensivePreference[][];
|
|
135
142
|
replaceKnockedImmediately?: Record<string, boolean>;
|
|
136
143
|
injuryReplacements?: ApiInjuryReplacement[];
|
|
144
|
+
liberoInjury?: ApiLiberoInjury;
|
|
137
145
|
}
|
|
138
146
|
export interface ApiLineupPreset {
|
|
139
147
|
presetId: string;
|
|
@@ -44,9 +44,16 @@ export interface InjuryReplacementAttributes {
|
|
|
44
44
|
replacementId: PlayerId;
|
|
45
45
|
benchBackfillId?: PlayerId;
|
|
46
46
|
}
|
|
47
|
+
export interface LiberoInjuryAttributes {
|
|
48
|
+
preMatchReplacementId?: PlayerId;
|
|
49
|
+
secondLiberoBackfillId?: PlayerId;
|
|
50
|
+
inMatchDesignateId?: PlayerId;
|
|
51
|
+
}
|
|
47
52
|
export interface LiberoSetSubConfigAttributes {
|
|
48
53
|
mode: LiberoSubMode;
|
|
49
54
|
fatigueBand?: EnergyBand;
|
|
55
|
+
conditions?: PinchCondition[];
|
|
56
|
+
conditionLogic?: ConditionLogic;
|
|
50
57
|
}
|
|
51
58
|
export interface LiberoSubConfigAttributes extends LiberoSetSubConfigAttributes {
|
|
52
59
|
sets?: LiberoSetSubConfigAttributes[];
|
|
@@ -68,10 +75,11 @@ export interface TacticsAttributes {
|
|
|
68
75
|
offensive_preference_sets?: OffensivePreferenceAttributes[][];
|
|
69
76
|
replace_knocked_immediately?: Record<PlayerId, boolean>;
|
|
70
77
|
injury_replacements?: InjuryReplacementAttributes[];
|
|
78
|
+
libero_injury?: LiberoInjuryAttributes;
|
|
71
79
|
}
|
|
72
80
|
export type TacticsPk = 'team_id';
|
|
73
81
|
export type TacticsId = TacticsModel[TacticsPk];
|
|
74
|
-
export type TacticsOptionalAttributes = 'lineup' | 'libero_replacements' | 'receive_rotation_offset' | 'designated_subs' | 'substitution_band' | 'second_libero' | 'libero_sub' | 'libero_replacement_sets' | 'rotation_system' | 'designated_setters' | 'offensive_preferences' | 'system_sets' | 'offensive_preference_sets' | 'replace_knocked_immediately' | 'injury_replacements';
|
|
82
|
+
export type TacticsOptionalAttributes = 'lineup' | 'libero_replacements' | 'receive_rotation_offset' | 'designated_subs' | 'substitution_band' | 'second_libero' | 'libero_sub' | 'libero_replacement_sets' | 'rotation_system' | 'designated_setters' | 'offensive_preferences' | 'system_sets' | 'offensive_preference_sets' | 'replace_knocked_immediately' | 'injury_replacements' | 'libero_injury';
|
|
75
83
|
export type TacticsCreationAttributes = Optional<TacticsAttributes, TacticsOptionalAttributes>;
|
|
76
84
|
export declare class TacticsModel extends Model<TacticsAttributes, TacticsCreationAttributes> implements TacticsAttributes {
|
|
77
85
|
team_id: string;
|
|
@@ -90,6 +98,7 @@ export declare class TacticsModel extends Model<TacticsAttributes, TacticsCreati
|
|
|
90
98
|
offensive_preference_sets?: OffensivePreferenceAttributes[][];
|
|
91
99
|
replace_knocked_immediately?: Record<PlayerId, boolean>;
|
|
92
100
|
injury_replacements?: InjuryReplacementAttributes[];
|
|
101
|
+
libero_injury?: LiberoInjuryAttributes;
|
|
93
102
|
team: TeamModel;
|
|
94
103
|
getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
|
|
95
104
|
setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
|
|
@@ -87,7 +87,14 @@ function transformToAttributes(tactics, teamId) {
|
|
|
87
87
|
starterId: ir.starter.id,
|
|
88
88
|
replacementId: ir.replacement.id,
|
|
89
89
|
benchBackfillId: ir.benchBackfill?.id
|
|
90
|
-
}))
|
|
90
|
+
})),
|
|
91
|
+
libero_injury: tactics.liberoInjury != null
|
|
92
|
+
? {
|
|
93
|
+
preMatchReplacementId: tactics.liberoInjury.preMatchReplacement?.id,
|
|
94
|
+
secondLiberoBackfillId: tactics.liberoInjury.secondLiberoBackfill?.id,
|
|
95
|
+
inMatchDesignateId: tactics.liberoInjury.inMatchDesignate?.id
|
|
96
|
+
}
|
|
97
|
+
: undefined
|
|
91
98
|
};
|
|
92
99
|
}
|
|
93
100
|
// Accepts TacticsAttributes (not just TacticsModel) so a per-match preset's config, shaped as column data by
|
|
@@ -215,6 +222,38 @@ function transformToObject(model, roster) {
|
|
|
215
222
|
healed.push({ starter, replacement, benchBackfill });
|
|
216
223
|
}
|
|
217
224
|
return healed;
|
|
225
|
+
})(),
|
|
226
|
+
// LENIENT (roster.find): drop any stale/invalid libero-injury id so a bad row can never crash a team load.
|
|
227
|
+
// Mirrors the schema pool rules: preMatchReplacement is a RESERVE or the L2 (not in the lineup) and needs a
|
|
228
|
+
// starting libero; secondLiberoBackfill is a RESERVE (not the current L2) kept only when the L2 is the
|
|
229
|
+
// replacement; inMatchDesignate is a bench/reserve player (not a court starter, the libero, or the L2).
|
|
230
|
+
liberoInjury: (() => {
|
|
231
|
+
const li = model.libero_injury;
|
|
232
|
+
if (li == null)
|
|
233
|
+
return undefined;
|
|
234
|
+
const liberoZoneId = lineup[CourtPosition.LIBERO_ZONE]?.id;
|
|
235
|
+
const injuryStarterIds = new Set([lineup[CourtPosition.LEFT_FRONT], lineup[CourtPosition.MIDDLE_FRONT], lineup[CourtPosition.RIGHT_FRONT],
|
|
236
|
+
lineup[CourtPosition.LEFT_BACK], lineup[CourtPosition.MIDDLE_BACK], lineup[CourtPosition.RIGHT_BACK]]
|
|
237
|
+
.map((p) => p.id));
|
|
238
|
+
const reserveOrL2 = (id) => {
|
|
239
|
+
if (id == null)
|
|
240
|
+
return undefined;
|
|
241
|
+
const p = roster.find((x) => x.id === id);
|
|
242
|
+
return p != null && !inLineupIds.has(p.id) ? p : undefined;
|
|
243
|
+
};
|
|
244
|
+
const preMatchReplacement = liberoZoneId != null ? reserveOrL2(li.preMatchReplacementId) : undefined;
|
|
245
|
+
const secondLiberoBackfill = (preMatchReplacement != null && preMatchReplacement.id === secondLibero?.id)
|
|
246
|
+
? (() => { const p = reserveOrL2(li.secondLiberoBackfillId); return p != null && p.id !== secondLibero?.id ? p : undefined; })()
|
|
247
|
+
: undefined;
|
|
248
|
+
const inMatchDesignate = (() => {
|
|
249
|
+
const p = li.inMatchDesignateId != null ? roster.find((x) => x.id === li.inMatchDesignateId) : undefined;
|
|
250
|
+
if (p == null || injuryStarterIds.has(p.id) || p.id === liberoZoneId || p.id === secondLibero?.id)
|
|
251
|
+
return undefined;
|
|
252
|
+
return p;
|
|
253
|
+
})();
|
|
254
|
+
if (preMatchReplacement == null && secondLiberoBackfill == null && inMatchDesignate == null)
|
|
255
|
+
return undefined;
|
|
256
|
+
return { preMatchReplacement, secondLiberoBackfill, inMatchDesignate };
|
|
218
257
|
})()
|
|
219
258
|
});
|
|
220
259
|
}
|
|
@@ -319,6 +358,30 @@ function tacticsColumnsToApiDto(model) {
|
|
|
319
358
|
healed.push({ starterId: ir.starterId, replacementId: ir.replacementId, benchBackfillId });
|
|
320
359
|
}
|
|
321
360
|
return healed.length > 0 ? healed : undefined;
|
|
361
|
+
})(),
|
|
362
|
+
// Same stale-data policy for the libero-injury ids (ids only; roster membership is checked in the service
|
|
363
|
+
// heal). preMatchReplacement = reserve or the L2 (not in the lineup) + needs a starting libero;
|
|
364
|
+
// secondLiberoBackfill = a reserve (not the current L2) kept only when the L2 is the replacement;
|
|
365
|
+
// inMatchDesignate = a bench/reserve player (not a court starter, the libero, or the L2).
|
|
366
|
+
liberoInjury: (() => {
|
|
367
|
+
const li = model.libero_injury;
|
|
368
|
+
if (li == null)
|
|
369
|
+
return undefined;
|
|
370
|
+
const liberoZoneId = model.lineup[CourtPosition.LIBERO_ZONE];
|
|
371
|
+
const starterSlotIds = new Set([model.lineup[CourtPosition.LEFT_FRONT], model.lineup[CourtPosition.MIDDLE_FRONT],
|
|
372
|
+
model.lineup[CourtPosition.RIGHT_FRONT], model.lineup[CourtPosition.LEFT_BACK],
|
|
373
|
+
model.lineup[CourtPosition.MIDDLE_BACK], model.lineup[CourtPosition.RIGHT_BACK]]);
|
|
374
|
+
const reserveOrL2 = (id) => (id != null && !lineupIds.has(id)) ? id : undefined;
|
|
375
|
+
const preMatchReplacementId = liberoZoneId != null ? reserveOrL2(li.preMatchReplacementId) : undefined;
|
|
376
|
+
const secondLiberoBackfillId = (preMatchReplacementId != null && preMatchReplacementId === validSecondLibero)
|
|
377
|
+
? (() => { const id = reserveOrL2(li.secondLiberoBackfillId); return id != null && id !== validSecondLibero ? id : undefined; })()
|
|
378
|
+
: undefined;
|
|
379
|
+
const designateEligible = li.inMatchDesignateId != null && !starterSlotIds.has(li.inMatchDesignateId) &&
|
|
380
|
+
li.inMatchDesignateId !== liberoZoneId && li.inMatchDesignateId !== validSecondLibero;
|
|
381
|
+
const inMatchDesignateId = designateEligible ? li.inMatchDesignateId : undefined;
|
|
382
|
+
if (preMatchReplacementId == null && secondLiberoBackfillId == null && inMatchDesignateId == null)
|
|
383
|
+
return undefined;
|
|
384
|
+
return { preMatchReplacementId, secondLiberoBackfillId, inMatchDesignateId };
|
|
322
385
|
})()
|
|
323
386
|
};
|
|
324
387
|
}
|
|
@@ -354,7 +417,8 @@ function apiTacticsConfigToAttributes(config, teamId) {
|
|
|
354
417
|
system_sets: config.systemSets,
|
|
355
418
|
offensive_preference_sets: config.offensivePreferenceSets,
|
|
356
419
|
replace_knocked_immediately: config.replaceKnockedImmediately,
|
|
357
|
-
injury_replacements: config.injuryReplacements
|
|
420
|
+
injury_replacements: config.injuryReplacements,
|
|
421
|
+
libero_injury: config.liberoInjury
|
|
358
422
|
};
|
|
359
423
|
}
|
|
360
424
|
export { transformToObject as transformToTactics, transformToAttributes as transformFromTactics, tacticsColumnsToApiDto, apiTacticsConfigToAttributes as apiTacticsConfigToTacticsAttributes };
|
|
@@ -137,3 +137,47 @@ describe('per-set tactics — service <-> row round-trip', () => {
|
|
|
137
137
|
expect(back.offensivePreferenceSets).toBeUndefined();
|
|
138
138
|
});
|
|
139
139
|
});
|
|
140
|
+
describe('libero injuries — service <-> row round-trip + lenient heal', () => {
|
|
141
|
+
const country = makeCountry();
|
|
142
|
+
const p1 = makePlayer(country);
|
|
143
|
+
const p2 = makePlayer(country);
|
|
144
|
+
const p3 = makePlayer(country);
|
|
145
|
+
const p4 = makePlayer(country);
|
|
146
|
+
const p5 = makePlayer(country);
|
|
147
|
+
const p6 = makePlayer(country);
|
|
148
|
+
const libero = makePlayer(country);
|
|
149
|
+
const l2 = makePlayer(country);
|
|
150
|
+
const reserveA = makePlayer(country);
|
|
151
|
+
const reserveB = makePlayer(country);
|
|
152
|
+
const roster = [p1, p2, p3, p4, p5, p6, libero, l2, reserveA, reserveB];
|
|
153
|
+
const tactics = Tactics.create({
|
|
154
|
+
lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, 0: libero, bench: [] },
|
|
155
|
+
liberoReplacements: [],
|
|
156
|
+
secondLibero: l2,
|
|
157
|
+
liberoInjury: { preMatchReplacement: l2, secondLiberoBackfill: reserveA, inMatchDesignate: reserveB }
|
|
158
|
+
});
|
|
159
|
+
it('writes libero_injury as id-based jsonb', () => {
|
|
160
|
+
const attrs = transformFromTactics(tactics, 'team-1');
|
|
161
|
+
expect(attrs.libero_injury).toEqual({
|
|
162
|
+
preMatchReplacementId: l2.id,
|
|
163
|
+
secondLiberoBackfillId: reserveA.id,
|
|
164
|
+
inMatchDesignateId: reserveB.id
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
it('resolves the stored libero_injury ids back to roster players', () => {
|
|
168
|
+
const attrs = transformFromTactics(tactics, 'team-1');
|
|
169
|
+
const back = transformToTactics(attrs, roster);
|
|
170
|
+
expect(back.liberoInjury?.preMatchReplacement?.id).toBe(l2.id);
|
|
171
|
+
expect(back.liberoInjury?.secondLiberoBackfill?.id).toBe(reserveA.id);
|
|
172
|
+
expect(back.liberoInjury?.inMatchDesignate?.id).toBe(reserveB.id);
|
|
173
|
+
});
|
|
174
|
+
it('drops a stale pre-match replacement that is now a court starter (lenient heal, no crash)', () => {
|
|
175
|
+
const attrs = transformFromTactics(tactics, 'team-1');
|
|
176
|
+
attrs.libero_injury.preMatchReplacementId = p2.id;
|
|
177
|
+
attrs.libero_injury.secondLiberoBackfillId = undefined;
|
|
178
|
+
const back = transformToTactics(attrs, roster);
|
|
179
|
+
expect(back.liberoInjury?.preMatchReplacement).toBeUndefined();
|
|
180
|
+
expect(back.liberoInjury?.secondLiberoBackfill).toBeUndefined();
|
|
181
|
+
expect(back.liberoInjury?.inMatchDesignate?.id).toBe(reserveB.id);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { EnergyBand } from './energy-band';
|
|
2
|
-
|
|
2
|
+
import { ConditionLogic, PinchCondition } from './pinch-condition';
|
|
3
|
+
export type LiberoSubMode = 'NEVER' | 'FATIGUE' | 'ALWAYS' | 'CONDITIONS';
|
|
3
4
|
export interface LiberoSetSubConfig {
|
|
4
5
|
readonly mode: LiberoSubMode;
|
|
5
6
|
readonly fatigueBand?: EnergyBand;
|
|
7
|
+
readonly conditions?: PinchCondition[];
|
|
8
|
+
readonly conditionLogic?: ConditionLogic;
|
|
6
9
|
}
|
|
7
10
|
export interface LiberoSubConfig extends LiberoSetSubConfig {
|
|
8
11
|
readonly sets?: LiberoSetSubConfig[];
|