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.
- package/dist/cjs/src/api/index.d.ts +10 -18
- package/dist/cjs/src/data/models/tactics.d.ts +17 -19
- package/dist/cjs/src/data/models/tactics.js +9 -0
- package/dist/cjs/src/data/transformers/match-preset-override.test.js +10 -1
- package/dist/cjs/src/data/transformers/rally-event.d.ts +2 -1
- package/dist/cjs/src/data/transformers/rally-event.js +12 -0
- package/dist/cjs/src/data/transformers/rally.js +2 -0
- package/dist/cjs/src/data/transformers/rally.test.d.ts +1 -0
- package/dist/cjs/src/data/transformers/rally.test.js +62 -0
- package/dist/cjs/src/data/transformers/tactics.js +38 -58
- package/dist/cjs/src/data/transformers/tactics.test.js +39 -20
- package/dist/cjs/src/service/team/blocking-preference.d.ts +5 -0
- package/dist/cjs/src/service/team/blocking-preference.js +2 -0
- package/dist/cjs/src/service/team/designated-sub.d.ts +2 -4
- package/dist/cjs/src/service/team/index.d.ts +2 -0
- package/dist/cjs/src/service/team/index.js +2 -0
- package/dist/cjs/src/service/team/libero-sub.d.ts +0 -4
- package/dist/cjs/src/service/team/offensive-preference.d.ts +1 -2
- package/dist/cjs/src/service/team/pinch-condition.d.ts +11 -3
- package/dist/cjs/src/service/team/pinch-condition.js +9 -4
- package/dist/cjs/src/service/team/schemas/designated-sub.z.d.ts +30 -32
- package/dist/cjs/src/service/team/schemas/designated-sub.z.js +27 -23
- package/dist/cjs/src/service/team/schemas/designated-sub.z.test.js +4 -2
- package/dist/cjs/src/service/team/schemas/libero-sub.z.d.ts +15 -34
- package/dist/cjs/src/service/team/schemas/libero-sub.z.js +10 -26
- package/dist/cjs/src/service/team/schemas/libero-sub.z.test.js +74 -35
- package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +39 -39
- package/dist/cjs/src/service/team/schemas/tactics.z.js +47 -13
- package/dist/cjs/src/service/team/schemas/tactics.z.test.js +81 -8
- package/dist/cjs/src/service/team/schemas/team.z.d.ts +38 -38
- package/dist/cjs/src/service/team/sub-config-convert.d.ts +13 -0
- package/dist/cjs/src/service/team/sub-config-convert.js +91 -0
- package/dist/cjs/src/service/team/sub-config-convert.test.d.ts +1 -0
- package/dist/cjs/src/service/team/sub-config-convert.test.js +84 -0
- package/dist/cjs/src/service/team/tactics.d.ts +5 -0
- package/dist/cjs/src/service/team/tactics.js +3 -1
- package/dist/esm/src/api/index.d.ts +10 -18
- package/dist/esm/src/data/models/tactics.d.ts +17 -19
- package/dist/esm/src/data/models/tactics.js +9 -0
- package/dist/esm/src/data/transformers/match-preset-override.test.js +10 -1
- package/dist/esm/src/data/transformers/rally-event.d.ts +2 -1
- package/dist/esm/src/data/transformers/rally-event.js +12 -1
- package/dist/esm/src/data/transformers/rally.js +4 -2
- package/dist/esm/src/data/transformers/rally.test.d.ts +1 -0
- package/dist/esm/src/data/transformers/rally.test.js +60 -0
- package/dist/esm/src/data/transformers/tactics.js +39 -59
- package/dist/esm/src/data/transformers/tactics.test.js +40 -21
- package/dist/esm/src/service/team/blocking-preference.d.ts +5 -0
- package/dist/esm/src/service/team/blocking-preference.js +1 -0
- package/dist/esm/src/service/team/designated-sub.d.ts +2 -4
- package/dist/esm/src/service/team/index.d.ts +2 -0
- package/dist/esm/src/service/team/index.js +2 -0
- package/dist/esm/src/service/team/libero-sub.d.ts +0 -4
- package/dist/esm/src/service/team/offensive-preference.d.ts +1 -2
- package/dist/esm/src/service/team/pinch-condition.d.ts +11 -3
- package/dist/esm/src/service/team/pinch-condition.js +9 -4
- package/dist/esm/src/service/team/schemas/designated-sub.z.d.ts +30 -32
- package/dist/esm/src/service/team/schemas/designated-sub.z.js +26 -22
- package/dist/esm/src/service/team/schemas/designated-sub.z.test.js +4 -2
- package/dist/esm/src/service/team/schemas/libero-sub.z.d.ts +15 -34
- package/dist/esm/src/service/team/schemas/libero-sub.z.js +9 -25
- package/dist/esm/src/service/team/schemas/libero-sub.z.test.js +75 -36
- package/dist/esm/src/service/team/schemas/tactics.z.d.ts +39 -39
- package/dist/esm/src/service/team/schemas/tactics.z.js +48 -14
- package/dist/esm/src/service/team/schemas/tactics.z.test.js +81 -8
- package/dist/esm/src/service/team/schemas/team.z.d.ts +38 -38
- package/dist/esm/src/service/team/sub-config-convert.d.ts +13 -0
- package/dist/esm/src/service/team/sub-config-convert.js +85 -0
- package/dist/esm/src/service/team/sub-config-convert.test.d.ts +1 -0
- package/dist/esm/src/service/team/sub-config-convert.test.js +82 -0
- package/dist/esm/src/service/team/tactics.d.ts +5 -0
- package/dist/esm/src/service/team/tactics.js +3 -1
- package/package.json +1 -1
|
@@ -1,37 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LiberoSubConfigSchema = exports.LiberoSetSubConfigSchema =
|
|
3
|
+
exports.LiberoSubConfigSchema = exports.LiberoSetSubConfigSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
-
const
|
|
5
|
+
const sub_config_convert_1 = require("../sub-config-convert");
|
|
6
6
|
const designated_sub_z_1 = require("./designated-sub.z");
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
7
|
+
// Pure condition engine (owner 2026-07-11): the second libero is active while `conditions` hold (combined
|
|
8
|
+
// per `conditionLogic`, default ALL; the STARTING libero is the FATIGUE-condition subject). No conditions =
|
|
9
|
+
// the starting libero always plays; [{ASAP}] = always the second libero (the old ALWAYS). The swapped-in
|
|
10
|
+
// player is always the dedicated `secondLibero`, so there is no bench to validate. Legacy mode-shaped
|
|
11
|
+
// configs (NEVER/FATIGUE/ALWAYS/CONDITIONS) are auto-converted by the preprocess.
|
|
11
12
|
const liberoSetSubConfigShape = {
|
|
12
|
-
mode: exports.LiberoSubModeSchema,
|
|
13
|
-
// fatigue mode only (absent => the team default `substitutionBand`):
|
|
14
|
-
fatigueBand: zod_1.z.nativeEnum(energy_band_1.EnergyBand).optional(),
|
|
15
|
-
// conditions mode only:
|
|
16
13
|
conditions: zod_1.z.array(designated_sub_z_1.PinchConditionSchema).optional(),
|
|
17
14
|
conditionLogic: designated_sub_z_1.ConditionLogicSchema.optional()
|
|
18
15
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (data.mode !== 'CONDITIONS')
|
|
22
|
-
return;
|
|
23
|
-
if (data.conditions == null || data.conditions.length === 0) {
|
|
24
|
-
ctx.addIssue({ code: 'custom', message: 'LIBERO_CONDITIONS_REQUIRE_A_CONDITION', path: ['conditions'] });
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
exports.LiberoSetSubConfigSchema = zod_1.z.object(liberoSetSubConfigShape).superRefine(refineConditions);
|
|
28
|
-
exports.LiberoSubConfigSchema = zod_1.z.object({
|
|
16
|
+
exports.LiberoSetSubConfigSchema = zod_1.z.preprocess(sub_config_convert_1.convertLegacyLiberoSetSubConfig, zod_1.z.object(liberoSetSubConfigShape));
|
|
17
|
+
exports.LiberoSubConfigSchema = zod_1.z.preprocess(sub_config_convert_1.convertLegacyLiberoSubConfig, zod_1.z.object({
|
|
29
18
|
...liberoSetSubConfigShape,
|
|
30
19
|
// Optional per-set overrides (index 0 = set 1 ... index 4 = set 5); absent => the flat config for all sets.
|
|
31
20
|
sets: zod_1.z.array(exports.LiberoSetSubConfigSchema).max(5).optional()
|
|
32
|
-
})
|
|
33
|
-
// The flat config is the fallback for any set a partial `sets` array doesn't cover, so validate it whenever
|
|
34
|
-
// `sets` is absent or covers fewer than 5 sets (each present per-set config is validated by its own schema).
|
|
35
|
-
if (data.sets == null || data.sets.length < 5)
|
|
36
|
-
refineConditions(data, ctx);
|
|
37
|
-
});
|
|
21
|
+
}));
|
|
@@ -4,85 +4,124 @@ 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
6
|
const pinch_condition_1 = require("../pinch-condition");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
// Unified condition engine (owner 2026-07-11): the libero rule is a pure condition list; legacy mode-shaped
|
|
8
|
+
// configs (NEVER/FATIGUE/ALWAYS/CONDITIONS) auto-convert in the schema preprocess.
|
|
9
|
+
(0, globals_1.describe)('LiberoSetSubConfigSchema (unified)', () => {
|
|
10
|
+
(0, globals_1.it)('accepts an empty config (no conditions = the starting libero always plays)', () => {
|
|
11
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({});
|
|
12
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
13
|
+
if (res.success)
|
|
14
|
+
(0, globals_1.expect)(res.data.conditions ?? []).toHaveLength(0);
|
|
15
15
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
(0, globals_1.it)('accepts a FATIGUE condition with a band', () => {
|
|
17
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({
|
|
18
|
+
conditions: [{ type: pinch_condition_1.PinchConditionType.FATIGUE, band: energy_band_1.EnergyBand.TIRED }]
|
|
19
|
+
});
|
|
20
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
20
21
|
});
|
|
21
|
-
(0, globals_1.it)('accepts a
|
|
22
|
-
(0, globals_1.expect)(libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({
|
|
22
|
+
(0, globals_1.it)('accepts a FATIGUE condition without a band (team default)', () => {
|
|
23
|
+
(0, globals_1.expect)(libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ conditions: [{ type: pinch_condition_1.PinchConditionType.FATIGUE }] }).success).toBe(true);
|
|
23
24
|
});
|
|
24
|
-
(0, globals_1.it)('rejects an unknown band', () => {
|
|
25
|
-
(0, globals_1.expect)(libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({
|
|
25
|
+
(0, globals_1.it)('rejects a FATIGUE condition with an unknown band', () => {
|
|
26
|
+
(0, globals_1.expect)(libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ conditions: [{ type: pinch_condition_1.PinchConditionType.FATIGUE, band: 'SLEEPY' }] }).success).toBe(false);
|
|
26
27
|
});
|
|
27
|
-
(0, globals_1.it)('accepts
|
|
28
|
+
(0, globals_1.it)('accepts a PHASE condition (serve/receive split)', () => {
|
|
28
29
|
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({
|
|
29
|
-
mode: 'CONDITIONS',
|
|
30
30
|
conditionLogic: 'ALL',
|
|
31
31
|
conditions: [{ type: pinch_condition_1.PinchConditionType.PHASE, phase: pinch_condition_1.MatchPhase.RECEIVING }]
|
|
32
32
|
});
|
|
33
33
|
(0, globals_1.expect)(res.success).toBe(true);
|
|
34
34
|
});
|
|
35
|
-
(0, globals_1.it)('accepts
|
|
35
|
+
(0, globals_1.it)('accepts mixed conditions (score + phase + fatigue) under ANY logic', () => {
|
|
36
36
|
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({
|
|
37
|
-
mode: 'CONDITIONS',
|
|
38
37
|
conditionLogic: 'ANY',
|
|
39
38
|
conditions: [
|
|
40
39
|
{ type: pinch_condition_1.PinchConditionType.MIN_OWN_SCORE, score: 20 },
|
|
41
|
-
{ type: pinch_condition_1.PinchConditionType.PHASE, phase: pinch_condition_1.MatchPhase.SERVING }
|
|
40
|
+
{ type: pinch_condition_1.PinchConditionType.PHASE, phase: pinch_condition_1.MatchPhase.SERVING },
|
|
41
|
+
{ type: pinch_condition_1.PinchConditionType.FATIGUE, band: energy_band_1.EnergyBand.EXHAUSTED }
|
|
42
42
|
]
|
|
43
43
|
});
|
|
44
44
|
(0, globals_1.expect)(res.success).toBe(true);
|
|
45
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
46
|
(0, globals_1.it)('rejects an invalid PHASE value', () => {
|
|
51
47
|
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({
|
|
52
|
-
mode: 'CONDITIONS',
|
|
53
48
|
conditions: [{ type: pinch_condition_1.PinchConditionType.PHASE, phase: 'ATTACKING' }]
|
|
54
49
|
});
|
|
55
50
|
(0, globals_1.expect)(res.success).toBe(false);
|
|
56
51
|
});
|
|
57
52
|
});
|
|
53
|
+
(0, globals_1.describe)('LiberoSetSubConfigSchema (legacy conversion)', () => {
|
|
54
|
+
(0, globals_1.it)('converts legacy NEVER to an empty condition list', () => {
|
|
55
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'NEVER' });
|
|
56
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
57
|
+
if (res.success)
|
|
58
|
+
(0, globals_1.expect)(res.data.conditions).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
(0, globals_1.it)('converts legacy ALWAYS to a single ASAP condition', () => {
|
|
61
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'ALWAYS' });
|
|
62
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
63
|
+
if (res.success)
|
|
64
|
+
(0, globals_1.expect)(res.data.conditions).toEqual([{ type: pinch_condition_1.PinchConditionType.ASAP }]);
|
|
65
|
+
});
|
|
66
|
+
(0, globals_1.it)('converts legacy FATIGUE + band to a FATIGUE condition carrying the band', () => {
|
|
67
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: energy_band_1.EnergyBand.WORN });
|
|
68
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
69
|
+
if (res.success)
|
|
70
|
+
(0, globals_1.expect)(res.data.conditions).toEqual([{ type: pinch_condition_1.PinchConditionType.FATIGUE, band: energy_band_1.EnergyBand.WORN }]);
|
|
71
|
+
});
|
|
72
|
+
(0, globals_1.it)('converts legacy FATIGUE without a band to a bandless FATIGUE condition (team default)', () => {
|
|
73
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE' });
|
|
74
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
75
|
+
if (res.success)
|
|
76
|
+
(0, globals_1.expect)(res.data.conditions).toEqual([{ type: pinch_condition_1.PinchConditionType.FATIGUE }]);
|
|
77
|
+
});
|
|
78
|
+
(0, globals_1.it)('converts legacy CONDITIONS by stripping the mode and keeping the conditions', () => {
|
|
79
|
+
const conditions = [{ type: pinch_condition_1.PinchConditionType.PHASE, phase: pinch_condition_1.MatchPhase.RECEIVING }];
|
|
80
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'CONDITIONS', conditionLogic: 'ALL', conditions });
|
|
81
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
82
|
+
if (res.success) {
|
|
83
|
+
(0, globals_1.expect)(res.data.conditions).toEqual(conditions);
|
|
84
|
+
(0, globals_1.expect)(res.data.mode).toBeUndefined();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
(0, globals_1.it)('heals a legacy CONDITIONS config with no conditions to never (empty list) instead of rejecting', () => {
|
|
88
|
+
const res = libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'CONDITIONS' });
|
|
89
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
90
|
+
if (res.success)
|
|
91
|
+
(0, globals_1.expect)(res.data.conditions ?? []).toHaveLength(0);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
58
94
|
(0, globals_1.describe)('LiberoSubConfigSchema', () => {
|
|
59
|
-
(0, globals_1.it)('accepts a flat config with no per-set overrides', () => {
|
|
95
|
+
(0, globals_1.it)('accepts a flat config with no per-set overrides (legacy shape converts)', () => {
|
|
60
96
|
(0, globals_1.expect)(libero_sub_z_1.LiberoSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: energy_band_1.EnergyBand.WORN }).success).toBe(true);
|
|
61
97
|
});
|
|
62
|
-
(0, globals_1.it)('accepts up to five per-set configs', () => {
|
|
98
|
+
(0, globals_1.it)('accepts up to five per-set configs (legacy per-set entries convert)', () => {
|
|
63
99
|
const res = libero_sub_z_1.LiberoSubConfigSchema.safeParse({
|
|
64
100
|
mode: 'NEVER',
|
|
65
101
|
sets: [
|
|
66
102
|
{ mode: 'NEVER' },
|
|
67
103
|
{ mode: 'ALWAYS' },
|
|
68
104
|
{ mode: 'FATIGUE', fatigueBand: energy_band_1.EnergyBand.TIRED },
|
|
69
|
-
{
|
|
70
|
-
{
|
|
105
|
+
{ conditions: [{ type: pinch_condition_1.PinchConditionType.FATIGUE }] },
|
|
106
|
+
{}
|
|
71
107
|
]
|
|
72
108
|
});
|
|
73
109
|
(0, globals_1.expect)(res.success).toBe(true);
|
|
110
|
+
if (res.success) {
|
|
111
|
+
(0, globals_1.expect)(res.data.sets?.[0].conditions).toEqual([]);
|
|
112
|
+
(0, globals_1.expect)(res.data.sets?.[1].conditions).toEqual([{ type: pinch_condition_1.PinchConditionType.ASAP }]);
|
|
113
|
+
(0, globals_1.expect)(res.data.sets?.[2].conditions).toEqual([{ type: pinch_condition_1.PinchConditionType.FATIGUE, band: energy_band_1.EnergyBand.TIRED }]);
|
|
114
|
+
}
|
|
74
115
|
});
|
|
75
116
|
(0, globals_1.it)('rejects more than five per-set configs', () => {
|
|
76
117
|
const res = libero_sub_z_1.LiberoSubConfigSchema.safeParse({
|
|
77
|
-
|
|
78
|
-
sets: Array.from({ length: 6 }, () => ({ mode: 'NEVER' }))
|
|
118
|
+
sets: Array.from({ length: 6 }, () => ({}))
|
|
79
119
|
});
|
|
80
120
|
(0, globals_1.expect)(res.success).toBe(false);
|
|
81
121
|
});
|
|
82
|
-
(0, globals_1.it)('rejects an unknown
|
|
122
|
+
(0, globals_1.it)('rejects an unknown condition type in a per-set config', () => {
|
|
83
123
|
const res = libero_sub_z_1.LiberoSubConfigSchema.safeParse({
|
|
84
|
-
|
|
85
|
-
sets: [{ mode: 'WHENEVER' }]
|
|
124
|
+
sets: [{ conditions: [{ type: 'WHENEVER' }] }]
|
|
86
125
|
});
|
|
87
126
|
(0, globals_1.expect)(res.success).toBe(false);
|
|
88
127
|
});
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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>;
|
|
@@ -50,14 +50,23 @@ const lineupSchema = zod_1.z.object({
|
|
|
50
50
|
});
|
|
51
51
|
// Per-player tempo preference (set-tempo rework): absent key = Auto.
|
|
52
52
|
const tempoPreferenceSchema = zod_1.z.enum(['FAST', 'HIGH']);
|
|
53
|
-
// One rotation's
|
|
54
|
-
//
|
|
55
|
-
//
|
|
53
|
+
// One rotation's attacker weighting. `rotation` is the main setter's rotational slot (1..6); `weights` maps each
|
|
54
|
+
// attacker's starter id to an integer percentage share (values sum to 100; 0 removes an attacker). `tempos`
|
|
55
|
+
// optionally forces a per-starter tempo (Auto when absent). Cross-field checks (keys are starters, weights sum to
|
|
56
|
+
// 100) live in the superRefine below.
|
|
56
57
|
const offensivePreferenceSchema = zod_1.z.object({
|
|
57
58
|
rotation: zod_1.z.number().int().min(1).max(6),
|
|
58
|
-
|
|
59
|
+
weights: zod_1.z.record(zod_1.z.string(), zod_1.z.number().int().min(0)),
|
|
59
60
|
tempos: zod_1.z.record(zod_1.z.string(), tempoPreferenceSchema).optional()
|
|
60
61
|
});
|
|
62
|
+
// The opposing attacking functions a dedicated blocker can commit to: every RoleEnum except the libero.
|
|
63
|
+
const blockingFunctionSchema = zod_1.z.nativeEnum(player_1.RoleEnum).refine((v) => v !== player_1.RoleEnum.LIBERO, { message: 'BLOCKING_TARGET_CANNOT_BE_LIBERO' });
|
|
64
|
+
// One rotation's dedicated-blocking assignments: `targets` maps my front-row starter ids to the opposing function
|
|
65
|
+
// each commits to block. Cross-field checks (keys are starters) live in the superRefine below.
|
|
66
|
+
const blockingPreferenceSchema = zod_1.z.object({
|
|
67
|
+
rotation: zod_1.z.number().int().min(1).max(6),
|
|
68
|
+
targets: zod_1.z.record(zod_1.z.string(), blockingFunctionSchema)
|
|
69
|
+
});
|
|
61
70
|
// One set's rotation-system override (per-set tactics): system + its designated setters travel together.
|
|
62
71
|
const systemSetSchema = zod_1.z.object({
|
|
63
72
|
rotationSystem: zod_1.z.nativeEnum(rotation_system_1.RotationSystemEnum),
|
|
@@ -85,6 +94,9 @@ exports.TacticsInputSchema = zod_1.z.object({
|
|
|
85
94
|
// validated with the same cross-field rules as the flat fields (see superRefine below).
|
|
86
95
|
systemSets: zod_1.z.array(systemSetSchema).max(5).optional(),
|
|
87
96
|
offensivePreferenceSets: zod_1.z.array(zod_1.z.array(offensivePreferenceSchema)).max(5).optional(),
|
|
97
|
+
// Blocking preferences (dedicated blocking). Defaults keep every existing Tactics.create({...}) call valid.
|
|
98
|
+
blockingPreferences: zod_1.z.array(blockingPreferenceSchema).default([]),
|
|
99
|
+
blockingPreferenceSets: zod_1.z.array(zod_1.z.array(blockingPreferenceSchema)).max(5).optional(),
|
|
88
100
|
// Injuries. Defaults keep every existing Tactics.create({...}) call valid. replaceKnockedImmediately is
|
|
89
101
|
// PER STARTER (owner order 2026-07-05): keyed by starter id, missing key = ON; keys validated against the
|
|
90
102
|
// lineup in the superRefine below. injuryReplacements is the per-starter explicit replacement mapping
|
|
@@ -153,16 +165,17 @@ exports.TacticsInputSchema = zod_1.z.object({
|
|
|
153
165
|
ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_DUPLICATE_ROTATION', path });
|
|
154
166
|
}
|
|
155
167
|
seenRotations.add(pref.rotation);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_DUPLICATE_ATTACKER', path });
|
|
159
|
-
}
|
|
160
|
-
for (const id of ids) {
|
|
168
|
+
// Weight keys are on-court starters, and the percentages must sum to exactly 100 (0 removes an attacker).
|
|
169
|
+
for (const id of Object.keys(pref.weights)) {
|
|
161
170
|
if (!starterIds.has(id)) {
|
|
162
171
|
ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_ATTACKER_NOT_A_STARTER', path });
|
|
163
172
|
}
|
|
164
173
|
}
|
|
165
|
-
|
|
174
|
+
const total = Object.values(pref.weights).reduce((sum, w) => sum + w, 0);
|
|
175
|
+
if (total !== 100) {
|
|
176
|
+
ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_WEIGHTS_MUST_SUM_TO_100', path });
|
|
177
|
+
}
|
|
178
|
+
// Tempo preference keys are starter ids too.
|
|
166
179
|
for (const id of Object.keys(pref.tempos ?? {})) {
|
|
167
180
|
if (!starterIds.has(id)) {
|
|
168
181
|
ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_TEMPO_NOT_A_STARTER', path });
|
|
@@ -170,6 +183,22 @@ exports.TacticsInputSchema = zod_1.z.object({
|
|
|
170
183
|
}
|
|
171
184
|
}
|
|
172
185
|
};
|
|
186
|
+
// Blocking preferences: unique rotations; each assignment keys an on-court starter (the UI only offers front-row
|
|
187
|
+
// players, the schema checks starters). The target function is validated by blockingFunctionSchema.
|
|
188
|
+
const validateBlockingPreferences = (preferences, path) => {
|
|
189
|
+
const seenRotations = new Set();
|
|
190
|
+
for (const pref of preferences) {
|
|
191
|
+
if (seenRotations.has(pref.rotation)) {
|
|
192
|
+
ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_DUPLICATE_ROTATION', path });
|
|
193
|
+
}
|
|
194
|
+
seenRotations.add(pref.rotation);
|
|
195
|
+
for (const id of Object.keys(pref.targets)) {
|
|
196
|
+
if (!starterIds.has(id)) {
|
|
197
|
+
ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER', path });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
173
202
|
validateSystem(data.rotationSystem, data.designatedSetters, ['designatedSetters']);
|
|
174
203
|
for (const [i, systemSet] of (data.systemSets ?? []).entries()) {
|
|
175
204
|
validateSystem(systemSet.rotationSystem, systemSet.designatedSetters, ['systemSets', i]);
|
|
@@ -178,6 +207,10 @@ exports.TacticsInputSchema = zod_1.z.object({
|
|
|
178
207
|
for (const [i, preferences] of (data.offensivePreferenceSets ?? []).entries()) {
|
|
179
208
|
validateOffensivePreferences(preferences, ['offensivePreferenceSets', i]);
|
|
180
209
|
}
|
|
210
|
+
validateBlockingPreferences(data.blockingPreferences, ['blockingPreferences']);
|
|
211
|
+
for (const [i, preferences] of (data.blockingPreferenceSets ?? []).entries()) {
|
|
212
|
+
validateBlockingPreferences(preferences, ['blockingPreferenceSets', i]);
|
|
213
|
+
}
|
|
181
214
|
// Second libero (L2). The second libero is a reserve (not a starter, the libero, or a bench player). A swap
|
|
182
215
|
// rule (FATIGUE/ALWAYS in any set) needs someone to swap in, so it requires a second libero, and a second
|
|
183
216
|
// libero needs a starting libero to replace ("no L2 without an L"). Per-set libero replacements, like the
|
|
@@ -188,10 +221,11 @@ exports.TacticsInputSchema = zod_1.z.object({
|
|
|
188
221
|
...(libero != null ? [libero.id] : []),
|
|
189
222
|
...data.lineup.bench.map(p => p.id)
|
|
190
223
|
]);
|
|
191
|
-
|
|
192
|
-
|
|
224
|
+
// Unified condition engine: the rule wants a swap when ANY config (flat or per-set) has conditions.
|
|
225
|
+
const liberoConditionSets = data.liberoSub != null
|
|
226
|
+
? [data.liberoSub.conditions ?? [], ...(data.liberoSub.sets ?? []).map(s => s.conditions ?? [])]
|
|
193
227
|
: [];
|
|
194
|
-
const wantsLiberoSwap =
|
|
228
|
+
const wantsLiberoSwap = liberoConditionSets.some(conditions => conditions.length > 0);
|
|
195
229
|
if (wantsLiberoSwap && data.secondLibero == null) {
|
|
196
230
|
ctx.addIssue({ code: 'custom', message: 'LIBERO_SWAP_REQUIRES_SECOND_LIBERO', path: ['secondLibero'] });
|
|
197
231
|
}
|