volleyballsimtypes 0.0.515 → 0.0.516
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 +1 -2
- package/dist/cjs/src/api/tactics-player-refs.js +2 -2
- package/dist/cjs/src/data/models/tactics.d.ts +2 -4
- package/dist/cjs/src/data/models/tactics.js +0 -5
- package/dist/cjs/src/data/transformers/tactics.js +4 -36
- package/dist/cjs/src/data/transformers/tactics.test.js +9 -46
- package/dist/cjs/src/service/team/attack-priority.d.ts +3 -0
- package/dist/cjs/src/service/team/attack-priority.js +11 -0
- package/dist/cjs/src/service/team/base-config.d.ts +1 -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/offensive-preference.d.ts +2 -1
- package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +15 -5
- package/dist/cjs/src/service/team/schemas/tactics.z.js +28 -32
- package/dist/cjs/src/service/team/schemas/tactics.z.test.js +25 -92
- package/dist/cjs/src/service/team/schemas/team.z.d.ts +15 -5
- package/dist/cjs/src/service/team/tactics.d.ts +0 -2
- package/dist/cjs/src/service/team/tactics.js +1 -2
- package/dist/esm/src/api/index.d.ts +1 -2
- package/dist/esm/src/api/tactics-player-refs.js +2 -2
- package/dist/esm/src/data/models/tactics.d.ts +2 -4
- package/dist/esm/src/data/models/tactics.js +0 -5
- package/dist/esm/src/data/transformers/tactics.js +4 -36
- package/dist/esm/src/data/transformers/tactics.test.js +9 -46
- package/dist/esm/src/service/team/attack-priority.d.ts +3 -0
- package/dist/esm/src/service/team/attack-priority.js +8 -0
- package/dist/esm/src/service/team/base-config.d.ts +1 -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/offensive-preference.d.ts +2 -1
- package/dist/esm/src/service/team/schemas/tactics.z.d.ts +15 -5
- package/dist/esm/src/service/team/schemas/tactics.z.js +28 -32
- package/dist/esm/src/service/team/schemas/tactics.z.test.js +25 -92
- package/dist/esm/src/service/team/schemas/team.z.d.ts +15 -5
- package/dist/esm/src/service/team/tactics.d.ts +0 -2
- package/dist/esm/src/service/team/tactics.js +1 -2
- package/package.json +1 -1
|
@@ -47,13 +47,14 @@ 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 attacker weighting. `rotation` is the main setter's rotational slot (1..6); `
|
|
51
|
-
//
|
|
52
|
-
// optionally forces a per-
|
|
53
|
-
//
|
|
50
|
+
// One rotation's attacker weighting. `rotation` is the main setter's rotational slot (1..6); `priorities` maps a
|
|
51
|
+
// player id (a starter, or a designated substitute that overrides its slot once on court) to an AttackPriority.
|
|
52
|
+
// `tempos` optionally forces a per-player tempo (Auto when absent). Cross-field checks (keys are starters or
|
|
53
|
+
// designated subs) live in the superRefine below. No sum constraint: the setter weights by PRIORITY_WEIGHT.
|
|
54
|
+
const attackPrioritySchema = z.enum(['HIGH', 'MID', 'LOW', 'NONE']);
|
|
54
55
|
const offensivePreferenceSchema = z.object({
|
|
55
56
|
rotation: z.number().int().min(1).max(6),
|
|
56
|
-
|
|
57
|
+
priorities: z.record(z.string(), attackPrioritySchema),
|
|
57
58
|
tempos: z.record(z.string(), tempoPreferenceSchema).optional()
|
|
58
59
|
});
|
|
59
60
|
// An opposing attacking function a front-row starter can commit to (every RoleEnum except the libero, which never
|
|
@@ -87,6 +88,9 @@ const baseCoordSchema = z.object({ d: z.number().finite(), r: z.number().finite(
|
|
|
87
88
|
const baseConfigSchema = z.object({
|
|
88
89
|
coords: z.record(z.string(), z.record(z.string(), z.record(z.string(), baseCoordSchema))),
|
|
89
90
|
receive: z.record(z.string(), z.record(z.string(), z.boolean())),
|
|
91
|
+
// Optional per-player receive override (owner 2026-08-13): [rotation][playerId] -> passes. Keys (designated subs)
|
|
92
|
+
// are validated in the superRefine. Optional so configs saved before this field validate unchanged.
|
|
93
|
+
receiveByPlayer: z.record(z.string(), z.record(z.string(), z.boolean())).optional(),
|
|
90
94
|
// Optional per-point-of-attack blocking (owner 2026-08-07): [DEF_L|DEF_M|DEF_R][rotation][zone] -> canBlock.
|
|
91
95
|
// Optional so presets/tactics saved before this field validate unchanged.
|
|
92
96
|
blocking: z.record(z.string(), z.record(z.string(), z.record(z.string(), z.boolean()))).optional(),
|
|
@@ -114,9 +118,6 @@ export const TacticsInputSchema = z.object({
|
|
|
114
118
|
offensivePreferences: z.array(offensivePreferenceSchema).default([]),
|
|
115
119
|
// Attack shares on serve (libero-serve feature): per-player attack share the sim applies when a
|
|
116
120
|
// libero-replaced player is on court unreplaced (serving, or through a libero injury). Keyed by starter id.
|
|
117
|
-
// This is NOT part of the offensivePreferences sum-to-100; its band (0-100) and libero-replacement-key rules
|
|
118
|
-
// are validated independently in the superRefine below.
|
|
119
|
-
serveAttackShares: z.record(z.string(), z.number().int()).default({}),
|
|
120
121
|
// Per-set overrides (5 entries, index 0 = set 1; absent => the flat fields apply to all sets). Each entry is
|
|
121
122
|
// validated with the same cross-field rules as the flat fields (see superRefine below).
|
|
122
123
|
systemSets: z.array(systemSetSchema).max(5).optional(),
|
|
@@ -164,6 +165,11 @@ export const TacticsInputSchema = z.object({
|
|
|
164
165
|
const starters = slots.map(([position, player]) => ({ position, id: player.id }));
|
|
165
166
|
const starterIds = new Set(starters.map(s => s.id));
|
|
166
167
|
const positionById = new Map(starters.map(s => [s.id, s.position]));
|
|
168
|
+
// Any bench player may carry its OWN per-player attack priority / tempo / block / receive (owner 2026-08-13):
|
|
169
|
+
// those maps are keyed by a player id, and the engine resolves them for whoever is on court. So the attack /
|
|
170
|
+
// block / receive key checks accept a starter OR any bench player (every bench player gets its own settings, not
|
|
171
|
+
// just the designated subs).
|
|
172
|
+
const allowedPlayerKeys = new Set([...starterIds, ...data.lineup.bench.map(p => p.id)]);
|
|
167
173
|
// Designated setters: count must match the system, each must be an on-court starter, and in 4-2 / 6-2 the two
|
|
168
174
|
// setters sit in opposite slots (3 rotations apart). Run on the flat fields and on every per-set entry.
|
|
169
175
|
const validateSystem = (rotationSystem, designatedSetters, path) => {
|
|
@@ -194,19 +200,15 @@ export const TacticsInputSchema = z.object({
|
|
|
194
200
|
ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_DUPLICATE_ROTATION', path });
|
|
195
201
|
}
|
|
196
202
|
seenRotations.add(pref.rotation);
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
203
|
+
// Priority + tempo keys are an on-court starter or a designated sub (no sum-to-100: the setter weights by
|
|
204
|
+
// PRIORITY_WEIGHT and normalizes by the real total).
|
|
205
|
+
for (const id of Object.keys(pref.priorities)) {
|
|
206
|
+
if (!allowedPlayerKeys.has(id)) {
|
|
200
207
|
ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_ATTACKER_NOT_A_STARTER', path });
|
|
201
208
|
}
|
|
202
209
|
}
|
|
203
|
-
const total = Object.values(pref.weights).reduce((sum, w) => sum + w, 0);
|
|
204
|
-
if (total !== 100) {
|
|
205
|
-
ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_WEIGHTS_MUST_SUM_TO_100', path });
|
|
206
|
-
}
|
|
207
|
-
// Tempo preference keys are starter ids too.
|
|
208
210
|
for (const id of Object.keys(pref.tempos ?? {})) {
|
|
209
|
-
if (!
|
|
211
|
+
if (!allowedPlayerKeys.has(id)) {
|
|
210
212
|
ctx.addIssue({ code: 'custom', message: 'OFFENSIVE_PREFERENCE_TEMPO_NOT_A_STARTER', path });
|
|
211
213
|
}
|
|
212
214
|
}
|
|
@@ -223,13 +225,13 @@ export const TacticsInputSchema = z.object({
|
|
|
223
225
|
}
|
|
224
226
|
seenRotations.add(pref.rotation);
|
|
225
227
|
for (const id of Object.keys(pref.commit)) {
|
|
226
|
-
if (!
|
|
228
|
+
if (!allowedPlayerKeys.has(id)) {
|
|
227
229
|
ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER', path });
|
|
228
230
|
}
|
|
229
231
|
}
|
|
230
232
|
for (const form of ['DEF_L', 'DEF_M', 'DEF_R']) {
|
|
231
233
|
for (const id of pref.noBlock[form] ?? []) {
|
|
232
|
-
if (!
|
|
234
|
+
if (!allowedPlayerKeys.has(id)) {
|
|
233
235
|
ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER', path });
|
|
234
236
|
}
|
|
235
237
|
}
|
|
@@ -248,19 +250,13 @@ export const TacticsInputSchema = z.object({
|
|
|
248
250
|
for (const [i, preferences] of (data.blockingPreferenceSets ?? []).entries()) {
|
|
249
251
|
validateBlockingPreferences(preferences, ['blockingPreferenceSets', i]);
|
|
250
252
|
}
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
for (const [id, value] of Object.entries(data.serveAttackShares)) {
|
|
259
|
-
if (!liberoReplacementIds.has(id)) {
|
|
260
|
-
ctx.addIssue({ code: 'custom', message: 'SERVE_ATTACK_SHARE_NOT_A_LIBERO_REPLACEMENT', path: ['serveAttackShares'] });
|
|
261
|
-
}
|
|
262
|
-
if (value < 0 || value > 100) {
|
|
263
|
-
ctx.addIssue({ code: 'custom', message: 'SERVE_ATTACK_SHARE_OUT_OF_BAND', path: ['serveAttackShares'] });
|
|
253
|
+
// Per-player receive override (substitute settings, owner 2026-08-13): [rotation][playerId] -> passes. Each inner
|
|
254
|
+
// key is an on-court starter or a designated sub.
|
|
255
|
+
for (const byPlayer of Object.values(data.basePositions?.receiveByPlayer ?? {})) {
|
|
256
|
+
for (const id of Object.keys(byPlayer)) {
|
|
257
|
+
if (!allowedPlayerKeys.has(id)) {
|
|
258
|
+
ctx.addIssue({ code: 'custom', message: 'RECEIVE_OVERRIDE_NOT_A_STARTER', path: ['basePositions', 'receiveByPlayer'] });
|
|
259
|
+
}
|
|
264
260
|
}
|
|
265
261
|
}
|
|
266
262
|
// Second libero (L2). The second libero is a reserve (not a starter, the libero, or a bench player). A swap
|
|
@@ -94,7 +94,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
94
94
|
{ rotationSystem: '4-2', designatedSetters: [p1, p4] }
|
|
95
95
|
],
|
|
96
96
|
offensivePreferenceSets: [
|
|
97
|
-
[{ rotation: 1,
|
|
97
|
+
[{ rotation: 1, priorities: { [p2.id]: 'HIGH', [p3.id]: 'MID' } }],
|
|
98
98
|
[]
|
|
99
99
|
]
|
|
100
100
|
}));
|
|
@@ -103,7 +103,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
103
103
|
it('carries the per-set fields through Tactics.create parsing', () => {
|
|
104
104
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
105
105
|
systemSets: [{ rotationSystem: '5-1', designatedSetters: [p1] }],
|
|
106
|
-
offensivePreferenceSets: [[{ rotation: 2,
|
|
106
|
+
offensivePreferenceSets: [[{ rotation: 2, priorities: { [p3.id]: 'HIGH' } }]]
|
|
107
107
|
}));
|
|
108
108
|
expect(res.success).toBe(true);
|
|
109
109
|
if (res.success) {
|
|
@@ -152,7 +152,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
152
152
|
});
|
|
153
153
|
it('rejects a per-set preferences list with a duplicate rotation', () => {
|
|
154
154
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
155
|
-
offensivePreferenceSets: [[{ rotation: 1,
|
|
155
|
+
offensivePreferenceSets: [[{ rotation: 1, priorities: { [p2.id]: 'HIGH' } }, { rotation: 1, priorities: { [p3.id]: 'HIGH' } }]]
|
|
156
156
|
}));
|
|
157
157
|
expect(res.success).toBe(false);
|
|
158
158
|
if (!res.success) {
|
|
@@ -162,25 +162,38 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
162
162
|
});
|
|
163
163
|
it('rejects a per-set preferences map that keys a non-starter', () => {
|
|
164
164
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
165
|
-
offensivePreferenceSets: [[{ rotation: 1,
|
|
165
|
+
offensivePreferenceSets: [[{ rotation: 1, priorities: { [reserve.id]: 'HIGH' } }]]
|
|
166
166
|
}));
|
|
167
167
|
expect(res.success).toBe(false);
|
|
168
168
|
if (!res.success) {
|
|
169
169
|
expect(res.error.issues.some(i => i.message === 'OFFENSIVE_PREFERENCE_ATTACKER_NOT_A_STARTER')).toBe(true);
|
|
170
170
|
}
|
|
171
171
|
});
|
|
172
|
-
it('
|
|
172
|
+
it('accepts offensive priorities with no sum constraint', () => {
|
|
173
173
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
174
|
-
offensivePreferences: [{ rotation: 1,
|
|
174
|
+
offensivePreferences: [{ rotation: 1, priorities: { [p2.id]: 'HIGH', [p3.id]: 'LOW' } }]
|
|
175
|
+
}));
|
|
176
|
+
expect(res.success).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
it('rejects an invalid attack priority value', () => {
|
|
179
|
+
const res = TacticsInputSchema.safeParse(baseInput({
|
|
180
|
+
offensivePreferences: [{ rotation: 1, priorities: { [p2.id]: 'MEGA' } }]
|
|
175
181
|
}));
|
|
176
182
|
expect(res.success).toBe(false);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
183
|
+
});
|
|
184
|
+
it('accepts a priority / tempo / no-block keyed by a designated substitute', () => {
|
|
185
|
+
const res = TacticsInputSchema.safeParse(baseInput({
|
|
186
|
+
lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [reserve] },
|
|
187
|
+
designatedSubs: [{ starter: p2, bench: reserve }],
|
|
188
|
+
offensivePreferences: [{ rotation: 1, priorities: { [reserve.id]: 'HIGH' }, tempos: { [reserve.id]: 'FAST' } }],
|
|
189
|
+
blockingPreferences: [{ rotation: 1, commit: {}, noBlock: { DEF_L: [reserve.id], DEF_M: [], DEF_R: [] } }],
|
|
190
|
+
basePositions: { coords: {}, receive: {}, receiveByPlayer: { 1: { [reserve.id]: false } } }
|
|
191
|
+
}));
|
|
192
|
+
expect(res.success).toBe(true);
|
|
180
193
|
});
|
|
181
194
|
it('accepts offensive preferences with a starter-keyed tempos map', () => {
|
|
182
195
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
183
|
-
offensivePreferences: [{ rotation: 1,
|
|
196
|
+
offensivePreferences: [{ rotation: 1, priorities: { [p2.id]: 'HIGH', [p3.id]: 'MID' }, tempos: { [p2.id]: 'FAST', [p3.id]: 'HIGH' } }]
|
|
184
197
|
}));
|
|
185
198
|
expect(res.success).toBe(true);
|
|
186
199
|
if (res.success) {
|
|
@@ -189,7 +202,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
189
202
|
});
|
|
190
203
|
it('rejects a tempos map keyed by a non-starter', () => {
|
|
191
204
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
192
|
-
offensivePreferences: [{ rotation: 1,
|
|
205
|
+
offensivePreferences: [{ rotation: 1, priorities: { [p2.id]: 'HIGH' }, tempos: { [reserve.id]: 'FAST' } }]
|
|
193
206
|
}));
|
|
194
207
|
expect(res.success).toBe(false);
|
|
195
208
|
if (!res.success) {
|
|
@@ -198,7 +211,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
198
211
|
});
|
|
199
212
|
it('rejects an invalid tempo value', () => {
|
|
200
213
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
201
|
-
offensivePreferences: [{ rotation: 1,
|
|
214
|
+
offensivePreferences: [{ rotation: 1, priorities: { [p2.id]: 'HIGH' }, tempos: { [p2.id]: 'SLOW' } }]
|
|
202
215
|
}));
|
|
203
216
|
expect(res.success).toBe(false);
|
|
204
217
|
});
|
|
@@ -323,83 +336,3 @@ describe('TacticsInputSchema — blocking preferences', () => {
|
|
|
323
336
|
expect(res.success).toBe(false);
|
|
324
337
|
});
|
|
325
338
|
});
|
|
326
|
-
describe('TacticsInputSchema — serve attack shares', () => {
|
|
327
|
-
const country = makeCountry();
|
|
328
|
-
const p1 = makePlayer(country);
|
|
329
|
-
const p2 = makePlayer(country);
|
|
330
|
-
const p3 = makePlayer(country);
|
|
331
|
-
const p4 = makePlayer(country);
|
|
332
|
-
const p5 = makePlayer(country);
|
|
333
|
-
const p6 = makePlayer(country);
|
|
334
|
-
const reserve = makePlayer(country);
|
|
335
|
-
// p5 sits at zone 5 (back row) and is the configured libero replacement, so it may carry a serve share.
|
|
336
|
-
function baseInput(overrides = {}) {
|
|
337
|
-
return {
|
|
338
|
-
lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
|
|
339
|
-
liberoReplacements: [p5],
|
|
340
|
-
...overrides
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
it('accepts a serve share keyed by a libero replacement, value in band', () => {
|
|
344
|
-
const res = TacticsInputSchema.safeParse(baseInput({ serveAttackShares: { [p5.id]: 30 } }));
|
|
345
|
-
expect(res.success).toBe(true);
|
|
346
|
-
if (res.success)
|
|
347
|
-
expect(res.data.serveAttackShares[p5.id]).toBe(30);
|
|
348
|
-
});
|
|
349
|
-
it('accepts 0 and 100 at the band edges', () => {
|
|
350
|
-
expect(TacticsInputSchema.safeParse(baseInput({ serveAttackShares: { [p5.id]: 0 } })).success).toBe(true);
|
|
351
|
-
expect(TacticsInputSchema.safeParse(baseInput({ serveAttackShares: { [p5.id]: 100 } })).success).toBe(true);
|
|
352
|
-
});
|
|
353
|
-
it('rejects a serve share keyed by a starter who is not a libero replacement', () => {
|
|
354
|
-
const res = TacticsInputSchema.safeParse(baseInput({ serveAttackShares: { [p2.id]: 30 } }));
|
|
355
|
-
expect(res.success).toBe(false);
|
|
356
|
-
if (!res.success) {
|
|
357
|
-
expect(res.error.issues.some(i => i.message === 'SERVE_ATTACK_SHARE_NOT_A_LIBERO_REPLACEMENT')).toBe(true);
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
it('rejects a serve share keyed by a reserve', () => {
|
|
361
|
-
const res = TacticsInputSchema.safeParse(baseInput({ serveAttackShares: { [reserve.id]: 30 } }));
|
|
362
|
-
expect(res.success).toBe(false);
|
|
363
|
-
if (!res.success) {
|
|
364
|
-
expect(res.error.issues.some(i => i.message === 'SERVE_ATTACK_SHARE_NOT_A_LIBERO_REPLACEMENT')).toBe(true);
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
it('rejects an out-of-band serve share (over 100)', () => {
|
|
368
|
-
const res = TacticsInputSchema.safeParse(baseInput({ serveAttackShares: { [p5.id]: 101 } }));
|
|
369
|
-
expect(res.success).toBe(false);
|
|
370
|
-
if (!res.success) {
|
|
371
|
-
expect(res.error.issues.some(i => i.message === 'SERVE_ATTACK_SHARE_OUT_OF_BAND')).toBe(true);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
it('rejects a negative serve share', () => {
|
|
375
|
-
const res = TacticsInputSchema.safeParse(baseInput({ serveAttackShares: { [p5.id]: -1 } }));
|
|
376
|
-
expect(res.success).toBe(false);
|
|
377
|
-
if (!res.success) {
|
|
378
|
-
expect(res.error.issues.some(i => i.message === 'SERVE_ATTACK_SHARE_OUT_OF_BAND')).toBe(true);
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
it('validates independently of the offensive-preference sum-to-100 (never enters that total)', () => {
|
|
382
|
-
// The other attackers' weights sum to exactly 100; the serve share on the libero replacement is separate
|
|
383
|
-
// and must not push the rotation over 100 or otherwise fail.
|
|
384
|
-
const res = TacticsInputSchema.safeParse(baseInput({
|
|
385
|
-
offensivePreferences: [{ rotation: 1, weights: { [p2.id]: 50, [p3.id]: 50 } }],
|
|
386
|
-
serveAttackShares: { [p5.id]: 40 }
|
|
387
|
-
}));
|
|
388
|
-
expect(res.success).toBe(true);
|
|
389
|
-
});
|
|
390
|
-
it('accepts a serve share on a per-set libero replacement', () => {
|
|
391
|
-
// p6 is a back-row starter made a libero replacement only via liberoReplacementSets.
|
|
392
|
-
const res = TacticsInputSchema.safeParse(baseInput({
|
|
393
|
-
liberoReplacements: [],
|
|
394
|
-
liberoReplacementSets: [[p6]],
|
|
395
|
-
serveAttackShares: { [p6.id]: 25 }
|
|
396
|
-
}));
|
|
397
|
-
expect(res.success).toBe(true);
|
|
398
|
-
});
|
|
399
|
-
it('defaults serveAttackShares to an empty map when omitted', () => {
|
|
400
|
-
const res = TacticsInputSchema.safeParse(baseInput());
|
|
401
|
-
expect(res.success).toBe(true);
|
|
402
|
-
if (res.success)
|
|
403
|
-
expect(res.data.serveAttackShares).toEqual({});
|
|
404
|
-
});
|
|
405
|
-
});
|
|
@@ -269,23 +269,32 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
269
269
|
designatedSetters: z.ZodDefault<z.ZodArray<z.ZodCustom<Player, Player>>>;
|
|
270
270
|
offensivePreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
271
271
|
rotation: z.ZodNumber;
|
|
272
|
-
|
|
272
|
+
priorities: z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
273
|
+
HIGH: "HIGH";
|
|
274
|
+
MID: "MID";
|
|
275
|
+
LOW: "LOW";
|
|
276
|
+
NONE: "NONE";
|
|
277
|
+
}>>;
|
|
273
278
|
tempos: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
274
|
-
FAST: "FAST";
|
|
275
279
|
HIGH: "HIGH";
|
|
280
|
+
FAST: "FAST";
|
|
276
281
|
}>>>;
|
|
277
282
|
}, z.core.$strip>>>;
|
|
278
|
-
serveAttackShares: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
279
283
|
systemSets: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
280
284
|
rotationSystem: z.ZodEnum<typeof import("..").RotationSystemEnum>;
|
|
281
285
|
designatedSetters: z.ZodArray<z.ZodCustom<Player, Player>>;
|
|
282
286
|
}, z.core.$strip>>>;
|
|
283
287
|
offensivePreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
|
|
284
288
|
rotation: z.ZodNumber;
|
|
285
|
-
|
|
289
|
+
priorities: z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
290
|
+
HIGH: "HIGH";
|
|
291
|
+
MID: "MID";
|
|
292
|
+
LOW: "LOW";
|
|
293
|
+
NONE: "NONE";
|
|
294
|
+
}>>;
|
|
286
295
|
tempos: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
287
|
-
FAST: "FAST";
|
|
288
296
|
HIGH: "HIGH";
|
|
297
|
+
FAST: "FAST";
|
|
289
298
|
}>>>;
|
|
290
299
|
}, z.core.$strip>>>>;
|
|
291
300
|
blockingPreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
@@ -312,6 +321,7 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
312
321
|
r: z.ZodNumber;
|
|
313
322
|
}, z.core.$strip>>>>;
|
|
314
323
|
receive: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodBoolean>>;
|
|
324
|
+
receiveByPlayer: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodBoolean>>>;
|
|
315
325
|
blocking: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodBoolean>>>>;
|
|
316
326
|
attackContact: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
317
327
|
d: z.ZodNumber;
|
|
@@ -35,7 +35,6 @@ export interface TacticsOpts {
|
|
|
35
35
|
readonly rotationSystem: RotationSystemEnum;
|
|
36
36
|
readonly designatedSetters: Player[];
|
|
37
37
|
readonly offensivePreferences: OffensivePreference[];
|
|
38
|
-
readonly serveAttackShares: Record<string, number>;
|
|
39
38
|
readonly systemSets?: SystemSet[];
|
|
40
39
|
readonly offensivePreferenceSets?: OffensivePreference[][];
|
|
41
40
|
readonly replaceKnockedImmediately: Record<string, boolean>;
|
|
@@ -57,7 +56,6 @@ export declare class Tactics {
|
|
|
57
56
|
readonly rotationSystem: RotationSystemEnum;
|
|
58
57
|
readonly designatedSetters: Player[];
|
|
59
58
|
readonly offensivePreferences: OffensivePreference[];
|
|
60
|
-
readonly serveAttackShares: Record<string, number>;
|
|
61
59
|
readonly systemSets?: SystemSet[];
|
|
62
60
|
readonly offensivePreferenceSets?: OffensivePreference[][];
|
|
63
61
|
readonly replaceKnockedImmediately: Record<string, boolean>;
|
|
@@ -15,7 +15,7 @@ export class Tactics {
|
|
|
15
15
|
}
|
|
16
16
|
return new Tactics(result.data);
|
|
17
17
|
}
|
|
18
|
-
constructor({ lineup, liberoReplacements, receiveRotationOffset = false, designatedSubs = [], substitutionBand = EnergyBand.TIRED, secondLibero, liberoSub, liberoReplacementSets, rotationSystem = RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [],
|
|
18
|
+
constructor({ lineup, liberoReplacements, receiveRotationOffset = false, designatedSubs = [], substitutionBand = EnergyBand.TIRED, secondLibero, liberoSub, liberoReplacementSets, rotationSystem = RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [], systemSets, offensivePreferenceSets, replaceKnockedImmediately = {}, injuryReplacements = [], liberoInjury, blockingPreferences = [], blockingPreferenceSets, basePositions }) {
|
|
19
19
|
this.lineup = lineup;
|
|
20
20
|
this.liberoReplacements = liberoReplacements;
|
|
21
21
|
this.receiveRotationOffset = receiveRotationOffset;
|
|
@@ -27,7 +27,6 @@ export class Tactics {
|
|
|
27
27
|
this.rotationSystem = rotationSystem;
|
|
28
28
|
this.designatedSetters = designatedSetters;
|
|
29
29
|
this.offensivePreferences = offensivePreferences;
|
|
30
|
-
this.serveAttackShares = serveAttackShares;
|
|
31
30
|
this.systemSets = systemSets;
|
|
32
31
|
this.offensivePreferenceSets = offensivePreferenceSets;
|
|
33
32
|
this.replaceKnockedImmediately = replaceKnockedImmediately;
|