volleyballsimtypes 0.0.511 → 0.0.513

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/cjs/src/api/index.d.ts +3 -2
  2. package/dist/cjs/src/api/tactics-player-refs.d.ts +2 -2
  3. package/dist/cjs/src/api/tactics-player-refs.js +4 -4
  4. package/dist/cjs/src/data/models/tactics.d.ts +3 -2
  5. package/dist/cjs/src/data/transformers/tactics.js +4 -4
  6. package/dist/cjs/src/data/transformers/tactics.test.js +16 -14
  7. package/dist/cjs/src/service/event/schemas/set.z.js +1 -1
  8. package/dist/cjs/src/service/event/set.d.ts +3 -2
  9. package/dist/cjs/src/service/event/set.js +4 -0
  10. package/dist/cjs/src/service/team/blocking-preference.d.ts +2 -3
  11. package/dist/cjs/src/service/team/blocking-preference.js +1 -7
  12. package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +12 -10
  13. package/dist/cjs/src/service/team/schemas/tactics.z.js +24 -20
  14. package/dist/cjs/src/service/team/schemas/tactics.z.test.js +34 -35
  15. package/dist/cjs/src/service/team/schemas/team.z.d.ts +12 -10
  16. package/dist/esm/src/api/index.d.ts +3 -2
  17. package/dist/esm/src/api/tactics-player-refs.d.ts +2 -2
  18. package/dist/esm/src/api/tactics-player-refs.js +4 -4
  19. package/dist/esm/src/data/models/tactics.d.ts +3 -2
  20. package/dist/esm/src/data/transformers/tactics.js +4 -4
  21. package/dist/esm/src/data/transformers/tactics.test.js +17 -15
  22. package/dist/esm/src/service/event/schemas/set.z.js +1 -1
  23. package/dist/esm/src/service/event/set.d.ts +3 -2
  24. package/dist/esm/src/service/event/set.js +4 -0
  25. package/dist/esm/src/service/team/blocking-preference.d.ts +2 -3
  26. package/dist/esm/src/service/team/blocking-preference.js +0 -6
  27. package/dist/esm/src/service/team/schemas/tactics.z.d.ts +12 -10
  28. package/dist/esm/src/service/team/schemas/tactics.z.js +24 -20
  29. package/dist/esm/src/service/team/schemas/tactics.z.test.js +34 -35
  30. package/dist/esm/src/service/team/schemas/team.z.d.ts +12 -10
  31. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { BaseConfig, BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, PointBreakdown, RallyMetrics, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, BlockingAssignment, DefenceFormation, CourtPosition, ConditionLogic, EnergyBand, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand } from '../service';
1
+ import { BaseConfig, BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, PointBreakdown, RallyMetrics, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, DefenceFormation, LineupFunction, CourtPosition, ConditionLogic, EnergyBand, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand } from '../service';
2
2
  export * from './tactics-player-refs';
3
3
  export type Rally = DataProps<_Rally> & {
4
4
  homePlayerPosition: PlayerPosition[];
@@ -102,7 +102,8 @@ export interface ApiOffensivePreference {
102
102
  }
103
103
  export interface ApiBlockingPreference {
104
104
  rotation: number;
105
- targets: Record<DefenceFormation, Record<string, BlockingAssignment>>;
105
+ commit: Record<string, LineupFunction>;
106
+ noBlock: Record<DefenceFormation, string[]>;
106
107
  }
107
108
  export interface ApiSystemSet {
108
109
  rotationSystem: RotationSystemEnum;
@@ -2,8 +2,8 @@ import type { Tactics } from '.';
2
2
  /**
3
3
  * Every player id a tactics config references, anywhere: the six court slots and the libero, the bench, libero
4
4
  * replacements (flat and per-set), the second libero, designated subs and their per-set overrides, designated
5
- * setters (flat and per-set), the keys of the offensive-weight and blocking-target maps, injury replacements
6
- * and the libero-injury reserves.
5
+ * setters (flat and per-set), the keys of the offensive-weight map, the blocking commit + no-block ids, injury
6
+ * replacements and the libero-injury reserves.
7
7
  *
8
8
  * There is exactly ONE implementation of this on purpose. It backs three rules that must agree or a player
9
9
  * slips through: the dismiss/retire guard (a referenced player cannot be removed), the event rarity check
@@ -4,8 +4,8 @@ exports.collectTacticsPlayerIds = collectTacticsPlayerIds;
4
4
  /**
5
5
  * Every player id a tactics config references, anywhere: the six court slots and the libero, the bench, libero
6
6
  * replacements (flat and per-set), the second libero, designated subs and their per-set overrides, designated
7
- * setters (flat and per-set), the keys of the offensive-weight and blocking-target maps, injury replacements
8
- * and the libero-injury reserves.
7
+ * setters (flat and per-set), the keys of the offensive-weight map, the blocking commit + no-block ids, injury
8
+ * replacements and the libero-injury reserves.
9
9
  *
10
10
  * There is exactly ONE implementation of this on purpose. It backs three rules that must agree or a player
11
11
  * slips through: the dismiss/retire guard (a referenced player cannot be removed), the event rarity check
@@ -29,8 +29,8 @@ function collectTacticsPlayerIds(config) {
29
29
  ...((config.offensivePreferences ?? []).flatMap(p => Object.keys(p.weights))),
30
30
  ...((config.systemSets ?? []).flatMap(s => s.designatedSetters)),
31
31
  ...((config.offensivePreferenceSets ?? []).flat().flatMap(p => Object.keys(p.weights))),
32
- ...((config.blockingPreferences ?? []).flatMap(p => Object.values(p.targets).flatMap(m => Object.keys(m ?? {})))),
33
- ...((config.blockingPreferenceSets ?? []).flat().flatMap(p => Object.values(p.targets).flatMap(m => Object.keys(m ?? {})))),
32
+ ...((config.blockingPreferences ?? []).flatMap(p => [...Object.keys(p.commit ?? {}), ...Object.values(p.noBlock ?? {}).flat()])),
33
+ ...((config.blockingPreferenceSets ?? []).flat().flatMap(p => [...Object.keys(p.commit ?? {}), ...Object.values(p.noBlock ?? {}).flat()])),
34
34
  // Injury replacements reference reserves (roster players outside the lineup); roster membership is the only
35
35
  // player check possible here (the schema enforces the bench/reserve role rules).
36
36
  ...((config.injuryReplacements ?? []).flatMap(ir => [ir.starterId, ir.replacementId, ir.benchBackfillId])),
@@ -1,7 +1,7 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model, Optional } from 'sequelize';
3
3
  import { PlayerId, TeamId, TeamModel } from '.';
4
- import { BaseConfig, BlockingAssignment, ConditionLogic, CourtPosition, DefenceFormation, EnergyBand, PinchCondition, RotationSystemEnum, SubBackMode, SubBand, SubMode } from '../../service';
4
+ import { BaseConfig, ConditionLogic, CourtPosition, DefenceFormation, EnergyBand, LineupFunction, PinchCondition, RotationSystemEnum, SubBackMode, SubBand, SubMode } from '../../service';
5
5
  export interface TacticsLineupAttributes {
6
6
  [CourtPosition.LIBERO_ZONE]?: PlayerId;
7
7
  [CourtPosition.LEFT_BACK]: PlayerId;
@@ -36,7 +36,8 @@ export interface OffensivePreferenceAttributes {
36
36
  }
37
37
  export interface BlockingPreferenceAttributes {
38
38
  rotation: number;
39
- targets: Record<DefenceFormation, Record<PlayerId, BlockingAssignment>>;
39
+ commit: Record<PlayerId, LineupFunction>;
40
+ noBlock: Record<DefenceFormation, PlayerId[]>;
40
41
  }
41
42
  export interface SystemSetAttributes {
42
43
  rotationSystem: RotationSystemEnum;
@@ -87,8 +87,8 @@ function transformToAttributes(tactics, teamId) {
87
87
  }))),
88
88
  serve_attack_shares: tactics.serveAttackShares ?? {},
89
89
  base_positions: tactics.basePositions,
90
- blocking_preferences: tactics.blockingPreferences.map(p => ({ rotation: p.rotation, targets: p.targets })),
91
- blocking_preference_sets: tactics.blockingPreferenceSets?.map(prefs => prefs.map(p => ({ rotation: p.rotation, targets: p.targets }))),
90
+ blocking_preferences: tactics.blockingPreferences.map(p => ({ rotation: p.rotation, commit: p.commit, noBlock: p.noBlock })),
91
+ blocking_preference_sets: tactics.blockingPreferenceSets?.map(prefs => prefs.map(p => ({ rotation: p.rotation, commit: p.commit, noBlock: p.noBlock }))),
92
92
  replace_knocked_immediately: tactics.replaceKnockedImmediately,
93
93
  injury_replacements: tactics.injuryReplacements.map(ir => ({
94
94
  starterId: ir.starter.id,
@@ -209,8 +209,8 @@ function transformToObject(model, roster) {
209
209
  return healed;
210
210
  })(),
211
211
  basePositions: model.base_positions ?? undefined,
212
- blockingPreferences: (model.blocking_preferences ?? []).map(p => ({ rotation: p.rotation, targets: p.targets })),
213
- blockingPreferenceSets: model.blocking_preference_sets?.map(prefs => prefs.map(p => ({ rotation: p.rotation, targets: p.targets }))),
212
+ blockingPreferences: (model.blocking_preferences ?? []).map(p => ({ rotation: p.rotation, commit: p.commit, noBlock: p.noBlock })),
213
+ blockingPreferenceSets: model.blocking_preference_sets?.map(prefs => prefs.map(p => ({ rotation: p.rotation, commit: p.commit, noBlock: p.noBlock }))),
214
214
  // LENIENT: keep only boolean entries keyed by a court starter or, when a second libero exists, the starting
215
215
  // libero (the libero knock toggle is an L2-only feature, owner 2026-07-06) so stale jsonb (an ex-starter's
216
216
  // key, a non-boolean value, a libero key left after the L2 was removed) can never fail schema validation
@@ -264,37 +264,39 @@ function makeTactics(designatedSubs) {
264
264
  const back = (0, tactics_1.transformToTactics)(attrs, roster);
265
265
  (0, globals_1.expect)(back.offensivePreferences[0].tempos).toEqual({ [p2.id]: 'FAST', [p3.id]: 'HIGH' });
266
266
  });
267
- (0, globals_1.it)('round-trips blocking preferences as per-defence-side id-keyed function maps', () => {
267
+ (0, globals_1.it)('round-trips blocking preferences as a base commit map plus per-side no-block ids', () => {
268
268
  const withBlocking = service_1.Tactics.create({
269
269
  lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
270
270
  liberoReplacements: [],
271
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: service_1.RoleEnum.OUTSIDE_HITTER }, DEF_R: { [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER } } }]
271
+ blockingPreferences: [{ rotation: 1, commit: { [p4.id]: service_1.RoleEnum.OUTSIDE_HITTER, [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_L: [p2.id] } }]
272
272
  });
273
273
  const attrs = (0, tactics_1.transformFromTactics)(withBlocking, 'team-1');
274
274
  (0, globals_1.expect)(attrs.blocking_preferences).toEqual([
275
- { rotation: 1, targets: { DEF_L: { [p4.id]: service_1.RoleEnum.OUTSIDE_HITTER }, DEF_M: {}, DEF_R: { [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER } } }
275
+ { rotation: 1, commit: { [p4.id]: service_1.RoleEnum.OUTSIDE_HITTER, [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_L: [p2.id], DEF_M: [], DEF_R: [] } }
276
276
  ]);
277
277
  const back = (0, tactics_1.transformToTactics)(attrs, roster);
278
- (0, globals_1.expect)(back.blockingPreferences[0].targets).toEqual({ DEF_L: { [p4.id]: service_1.RoleEnum.OUTSIDE_HITTER }, DEF_M: {}, DEF_R: { [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER } });
278
+ (0, globals_1.expect)(back.blockingPreferences[0].commit).toEqual({ [p4.id]: service_1.RoleEnum.OUTSIDE_HITTER, [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER });
279
+ (0, globals_1.expect)(back.blockingPreferences[0].noBlock).toEqual({ DEF_L: [p2.id], DEF_M: [], DEF_R: [] });
279
280
  });
280
- // NO_BLOCK rides in the same targets map as a commit assignment, so the transformers copy it wholesale with no
281
- // mapping code. This pins that: the sentinel must survive the row round-trip verbatim, mixed with a commit, and
282
- // in the per-set overrides. If it ever gets translated or dropped, a player silently starts blocking again.
283
- (0, globals_1.it)('round-trips a NO_BLOCK assignment, mixed with a commit and in per-set overrides', () => {
281
+ // The base commit map (blind) and the per-side no-block ids are copied wholesale by the transformers with no
282
+ // mapping code. This pins that they survive the row round-trip verbatim, including in the per-set overrides. If a
283
+ // no-block id is ever translated or dropped, a player silently starts blocking again.
284
+ (0, globals_1.it)('round-trips a base commit plus per-side no-block ids, including per-set overrides', () => {
284
285
  const withNoBlock = service_1.Tactics.create({
285
286
  lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
286
287
  liberoReplacements: [],
287
- blockingPreferences: [{ rotation: 1, targets: { DEF_M: { [p4.id]: service_1.NO_BLOCK, [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER } } }],
288
- blockingPreferenceSets: [[{ rotation: 2, targets: { DEF_L: { [p2.id]: service_1.NO_BLOCK } } }]]
288
+ blockingPreferences: [{ rotation: 1, commit: { [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_M: [p4.id] } }],
289
+ blockingPreferenceSets: [[{ rotation: 2, noBlock: { DEF_L: [p2.id] } }]]
289
290
  });
290
291
  const attrs = (0, tactics_1.transformFromTactics)(withNoBlock, 'team-1');
291
292
  (0, globals_1.expect)(attrs.blocking_preferences).toEqual([
292
- { rotation: 1, targets: { DEF_L: {}, DEF_M: { [p4.id]: service_1.NO_BLOCK, [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER }, DEF_R: {} } }
293
+ { rotation: 1, commit: { [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_L: [], DEF_M: [p4.id], DEF_R: [] } }
293
294
  ]);
294
- (0, globals_1.expect)(attrs.blocking_preference_sets).toEqual([[{ rotation: 2, targets: { DEF_L: { [p2.id]: service_1.NO_BLOCK }, DEF_M: {}, DEF_R: {} } }]]);
295
+ (0, globals_1.expect)(attrs.blocking_preference_sets).toEqual([[{ rotation: 2, commit: {}, noBlock: { DEF_L: [p2.id], DEF_M: [], DEF_R: [] } }]]);
295
296
  const back = (0, tactics_1.transformToTactics)(attrs, roster);
296
- (0, globals_1.expect)(back.blockingPreferences[0].targets).toEqual({ DEF_L: {}, DEF_M: { [p4.id]: service_1.NO_BLOCK, [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER }, DEF_R: {} });
297
- (0, globals_1.expect)(back.blockingPreferenceSets?.[0][0].targets).toEqual({ DEF_L: { [p2.id]: service_1.NO_BLOCK }, DEF_M: {}, DEF_R: {} });
297
+ (0, globals_1.expect)(back.blockingPreferences[0].commit).toEqual({ [p3.id]: service_1.RoleEnum.MIDDLE_BLOCKER });
298
+ (0, globals_1.expect)(back.blockingPreferences[0].noBlock).toEqual({ DEF_L: [], DEF_M: [p4.id], DEF_R: [] });
299
+ (0, globals_1.expect)(back.blockingPreferenceSets?.[0][0].noBlock).toEqual({ DEF_L: [p2.id], DEF_M: [], DEF_R: [] });
298
300
  });
299
301
  });
300
302
  (0, globals_1.describe)('libero injuries — service <-> row round-trip + lenient heal', () => {
@@ -5,7 +5,7 @@ const zod_1 = require("zod");
5
5
  const player_1 = require("../../player");
6
6
  const incident_z_1 = require("./incident.z");
7
7
  const positional_z_1 = require("./positional.z");
8
- const setTypeValues = [0, 1];
8
+ const setTypeValues = [0, 1, 2]; // OVERHAND, DUMP, BUMP
9
9
  const setFailureValues = [0, 1, 2, 3, 4];
10
10
  const setTempoValues = [0, 1, 2];
11
11
  const courtTargetValues = Array.from({ length: 13 }, (_, i) => i);
@@ -1,9 +1,10 @@
1
1
  import { InPlayEvent } from './in-play-event';
2
2
  export declare enum SetTypeEnum {
3
3
  OVERHAND = 0,
4
- DUMP = 1
4
+ DUMP = 1,
5
+ BUMP = 2
5
6
  }
6
- export type SetType = SetTypeEnum.OVERHAND | SetTypeEnum.DUMP;
7
+ export type SetType = SetTypeEnum.OVERHAND | SetTypeEnum.BUMP | SetTypeEnum.DUMP;
7
8
  export declare enum SetTempoEnum {
8
9
  FIRST = 0,
9
10
  SECOND = 1,
@@ -8,6 +8,10 @@ var SetTypeEnum;
8
8
  (function (SetTypeEnum) {
9
9
  SetTypeEnum[SetTypeEnum["OVERHAND"] = 0] = "OVERHAND";
10
10
  SetTypeEnum[SetTypeEnum["DUMP"] = 1] = "DUMP";
11
+ // A forearm (bump) set, used when the pass is off the net (owner 2026-08-11). OVERHAND and BUMP are both normal
12
+ // sets and behave identically for metrics (assist, first-attack); they differ only by technique (which stat graded
13
+ // the set) and their play-by-play label. DUMP remains the setter's own second-ball attack.
14
+ SetTypeEnum[SetTypeEnum["BUMP"] = 2] = "BUMP";
11
15
  })(SetTypeEnum || (exports.SetTypeEnum = SetTypeEnum = {}));
12
16
  // The tempo of a set (set-tempo rework). Decided by the simulator (pass quality + target lane + setter skill)
13
17
  // or forced by a per-player offensive preference. FIRST = quick (swings the `quick` stat, hardest to nail,
@@ -1,9 +1,8 @@
1
1
  import { LineupFunction } from './lineup-function';
2
- export declare const NO_BLOCK: "NO_BLOCK";
3
- export type BlockingAssignment = LineupFunction | typeof NO_BLOCK;
4
2
  export type DefenceFormation = 'DEF_L' | 'DEF_M' | 'DEF_R';
5
3
  export declare const DEFENCE_FORMATIONS: readonly DefenceFormation[];
6
4
  export interface BlockingPreference {
7
5
  readonly rotation: number;
8
- readonly targets: Record<DefenceFormation, Record<string, BlockingAssignment>>;
6
+ readonly commit: Record<string, LineupFunction>;
7
+ readonly noBlock: Record<DefenceFormation, string[]>;
9
8
  }
@@ -1,10 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFENCE_FORMATIONS = exports.NO_BLOCK = void 0;
4
- // A front-row player opted OUT of blocking for this rotation. They never join the wall under any circumstance:
5
- // not as the lead blocker (the lane's next-nearest front-row player leads instead), not as a side blocker, and
6
- // no trait can coerce them in -- MOVING_WALL's guaranteed lead hand, its beaten-read hand and its side-joiner
7
- // roll are all suppressed, as is a dedicated-blocking commit. Sits in the same `targets` map as a commit
8
- // assignment, so the two are mutually exclusive by construction: a player is reading, committed, or out.
9
- exports.NO_BLOCK = 'NO_BLOCK';
3
+ exports.DEFENCE_FORMATIONS = void 0;
10
4
  exports.DEFENCE_FORMATIONS = ['DEF_L', 'DEF_M', 'DEF_R'];
@@ -282,19 +282,21 @@ export declare const TacticsInputSchema: z.ZodObject<{
282
282
  }, z.core.$strip>>>>;
283
283
  blockingPreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
284
284
  rotation: z.ZodNumber;
285
- targets: z.ZodObject<{
286
- DEF_L: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
287
- DEF_M: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
288
- DEF_R: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
289
- }, z.core.$strip>;
285
+ commit: z.ZodDefault<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>>>>;
286
+ noBlock: z.ZodDefault<z.ZodObject<{
287
+ DEF_L: z.ZodDefault<z.ZodArray<z.ZodString>>;
288
+ DEF_M: z.ZodDefault<z.ZodArray<z.ZodString>>;
289
+ DEF_R: z.ZodDefault<z.ZodArray<z.ZodString>>;
290
+ }, z.core.$strip>>;
290
291
  }, z.core.$strip>>>;
291
292
  blockingPreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
292
293
  rotation: z.ZodNumber;
293
- targets: z.ZodObject<{
294
- DEF_L: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
295
- DEF_M: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
296
- DEF_R: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
297
- }, z.core.$strip>;
294
+ commit: z.ZodDefault<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>>>>;
295
+ noBlock: z.ZodDefault<z.ZodObject<{
296
+ DEF_L: z.ZodDefault<z.ZodArray<z.ZodString>>;
297
+ DEF_M: z.ZodDefault<z.ZodArray<z.ZodString>>;
298
+ DEF_R: z.ZodDefault<z.ZodArray<z.ZodString>>;
299
+ }, z.core.$strip>>;
298
300
  }, z.core.$strip>>>>;
299
301
  basePositions: z.ZodOptional<z.ZodObject<{
300
302
  coords: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TacticsInputSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const player_1 = require("../../player");
6
- const blocking_preference_1 = require("../blocking-preference");
7
6
  const energy_band_1 = require("../energy-band");
8
7
  const rotation_system_1 = require("../rotation-system");
9
8
  const designated_sub_z_1 = require("./designated-sub.z");
@@ -60,24 +59,23 @@ const offensivePreferenceSchema = zod_1.z.object({
60
59
  weights: zod_1.z.record(zod_1.z.string(), zod_1.z.number().int().min(0)),
61
60
  tempos: zod_1.z.record(zod_1.z.string(), tempoPreferenceSchema).optional()
62
61
  });
63
- // What a front-row starter can be assigned: an opposing attacking function to commit to (every RoleEnum except
64
- // the libero, which never attacks), or NO_BLOCK to sit the rotation out of the block entirely.
65
- const blockingFunctionSchema = zod_1.z.union([
66
- zod_1.z.nativeEnum(player_1.RoleEnum).refine((v) => v !== player_1.RoleEnum.LIBERO, { message: 'BLOCKING_TARGET_CANNOT_BE_LIBERO' }),
67
- zod_1.z.literal(blocking_preference_1.NO_BLOCK)
68
- ]);
69
- // One rotation's dedicated-blocking assignments: `targets` maps my front-row starter ids to the opposing function
70
- // each commits to block. Cross-field checks (keys are starters) live in the superRefine below.
71
- const blockingTargetsSchema = zod_1.z.record(zod_1.z.string(), blockingFunctionSchema);
72
- // Per point-of-attack defence (owner 2026-08-06): a separate front-row commit map for an attack from my left /
73
- // middle / right. Each defaults to {} so a partial config stays valid.
62
+ // An opposing attacking function a front-row starter can commit to (every RoleEnum except the libero, which never
63
+ // attacks). The commit is the BLIND, pre-set Defence-base choice (owner 2026-08-10).
64
+ const commitFunctionSchema = zod_1.z.nativeEnum(player_1.RoleEnum).refine((v) => v !== player_1.RoleEnum.LIBERO, { message: 'BLOCKING_TARGET_CANNOT_BE_LIBERO' });
65
+ // One rotation's blocking config (owner 2026-08-10). `commit` maps my front-row starter ids to the opposing function
66
+ // each commits to (blind, rotation-wide; absent = reads); `noBlock` lists, per point-of-attack defence, the front-row
67
+ // starters that sit out of the block for an attack from that side (overrides the commit for that side). Each side
68
+ // defaults to [] so a partial config stays valid. Cross-field checks (keys/ids are starters) live in the superRefine.
69
+ const commitSchema = zod_1.z.record(zod_1.z.string(), commitFunctionSchema);
70
+ const noBlockSchema = zod_1.z.object({
71
+ DEF_L: zod_1.z.array(zod_1.z.string()).default([]),
72
+ DEF_M: zod_1.z.array(zod_1.z.string()).default([]),
73
+ DEF_R: zod_1.z.array(zod_1.z.string()).default([])
74
+ });
74
75
  const blockingPreferenceSchema = zod_1.z.object({
75
76
  rotation: zod_1.z.number().int().min(1).max(6),
76
- targets: zod_1.z.object({
77
- DEF_L: blockingTargetsSchema.default({}),
78
- DEF_M: blockingTargetsSchema.default({}),
79
- DEF_R: blockingTargetsSchema.default({})
80
- })
77
+ commit: commitSchema.default({}),
78
+ noBlock: noBlockSchema.default({ DEF_L: [], DEF_M: [], DEF_R: [] })
81
79
  });
82
80
  // One set's rotation-system override (per-set tactics): system + its designated setters travel together.
83
81
  const systemSetSchema = zod_1.z.object({
@@ -214,8 +212,9 @@ exports.TacticsInputSchema = zod_1.z.object({
214
212
  }
215
213
  }
216
214
  };
217
- // Blocking preferences: unique rotations; each assignment keys an on-court starter (the UI only offers front-row
218
- // players, the schema checks starters). The target function is validated by blockingFunctionSchema.
215
+ // Blocking preferences: unique rotations; every committer id (base commit) and every no-block id (per side) keys
216
+ // an on-court starter (the UI only offers front-row players, the schema checks starters). The commit function is
217
+ // validated by commitFunctionSchema.
219
218
  const validateBlockingPreferences = (preferences, path) => {
220
219
  const seenRotations = new Set();
221
220
  for (const pref of preferences) {
@@ -223,8 +222,13 @@ exports.TacticsInputSchema = zod_1.z.object({
223
222
  ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_DUPLICATE_ROTATION', path });
224
223
  }
225
224
  seenRotations.add(pref.rotation);
225
+ for (const id of Object.keys(pref.commit)) {
226
+ if (!starterIds.has(id)) {
227
+ ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER', path });
228
+ }
229
+ }
226
230
  for (const form of ['DEF_L', 'DEF_M', 'DEF_R']) {
227
- for (const id of Object.keys(pref.targets[form] ?? {})) {
231
+ for (const id of pref.noBlock[form] ?? []) {
228
232
  if (!starterIds.has(id)) {
229
233
  ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER', path });
230
234
  }
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const globals_1 = require("@jest/globals");
4
4
  const tactics_z_1 = require("./tactics.z");
5
- const blocking_preference_1 = require("../blocking-preference");
6
5
  const player_1 = require("../../player");
7
6
  const test_helpers_1 = require("../../test-helpers");
8
7
  (0, globals_1.describe)('TacticsInputSchema — second libero rules', () => {
@@ -236,40 +235,49 @@ const test_helpers_1 = require("../../test-helpers");
236
235
  ...overrides
237
236
  };
238
237
  }
239
- (0, globals_1.it)('accepts a blocking preference that keys starters to attacking functions, per defence side', () => {
238
+ (0, globals_1.it)('accepts a blocking preference that commits starters to attacking functions', () => {
240
239
  const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
241
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: player_1.RoleEnum.OUTSIDE_HITTER }, DEF_M: {}, DEF_R: { [p3.id]: player_1.RoleEnum.MIDDLE_BLOCKER } } }]
240
+ blockingPreferences: [{ rotation: 1, commit: { [p4.id]: player_1.RoleEnum.OUTSIDE_HITTER, [p3.id]: player_1.RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_L: [], DEF_M: [], DEF_R: [] } }]
242
241
  }));
243
242
  (0, globals_1.expect)(res.success).toBe(true);
244
243
  if (res.success) {
245
- (0, globals_1.expect)(res.data.blockingPreferences[0].targets.DEF_L[p4.id]).toBe(player_1.RoleEnum.OUTSIDE_HITTER);
246
- (0, globals_1.expect)(res.data.blockingPreferences[0].targets.DEF_R[p3.id]).toBe(player_1.RoleEnum.MIDDLE_BLOCKER);
247
- (0, globals_1.expect)(res.data.blockingPreferences[0].targets.DEF_M).toEqual({}); // the middle side is independent (empty here)
244
+ (0, globals_1.expect)(res.data.blockingPreferences[0].commit[p4.id]).toBe(player_1.RoleEnum.OUTSIDE_HITTER);
245
+ (0, globals_1.expect)(res.data.blockingPreferences[0].commit[p3.id]).toBe(player_1.RoleEnum.MIDDLE_BLOCKER);
248
246
  }
249
247
  });
250
- (0, globals_1.it)('defaults the omitted defence sides to empty maps', () => {
248
+ (0, globals_1.it)('defaults an omitted commit to empty and omitted no-block sides to empty arrays', () => {
251
249
  const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
252
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: blocking_preference_1.NO_BLOCK } } }]
250
+ blockingPreferences: [{ rotation: 1, noBlock: { DEF_L: [p4.id] } }]
253
251
  }));
254
252
  (0, globals_1.expect)(res.success).toBe(true);
255
253
  if (res.success) {
256
- (0, globals_1.expect)(res.data.blockingPreferences[0].targets.DEF_L[p4.id]).toBe(blocking_preference_1.NO_BLOCK);
257
- (0, globals_1.expect)(res.data.blockingPreferences[0].targets.DEF_M).toEqual({});
258
- (0, globals_1.expect)(res.data.blockingPreferences[0].targets.DEF_R).toEqual({});
254
+ (0, globals_1.expect)(res.data.blockingPreferences[0].commit).toEqual({});
255
+ (0, globals_1.expect)(res.data.blockingPreferences[0].noBlock.DEF_L).toEqual([p4.id]);
256
+ (0, globals_1.expect)(res.data.blockingPreferences[0].noBlock.DEF_M).toEqual([]);
257
+ (0, globals_1.expect)(res.data.blockingPreferences[0].noBlock.DEF_R).toEqual([]);
259
258
  }
260
259
  });
261
- (0, globals_1.it)('rejects a blocking target (on any side) that is not a starter', () => {
260
+ (0, globals_1.it)('rejects a commit id that is not a starter', () => {
262
261
  const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
263
- blockingPreferences: [{ rotation: 1, targets: { DEF_M: { [reserve.id]: player_1.RoleEnum.OUTSIDE_HITTER } } }]
262
+ blockingPreferences: [{ rotation: 1, commit: { [reserve.id]: player_1.RoleEnum.OUTSIDE_HITTER } }]
264
263
  }));
265
264
  (0, globals_1.expect)(res.success).toBe(false);
266
265
  if (!res.success) {
267
266
  (0, globals_1.expect)(res.error.issues.some(i => i.message === 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER')).toBe(true);
268
267
  }
269
268
  });
270
- (0, globals_1.it)('rejects a blocking target function of libero', () => {
269
+ (0, globals_1.it)('rejects a no-block id (on any side) that is not a starter', () => {
271
270
  const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
272
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: player_1.RoleEnum.LIBERO } } }]
271
+ blockingPreferences: [{ rotation: 1, noBlock: { DEF_M: [reserve.id] } }]
272
+ }));
273
+ (0, globals_1.expect)(res.success).toBe(false);
274
+ if (!res.success) {
275
+ (0, globals_1.expect)(res.error.issues.some(i => i.message === 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER')).toBe(true);
276
+ }
277
+ });
278
+ (0, globals_1.it)('rejects a commit function of libero', () => {
279
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
280
+ blockingPreferences: [{ rotation: 1, commit: { [p4.id]: player_1.RoleEnum.LIBERO } }]
273
281
  }));
274
282
  (0, globals_1.expect)(res.success).toBe(false);
275
283
  if (!res.success) {
@@ -279,8 +287,8 @@ const test_helpers_1 = require("../../test-helpers");
279
287
  (0, globals_1.it)('rejects a duplicate rotation in blocking preferences', () => {
280
288
  const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
281
289
  blockingPreferences: [
282
- { rotation: 1, targets: { DEF_L: { [p4.id]: player_1.RoleEnum.OUTSIDE_HITTER } } },
283
- { rotation: 1, targets: { DEF_M: { [p3.id]: player_1.RoleEnum.MIDDLE_BLOCKER } } }
290
+ { rotation: 1, commit: { [p4.id]: player_1.RoleEnum.OUTSIDE_HITTER } },
291
+ { rotation: 1, commit: { [p3.id]: player_1.RoleEnum.MIDDLE_BLOCKER } }
284
292
  ]
285
293
  }));
286
294
  (0, globals_1.expect)(res.success).toBe(false);
@@ -290,38 +298,29 @@ const test_helpers_1 = require("../../test-helpers");
290
298
  });
291
299
  (0, globals_1.it)('accepts per-set blocking preference overrides', () => {
292
300
  const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
293
- blockingPreferenceSets: [[{ rotation: 1, targets: { DEF_R: { [p4.id]: player_1.RoleEnum.OPPOSITE_HITTER } } }], []]
301
+ blockingPreferenceSets: [[{ rotation: 1, commit: { [p4.id]: player_1.RoleEnum.OPPOSITE_HITTER } }], []]
294
302
  }));
295
303
  (0, globals_1.expect)(res.success).toBe(true);
296
304
  });
297
- (0, globals_1.it)('accepts NO_BLOCK as an assignment, alongside a commit on another starter, on the same side', () => {
305
+ (0, globals_1.it)('accepts a starter committed on the base AND no-blocked on a side (no-block overrides commit there)', () => {
298
306
  const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
299
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: blocking_preference_1.NO_BLOCK, [p3.id]: player_1.RoleEnum.MIDDLE_BLOCKER } } }]
307
+ blockingPreferences: [{ rotation: 1, commit: { [p3.id]: player_1.RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_L: [p3.id] } }]
300
308
  }));
301
309
  (0, globals_1.expect)(res.success).toBe(true);
302
310
  if (res.success) {
303
- (0, globals_1.expect)(res.data.blockingPreferences[0].targets.DEF_L[p4.id]).toBe(blocking_preference_1.NO_BLOCK);
304
- (0, globals_1.expect)(res.data.blockingPreferences[0].targets.DEF_L[p3.id]).toBe(player_1.RoleEnum.MIDDLE_BLOCKER);
311
+ (0, globals_1.expect)(res.data.blockingPreferences[0].commit[p3.id]).toBe(player_1.RoleEnum.MIDDLE_BLOCKER);
312
+ (0, globals_1.expect)(res.data.blockingPreferences[0].noBlock.DEF_L).toEqual([p3.id]);
305
313
  }
306
314
  });
307
- (0, globals_1.it)('accepts NO_BLOCK in a per-set override', () => {
315
+ (0, globals_1.it)('accepts a no-block in a per-set override', () => {
308
316
  const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
309
- blockingPreferenceSets: [[{ rotation: 1, targets: { DEF_M: { [p4.id]: blocking_preference_1.NO_BLOCK } } }], []]
317
+ blockingPreferenceSets: [[{ rotation: 1, noBlock: { DEF_M: [p4.id] } }], []]
310
318
  }));
311
319
  (0, globals_1.expect)(res.success).toBe(true);
312
320
  });
313
- (0, globals_1.it)('still rejects a NO_BLOCK assignment on a player who is not a starter', () => {
314
- const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
315
- blockingPreferences: [{ rotation: 1, targets: { DEF_R: { [reserve.id]: blocking_preference_1.NO_BLOCK } } }]
316
- }));
317
- (0, globals_1.expect)(res.success).toBe(false);
318
- if (!res.success) {
319
- (0, globals_1.expect)(res.error.issues.some(i => i.message === 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER')).toBe(true);
320
- }
321
- });
322
- (0, globals_1.it)('rejects an unknown assignment string', () => {
321
+ (0, globals_1.it)('rejects an unknown commit function string', () => {
323
322
  const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
324
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: 'NO_BLOCKING' } } }]
323
+ blockingPreferences: [{ rotation: 1, commit: { [p4.id]: 'NOT_A_ROLE' } }]
325
324
  }));
326
325
  (0, globals_1.expect)(res.success).toBe(false);
327
326
  });
@@ -290,19 +290,21 @@ export declare const TeamInputSchema: z.ZodObject<{
290
290
  }, z.core.$strip>>>>;
291
291
  blockingPreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
292
292
  rotation: z.ZodNumber;
293
- targets: z.ZodObject<{
294
- DEF_L: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
295
- DEF_M: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
296
- DEF_R: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
297
- }, z.core.$strip>;
293
+ commit: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>>>;
294
+ noBlock: z.ZodDefault<z.ZodObject<{
295
+ DEF_L: z.ZodDefault<z.ZodArray<z.ZodString>>;
296
+ DEF_M: z.ZodDefault<z.ZodArray<z.ZodString>>;
297
+ DEF_R: z.ZodDefault<z.ZodArray<z.ZodString>>;
298
+ }, z.core.$strip>>;
298
299
  }, z.core.$strip>>>;
299
300
  blockingPreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
300
301
  rotation: z.ZodNumber;
301
- targets: z.ZodObject<{
302
- DEF_L: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
303
- DEF_M: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
304
- DEF_R: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
305
- }, z.core.$strip>;
302
+ commit: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>>>;
303
+ noBlock: z.ZodDefault<z.ZodObject<{
304
+ DEF_L: z.ZodDefault<z.ZodArray<z.ZodString>>;
305
+ DEF_M: z.ZodDefault<z.ZodArray<z.ZodString>>;
306
+ DEF_R: z.ZodDefault<z.ZodArray<z.ZodString>>;
307
+ }, z.core.$strip>>;
306
308
  }, z.core.$strip>>>>;
307
309
  basePositions: z.ZodOptional<z.ZodObject<{
308
310
  coords: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -1,4 +1,4 @@
1
- import { BaseConfig, BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, PointBreakdown, RallyMetrics, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, BlockingAssignment, DefenceFormation, CourtPosition, ConditionLogic, EnergyBand, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand } from '../service';
1
+ import { BaseConfig, BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, PointBreakdown, RallyMetrics, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, DefenceFormation, LineupFunction, CourtPosition, ConditionLogic, EnergyBand, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand } from '../service';
2
2
  export * from './tactics-player-refs';
3
3
  export type Rally = DataProps<_Rally> & {
4
4
  homePlayerPosition: PlayerPosition[];
@@ -102,7 +102,8 @@ export interface ApiOffensivePreference {
102
102
  }
103
103
  export interface ApiBlockingPreference {
104
104
  rotation: number;
105
- targets: Record<DefenceFormation, Record<string, BlockingAssignment>>;
105
+ commit: Record<string, LineupFunction>;
106
+ noBlock: Record<DefenceFormation, string[]>;
106
107
  }
107
108
  export interface ApiSystemSet {
108
109
  rotationSystem: RotationSystemEnum;
@@ -2,8 +2,8 @@ import type { Tactics } from '.';
2
2
  /**
3
3
  * Every player id a tactics config references, anywhere: the six court slots and the libero, the bench, libero
4
4
  * replacements (flat and per-set), the second libero, designated subs and their per-set overrides, designated
5
- * setters (flat and per-set), the keys of the offensive-weight and blocking-target maps, injury replacements
6
- * and the libero-injury reserves.
5
+ * setters (flat and per-set), the keys of the offensive-weight map, the blocking commit + no-block ids, injury
6
+ * replacements and the libero-injury reserves.
7
7
  *
8
8
  * There is exactly ONE implementation of this on purpose. It backs three rules that must agree or a player
9
9
  * slips through: the dismiss/retire guard (a referenced player cannot be removed), the event rarity check
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Every player id a tactics config references, anywhere: the six court slots and the libero, the bench, libero
3
3
  * replacements (flat and per-set), the second libero, designated subs and their per-set overrides, designated
4
- * setters (flat and per-set), the keys of the offensive-weight and blocking-target maps, injury replacements
5
- * and the libero-injury reserves.
4
+ * setters (flat and per-set), the keys of the offensive-weight map, the blocking commit + no-block ids, injury
5
+ * replacements and the libero-injury reserves.
6
6
  *
7
7
  * There is exactly ONE implementation of this on purpose. It backs three rules that must agree or a player
8
8
  * slips through: the dismiss/retire guard (a referenced player cannot be removed), the event rarity check
@@ -26,8 +26,8 @@ export function collectTacticsPlayerIds(config) {
26
26
  ...((config.offensivePreferences ?? []).flatMap(p => Object.keys(p.weights))),
27
27
  ...((config.systemSets ?? []).flatMap(s => s.designatedSetters)),
28
28
  ...((config.offensivePreferenceSets ?? []).flat().flatMap(p => Object.keys(p.weights))),
29
- ...((config.blockingPreferences ?? []).flatMap(p => Object.values(p.targets).flatMap(m => Object.keys(m ?? {})))),
30
- ...((config.blockingPreferenceSets ?? []).flat().flatMap(p => Object.values(p.targets).flatMap(m => Object.keys(m ?? {})))),
29
+ ...((config.blockingPreferences ?? []).flatMap(p => [...Object.keys(p.commit ?? {}), ...Object.values(p.noBlock ?? {}).flat()])),
30
+ ...((config.blockingPreferenceSets ?? []).flat().flatMap(p => [...Object.keys(p.commit ?? {}), ...Object.values(p.noBlock ?? {}).flat()])),
31
31
  // Injury replacements reference reserves (roster players outside the lineup); roster membership is the only
32
32
  // player check possible here (the schema enforces the bench/reserve role rules).
33
33
  ...((config.injuryReplacements ?? []).flatMap(ir => [ir.starterId, ir.replacementId, ir.benchBackfillId])),
@@ -1,7 +1,7 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model, Optional } from 'sequelize';
3
3
  import { PlayerId, TeamId, TeamModel } from '.';
4
- import { BaseConfig, BlockingAssignment, ConditionLogic, CourtPosition, DefenceFormation, EnergyBand, PinchCondition, RotationSystemEnum, SubBackMode, SubBand, SubMode } from '../../service';
4
+ import { BaseConfig, ConditionLogic, CourtPosition, DefenceFormation, EnergyBand, LineupFunction, PinchCondition, RotationSystemEnum, SubBackMode, SubBand, SubMode } from '../../service';
5
5
  export interface TacticsLineupAttributes {
6
6
  [CourtPosition.LIBERO_ZONE]?: PlayerId;
7
7
  [CourtPosition.LEFT_BACK]: PlayerId;
@@ -36,7 +36,8 @@ export interface OffensivePreferenceAttributes {
36
36
  }
37
37
  export interface BlockingPreferenceAttributes {
38
38
  rotation: number;
39
- targets: Record<DefenceFormation, Record<PlayerId, BlockingAssignment>>;
39
+ commit: Record<PlayerId, LineupFunction>;
40
+ noBlock: Record<DefenceFormation, PlayerId[]>;
40
41
  }
41
42
  export interface SystemSetAttributes {
42
43
  rotationSystem: RotationSystemEnum;
@@ -81,8 +81,8 @@ function transformToAttributes(tactics, teamId) {
81
81
  }))),
82
82
  serve_attack_shares: tactics.serveAttackShares ?? {},
83
83
  base_positions: tactics.basePositions,
84
- blocking_preferences: tactics.blockingPreferences.map(p => ({ rotation: p.rotation, targets: p.targets })),
85
- blocking_preference_sets: tactics.blockingPreferenceSets?.map(prefs => prefs.map(p => ({ rotation: p.rotation, targets: p.targets }))),
84
+ blocking_preferences: tactics.blockingPreferences.map(p => ({ rotation: p.rotation, commit: p.commit, noBlock: p.noBlock })),
85
+ blocking_preference_sets: tactics.blockingPreferenceSets?.map(prefs => prefs.map(p => ({ rotation: p.rotation, commit: p.commit, noBlock: p.noBlock }))),
86
86
  replace_knocked_immediately: tactics.replaceKnockedImmediately,
87
87
  injury_replacements: tactics.injuryReplacements.map(ir => ({
88
88
  starterId: ir.starter.id,
@@ -203,8 +203,8 @@ function transformToObject(model, roster) {
203
203
  return healed;
204
204
  })(),
205
205
  basePositions: model.base_positions ?? undefined,
206
- blockingPreferences: (model.blocking_preferences ?? []).map(p => ({ rotation: p.rotation, targets: p.targets })),
207
- blockingPreferenceSets: model.blocking_preference_sets?.map(prefs => prefs.map(p => ({ rotation: p.rotation, targets: p.targets }))),
206
+ blockingPreferences: (model.blocking_preferences ?? []).map(p => ({ rotation: p.rotation, commit: p.commit, noBlock: p.noBlock })),
207
+ blockingPreferenceSets: model.blocking_preference_sets?.map(prefs => prefs.map(p => ({ rotation: p.rotation, commit: p.commit, noBlock: p.noBlock }))),
208
208
  // LENIENT: keep only boolean entries keyed by a court starter or, when a second libero exists, the starting
209
209
  // libero (the libero knock toggle is an L2-only feature, owner 2026-07-06) so stale jsonb (an ex-starter's
210
210
  // key, a non-boolean value, a libero key left after the L2 was removed) can never fail schema validation
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from '@jest/globals';
2
2
  import { tacticsColumnsToApiDto, transformFromTactics, transformToTactics } from './tactics';
3
- import { CourtPosition, EnergyBand, NO_BLOCK, RoleEnum, RotationSystemEnum, Tactics } from '../../service';
3
+ import { CourtPosition, EnergyBand, RoleEnum, RotationSystemEnum, Tactics } from '../../service';
4
4
  import { makeCountry, makePlayer } from '../../service/test-helpers';
5
5
  // Minimal TacticsModel-shaped object; cast through unknown since the transformer only reads columns.
6
6
  function makeTactics(designatedSubs) {
@@ -262,37 +262,39 @@ describe('per-set tactics — service <-> row round-trip', () => {
262
262
  const back = transformToTactics(attrs, roster);
263
263
  expect(back.offensivePreferences[0].tempos).toEqual({ [p2.id]: 'FAST', [p3.id]: 'HIGH' });
264
264
  });
265
- it('round-trips blocking preferences as per-defence-side id-keyed function maps', () => {
265
+ it('round-trips blocking preferences as a base commit map plus per-side no-block ids', () => {
266
266
  const withBlocking = Tactics.create({
267
267
  lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
268
268
  liberoReplacements: [],
269
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: RoleEnum.OUTSIDE_HITTER }, DEF_R: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } } }]
269
+ blockingPreferences: [{ rotation: 1, commit: { [p4.id]: RoleEnum.OUTSIDE_HITTER, [p3.id]: RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_L: [p2.id] } }]
270
270
  });
271
271
  const attrs = transformFromTactics(withBlocking, 'team-1');
272
272
  expect(attrs.blocking_preferences).toEqual([
273
- { rotation: 1, targets: { DEF_L: { [p4.id]: RoleEnum.OUTSIDE_HITTER }, DEF_M: {}, DEF_R: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } } }
273
+ { rotation: 1, commit: { [p4.id]: RoleEnum.OUTSIDE_HITTER, [p3.id]: RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_L: [p2.id], DEF_M: [], DEF_R: [] } }
274
274
  ]);
275
275
  const back = transformToTactics(attrs, roster);
276
- expect(back.blockingPreferences[0].targets).toEqual({ DEF_L: { [p4.id]: RoleEnum.OUTSIDE_HITTER }, DEF_M: {}, DEF_R: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } });
276
+ expect(back.blockingPreferences[0].commit).toEqual({ [p4.id]: RoleEnum.OUTSIDE_HITTER, [p3.id]: RoleEnum.MIDDLE_BLOCKER });
277
+ expect(back.blockingPreferences[0].noBlock).toEqual({ DEF_L: [p2.id], DEF_M: [], DEF_R: [] });
277
278
  });
278
- // NO_BLOCK rides in the same targets map as a commit assignment, so the transformers copy it wholesale with no
279
- // mapping code. This pins that: the sentinel must survive the row round-trip verbatim, mixed with a commit, and
280
- // in the per-set overrides. If it ever gets translated or dropped, a player silently starts blocking again.
281
- it('round-trips a NO_BLOCK assignment, mixed with a commit and in per-set overrides', () => {
279
+ // The base commit map (blind) and the per-side no-block ids are copied wholesale by the transformers with no
280
+ // mapping code. This pins that they survive the row round-trip verbatim, including in the per-set overrides. If a
281
+ // no-block id is ever translated or dropped, a player silently starts blocking again.
282
+ it('round-trips a base commit plus per-side no-block ids, including per-set overrides', () => {
282
283
  const withNoBlock = Tactics.create({
283
284
  lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
284
285
  liberoReplacements: [],
285
- blockingPreferences: [{ rotation: 1, targets: { DEF_M: { [p4.id]: NO_BLOCK, [p3.id]: RoleEnum.MIDDLE_BLOCKER } } }],
286
- blockingPreferenceSets: [[{ rotation: 2, targets: { DEF_L: { [p2.id]: NO_BLOCK } } }]]
286
+ blockingPreferences: [{ rotation: 1, commit: { [p3.id]: RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_M: [p4.id] } }],
287
+ blockingPreferenceSets: [[{ rotation: 2, noBlock: { DEF_L: [p2.id] } }]]
287
288
  });
288
289
  const attrs = transformFromTactics(withNoBlock, 'team-1');
289
290
  expect(attrs.blocking_preferences).toEqual([
290
- { rotation: 1, targets: { DEF_L: {}, DEF_M: { [p4.id]: NO_BLOCK, [p3.id]: RoleEnum.MIDDLE_BLOCKER }, DEF_R: {} } }
291
+ { rotation: 1, commit: { [p3.id]: RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_L: [], DEF_M: [p4.id], DEF_R: [] } }
291
292
  ]);
292
- expect(attrs.blocking_preference_sets).toEqual([[{ rotation: 2, targets: { DEF_L: { [p2.id]: NO_BLOCK }, DEF_M: {}, DEF_R: {} } }]]);
293
+ expect(attrs.blocking_preference_sets).toEqual([[{ rotation: 2, commit: {}, noBlock: { DEF_L: [p2.id], DEF_M: [], DEF_R: [] } }]]);
293
294
  const back = transformToTactics(attrs, roster);
294
- expect(back.blockingPreferences[0].targets).toEqual({ DEF_L: {}, DEF_M: { [p4.id]: NO_BLOCK, [p3.id]: RoleEnum.MIDDLE_BLOCKER }, DEF_R: {} });
295
- expect(back.blockingPreferenceSets?.[0][0].targets).toEqual({ DEF_L: { [p2.id]: NO_BLOCK }, DEF_M: {}, DEF_R: {} });
295
+ expect(back.blockingPreferences[0].commit).toEqual({ [p3.id]: RoleEnum.MIDDLE_BLOCKER });
296
+ expect(back.blockingPreferences[0].noBlock).toEqual({ DEF_L: [], DEF_M: [p4.id], DEF_R: [] });
297
+ expect(back.blockingPreferenceSets?.[0][0].noBlock).toEqual({ DEF_L: [p2.id], DEF_M: [], DEF_R: [] });
296
298
  });
297
299
  });
298
300
  describe('libero injuries — service <-> row round-trip + lenient heal', () => {
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { TraitEnum } from '../../player';
3
3
  import { EventIncidentSchema } from './incident.z';
4
4
  import { positionalEventFields } from './positional.z';
5
- const setTypeValues = [0, 1];
5
+ const setTypeValues = [0, 1, 2]; // OVERHAND, DUMP, BUMP
6
6
  const setFailureValues = [0, 1, 2, 3, 4];
7
7
  const setTempoValues = [0, 1, 2];
8
8
  const courtTargetValues = Array.from({ length: 13 }, (_, i) => i);
@@ -1,9 +1,10 @@
1
1
  import { InPlayEvent } from './in-play-event';
2
2
  export declare enum SetTypeEnum {
3
3
  OVERHAND = 0,
4
- DUMP = 1
4
+ DUMP = 1,
5
+ BUMP = 2
5
6
  }
6
- export type SetType = SetTypeEnum.OVERHAND | SetTypeEnum.DUMP;
7
+ export type SetType = SetTypeEnum.OVERHAND | SetTypeEnum.BUMP | SetTypeEnum.DUMP;
7
8
  export declare enum SetTempoEnum {
8
9
  FIRST = 0,
9
10
  SECOND = 1,
@@ -5,6 +5,10 @@ export var SetTypeEnum;
5
5
  (function (SetTypeEnum) {
6
6
  SetTypeEnum[SetTypeEnum["OVERHAND"] = 0] = "OVERHAND";
7
7
  SetTypeEnum[SetTypeEnum["DUMP"] = 1] = "DUMP";
8
+ // A forearm (bump) set, used when the pass is off the net (owner 2026-08-11). OVERHAND and BUMP are both normal
9
+ // sets and behave identically for metrics (assist, first-attack); they differ only by technique (which stat graded
10
+ // the set) and their play-by-play label. DUMP remains the setter's own second-ball attack.
11
+ SetTypeEnum[SetTypeEnum["BUMP"] = 2] = "BUMP";
8
12
  })(SetTypeEnum || (SetTypeEnum = {}));
9
13
  // The tempo of a set (set-tempo rework). Decided by the simulator (pass quality + target lane + setter skill)
10
14
  // or forced by a per-player offensive preference. FIRST = quick (swings the `quick` stat, hardest to nail,
@@ -1,9 +1,8 @@
1
1
  import { LineupFunction } from './lineup-function';
2
- export declare const NO_BLOCK: "NO_BLOCK";
3
- export type BlockingAssignment = LineupFunction | typeof NO_BLOCK;
4
2
  export type DefenceFormation = 'DEF_L' | 'DEF_M' | 'DEF_R';
5
3
  export declare const DEFENCE_FORMATIONS: readonly DefenceFormation[];
6
4
  export interface BlockingPreference {
7
5
  readonly rotation: number;
8
- readonly targets: Record<DefenceFormation, Record<string, BlockingAssignment>>;
6
+ readonly commit: Record<string, LineupFunction>;
7
+ readonly noBlock: Record<DefenceFormation, string[]>;
9
8
  }
@@ -1,7 +1 @@
1
- // A front-row player opted OUT of blocking for this rotation. They never join the wall under any circumstance:
2
- // not as the lead blocker (the lane's next-nearest front-row player leads instead), not as a side blocker, and
3
- // no trait can coerce them in -- MOVING_WALL's guaranteed lead hand, its beaten-read hand and its side-joiner
4
- // roll are all suppressed, as is a dedicated-blocking commit. Sits in the same `targets` map as a commit
5
- // assignment, so the two are mutually exclusive by construction: a player is reading, committed, or out.
6
- export const NO_BLOCK = 'NO_BLOCK';
7
1
  export const DEFENCE_FORMATIONS = ['DEF_L', 'DEF_M', 'DEF_R'];
@@ -282,19 +282,21 @@ export declare const TacticsInputSchema: z.ZodObject<{
282
282
  }, z.core.$strip>>>>;
283
283
  blockingPreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
284
284
  rotation: z.ZodNumber;
285
- targets: z.ZodObject<{
286
- DEF_L: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
287
- DEF_M: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
288
- DEF_R: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
289
- }, z.core.$strip>;
285
+ commit: z.ZodDefault<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>>>>;
286
+ noBlock: z.ZodDefault<z.ZodObject<{
287
+ DEF_L: z.ZodDefault<z.ZodArray<z.ZodString>>;
288
+ DEF_M: z.ZodDefault<z.ZodArray<z.ZodString>>;
289
+ DEF_R: z.ZodDefault<z.ZodArray<z.ZodString>>;
290
+ }, z.core.$strip>>;
290
291
  }, z.core.$strip>>>;
291
292
  blockingPreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
292
293
  rotation: z.ZodNumber;
293
- targets: z.ZodObject<{
294
- DEF_L: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
295
- DEF_M: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
296
- DEF_R: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [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>>, z.ZodLiteral<"NO_BLOCK">]>>>;
297
- }, z.core.$strip>;
294
+ commit: z.ZodDefault<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>>>>;
295
+ noBlock: z.ZodDefault<z.ZodObject<{
296
+ DEF_L: z.ZodDefault<z.ZodArray<z.ZodString>>;
297
+ DEF_M: z.ZodDefault<z.ZodArray<z.ZodString>>;
298
+ DEF_R: z.ZodDefault<z.ZodArray<z.ZodString>>;
299
+ }, z.core.$strip>>;
298
300
  }, z.core.$strip>>>>;
299
301
  basePositions: z.ZodOptional<z.ZodObject<{
300
302
  coords: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -1,6 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  import { Player, RoleEnum } from '../../player';
3
- import { NO_BLOCK } from '../blocking-preference';
4
3
  import { EnergyBand } from '../energy-band';
5
4
  import { RotationSystemEnum, setterCountFor } from '../rotation-system';
6
5
  import { DesignatedSubSchema, SubBandSchema } from './designated-sub.z';
@@ -57,24 +56,23 @@ const offensivePreferenceSchema = z.object({
57
56
  weights: z.record(z.string(), z.number().int().min(0)),
58
57
  tempos: z.record(z.string(), tempoPreferenceSchema).optional()
59
58
  });
60
- // What a front-row starter can be assigned: an opposing attacking function to commit to (every RoleEnum except
61
- // the libero, which never attacks), or NO_BLOCK to sit the rotation out of the block entirely.
62
- const blockingFunctionSchema = z.union([
63
- z.nativeEnum(RoleEnum).refine((v) => v !== RoleEnum.LIBERO, { message: 'BLOCKING_TARGET_CANNOT_BE_LIBERO' }),
64
- z.literal(NO_BLOCK)
65
- ]);
66
- // One rotation's dedicated-blocking assignments: `targets` maps my front-row starter ids to the opposing function
67
- // each commits to block. Cross-field checks (keys are starters) live in the superRefine below.
68
- const blockingTargetsSchema = z.record(z.string(), blockingFunctionSchema);
69
- // Per point-of-attack defence (owner 2026-08-06): a separate front-row commit map for an attack from my left /
70
- // middle / right. Each defaults to {} so a partial config stays valid.
59
+ // An opposing attacking function a front-row starter can commit to (every RoleEnum except the libero, which never
60
+ // attacks). The commit is the BLIND, pre-set Defence-base choice (owner 2026-08-10).
61
+ const commitFunctionSchema = z.nativeEnum(RoleEnum).refine((v) => v !== RoleEnum.LIBERO, { message: 'BLOCKING_TARGET_CANNOT_BE_LIBERO' });
62
+ // One rotation's blocking config (owner 2026-08-10). `commit` maps my front-row starter ids to the opposing function
63
+ // each commits to (blind, rotation-wide; absent = reads); `noBlock` lists, per point-of-attack defence, the front-row
64
+ // starters that sit out of the block for an attack from that side (overrides the commit for that side). Each side
65
+ // defaults to [] so a partial config stays valid. Cross-field checks (keys/ids are starters) live in the superRefine.
66
+ const commitSchema = z.record(z.string(), commitFunctionSchema);
67
+ const noBlockSchema = z.object({
68
+ DEF_L: z.array(z.string()).default([]),
69
+ DEF_M: z.array(z.string()).default([]),
70
+ DEF_R: z.array(z.string()).default([])
71
+ });
71
72
  const blockingPreferenceSchema = z.object({
72
73
  rotation: z.number().int().min(1).max(6),
73
- targets: z.object({
74
- DEF_L: blockingTargetsSchema.default({}),
75
- DEF_M: blockingTargetsSchema.default({}),
76
- DEF_R: blockingTargetsSchema.default({})
77
- })
74
+ commit: commitSchema.default({}),
75
+ noBlock: noBlockSchema.default({ DEF_L: [], DEF_M: [], DEF_R: [] })
78
76
  });
79
77
  // One set's rotation-system override (per-set tactics): system + its designated setters travel together.
80
78
  const systemSetSchema = z.object({
@@ -211,8 +209,9 @@ export const TacticsInputSchema = z.object({
211
209
  }
212
210
  }
213
211
  };
214
- // Blocking preferences: unique rotations; each assignment keys an on-court starter (the UI only offers front-row
215
- // players, the schema checks starters). The target function is validated by blockingFunctionSchema.
212
+ // Blocking preferences: unique rotations; every committer id (base commit) and every no-block id (per side) keys
213
+ // an on-court starter (the UI only offers front-row players, the schema checks starters). The commit function is
214
+ // validated by commitFunctionSchema.
216
215
  const validateBlockingPreferences = (preferences, path) => {
217
216
  const seenRotations = new Set();
218
217
  for (const pref of preferences) {
@@ -220,8 +219,13 @@ export const TacticsInputSchema = z.object({
220
219
  ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_DUPLICATE_ROTATION', path });
221
220
  }
222
221
  seenRotations.add(pref.rotation);
222
+ for (const id of Object.keys(pref.commit)) {
223
+ if (!starterIds.has(id)) {
224
+ ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER', path });
225
+ }
226
+ }
223
227
  for (const form of ['DEF_L', 'DEF_M', 'DEF_R']) {
224
- for (const id of Object.keys(pref.targets[form] ?? {})) {
228
+ for (const id of pref.noBlock[form] ?? []) {
225
229
  if (!starterIds.has(id)) {
226
230
  ctx.addIssue({ code: 'custom', message: 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER', path });
227
231
  }
@@ -1,6 +1,5 @@
1
1
  import { describe, it, expect } from '@jest/globals';
2
2
  import { TacticsInputSchema } from './tactics.z';
3
- import { NO_BLOCK } from '../blocking-preference';
4
3
  import { RoleEnum } from '../../player';
5
4
  import { makeCountry, makePlayer } from '../../test-helpers';
6
5
  describe('TacticsInputSchema — second libero rules', () => {
@@ -234,40 +233,49 @@ describe('TacticsInputSchema — blocking preferences', () => {
234
233
  ...overrides
235
234
  };
236
235
  }
237
- it('accepts a blocking preference that keys starters to attacking functions, per defence side', () => {
236
+ it('accepts a blocking preference that commits starters to attacking functions', () => {
238
237
  const res = TacticsInputSchema.safeParse(baseInput({
239
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: RoleEnum.OUTSIDE_HITTER }, DEF_M: {}, DEF_R: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } } }]
238
+ blockingPreferences: [{ rotation: 1, commit: { [p4.id]: RoleEnum.OUTSIDE_HITTER, [p3.id]: RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_L: [], DEF_M: [], DEF_R: [] } }]
240
239
  }));
241
240
  expect(res.success).toBe(true);
242
241
  if (res.success) {
243
- expect(res.data.blockingPreferences[0].targets.DEF_L[p4.id]).toBe(RoleEnum.OUTSIDE_HITTER);
244
- expect(res.data.blockingPreferences[0].targets.DEF_R[p3.id]).toBe(RoleEnum.MIDDLE_BLOCKER);
245
- expect(res.data.blockingPreferences[0].targets.DEF_M).toEqual({}); // the middle side is independent (empty here)
242
+ expect(res.data.blockingPreferences[0].commit[p4.id]).toBe(RoleEnum.OUTSIDE_HITTER);
243
+ expect(res.data.blockingPreferences[0].commit[p3.id]).toBe(RoleEnum.MIDDLE_BLOCKER);
246
244
  }
247
245
  });
248
- it('defaults the omitted defence sides to empty maps', () => {
246
+ it('defaults an omitted commit to empty and omitted no-block sides to empty arrays', () => {
249
247
  const res = TacticsInputSchema.safeParse(baseInput({
250
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: NO_BLOCK } } }]
248
+ blockingPreferences: [{ rotation: 1, noBlock: { DEF_L: [p4.id] } }]
251
249
  }));
252
250
  expect(res.success).toBe(true);
253
251
  if (res.success) {
254
- expect(res.data.blockingPreferences[0].targets.DEF_L[p4.id]).toBe(NO_BLOCK);
255
- expect(res.data.blockingPreferences[0].targets.DEF_M).toEqual({});
256
- expect(res.data.blockingPreferences[0].targets.DEF_R).toEqual({});
252
+ expect(res.data.blockingPreferences[0].commit).toEqual({});
253
+ expect(res.data.blockingPreferences[0].noBlock.DEF_L).toEqual([p4.id]);
254
+ expect(res.data.blockingPreferences[0].noBlock.DEF_M).toEqual([]);
255
+ expect(res.data.blockingPreferences[0].noBlock.DEF_R).toEqual([]);
257
256
  }
258
257
  });
259
- it('rejects a blocking target (on any side) that is not a starter', () => {
258
+ it('rejects a commit id that is not a starter', () => {
260
259
  const res = TacticsInputSchema.safeParse(baseInput({
261
- blockingPreferences: [{ rotation: 1, targets: { DEF_M: { [reserve.id]: RoleEnum.OUTSIDE_HITTER } } }]
260
+ blockingPreferences: [{ rotation: 1, commit: { [reserve.id]: RoleEnum.OUTSIDE_HITTER } }]
262
261
  }));
263
262
  expect(res.success).toBe(false);
264
263
  if (!res.success) {
265
264
  expect(res.error.issues.some(i => i.message === 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER')).toBe(true);
266
265
  }
267
266
  });
268
- it('rejects a blocking target function of libero', () => {
267
+ it('rejects a no-block id (on any side) that is not a starter', () => {
269
268
  const res = TacticsInputSchema.safeParse(baseInput({
270
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: RoleEnum.LIBERO } } }]
269
+ blockingPreferences: [{ rotation: 1, noBlock: { DEF_M: [reserve.id] } }]
270
+ }));
271
+ expect(res.success).toBe(false);
272
+ if (!res.success) {
273
+ expect(res.error.issues.some(i => i.message === 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER')).toBe(true);
274
+ }
275
+ });
276
+ it('rejects a commit function of libero', () => {
277
+ const res = TacticsInputSchema.safeParse(baseInput({
278
+ blockingPreferences: [{ rotation: 1, commit: { [p4.id]: RoleEnum.LIBERO } }]
271
279
  }));
272
280
  expect(res.success).toBe(false);
273
281
  if (!res.success) {
@@ -277,8 +285,8 @@ describe('TacticsInputSchema — blocking preferences', () => {
277
285
  it('rejects a duplicate rotation in blocking preferences', () => {
278
286
  const res = TacticsInputSchema.safeParse(baseInput({
279
287
  blockingPreferences: [
280
- { rotation: 1, targets: { DEF_L: { [p4.id]: RoleEnum.OUTSIDE_HITTER } } },
281
- { rotation: 1, targets: { DEF_M: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } } }
288
+ { rotation: 1, commit: { [p4.id]: RoleEnum.OUTSIDE_HITTER } },
289
+ { rotation: 1, commit: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } }
282
290
  ]
283
291
  }));
284
292
  expect(res.success).toBe(false);
@@ -288,38 +296,29 @@ describe('TacticsInputSchema — blocking preferences', () => {
288
296
  });
289
297
  it('accepts per-set blocking preference overrides', () => {
290
298
  const res = TacticsInputSchema.safeParse(baseInput({
291
- blockingPreferenceSets: [[{ rotation: 1, targets: { DEF_R: { [p4.id]: RoleEnum.OPPOSITE_HITTER } } }], []]
299
+ blockingPreferenceSets: [[{ rotation: 1, commit: { [p4.id]: RoleEnum.OPPOSITE_HITTER } }], []]
292
300
  }));
293
301
  expect(res.success).toBe(true);
294
302
  });
295
- it('accepts NO_BLOCK as an assignment, alongside a commit on another starter, on the same side', () => {
303
+ it('accepts a starter committed on the base AND no-blocked on a side (no-block overrides commit there)', () => {
296
304
  const res = TacticsInputSchema.safeParse(baseInput({
297
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: NO_BLOCK, [p3.id]: RoleEnum.MIDDLE_BLOCKER } } }]
305
+ blockingPreferences: [{ rotation: 1, commit: { [p3.id]: RoleEnum.MIDDLE_BLOCKER }, noBlock: { DEF_L: [p3.id] } }]
298
306
  }));
299
307
  expect(res.success).toBe(true);
300
308
  if (res.success) {
301
- expect(res.data.blockingPreferences[0].targets.DEF_L[p4.id]).toBe(NO_BLOCK);
302
- expect(res.data.blockingPreferences[0].targets.DEF_L[p3.id]).toBe(RoleEnum.MIDDLE_BLOCKER);
309
+ expect(res.data.blockingPreferences[0].commit[p3.id]).toBe(RoleEnum.MIDDLE_BLOCKER);
310
+ expect(res.data.blockingPreferences[0].noBlock.DEF_L).toEqual([p3.id]);
303
311
  }
304
312
  });
305
- it('accepts NO_BLOCK in a per-set override', () => {
313
+ it('accepts a no-block in a per-set override', () => {
306
314
  const res = TacticsInputSchema.safeParse(baseInput({
307
- blockingPreferenceSets: [[{ rotation: 1, targets: { DEF_M: { [p4.id]: NO_BLOCK } } }], []]
315
+ blockingPreferenceSets: [[{ rotation: 1, noBlock: { DEF_M: [p4.id] } }], []]
308
316
  }));
309
317
  expect(res.success).toBe(true);
310
318
  });
311
- it('still rejects a NO_BLOCK assignment on a player who is not a starter', () => {
312
- const res = TacticsInputSchema.safeParse(baseInput({
313
- blockingPreferences: [{ rotation: 1, targets: { DEF_R: { [reserve.id]: NO_BLOCK } } }]
314
- }));
315
- expect(res.success).toBe(false);
316
- if (!res.success) {
317
- expect(res.error.issues.some(i => i.message === 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER')).toBe(true);
318
- }
319
- });
320
- it('rejects an unknown assignment string', () => {
319
+ it('rejects an unknown commit function string', () => {
321
320
  const res = TacticsInputSchema.safeParse(baseInput({
322
- blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: 'NO_BLOCKING' } } }]
321
+ blockingPreferences: [{ rotation: 1, commit: { [p4.id]: 'NOT_A_ROLE' } }]
323
322
  }));
324
323
  expect(res.success).toBe(false);
325
324
  });
@@ -290,19 +290,21 @@ export declare const TeamInputSchema: z.ZodObject<{
290
290
  }, z.core.$strip>>>>;
291
291
  blockingPreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
292
292
  rotation: z.ZodNumber;
293
- targets: z.ZodObject<{
294
- DEF_L: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
295
- DEF_M: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
296
- DEF_R: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
297
- }, z.core.$strip>;
293
+ commit: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>>>;
294
+ noBlock: z.ZodDefault<z.ZodObject<{
295
+ DEF_L: z.ZodDefault<z.ZodArray<z.ZodString>>;
296
+ DEF_M: z.ZodDefault<z.ZodArray<z.ZodString>>;
297
+ DEF_R: z.ZodDefault<z.ZodArray<z.ZodString>>;
298
+ }, z.core.$strip>>;
298
299
  }, z.core.$strip>>>;
299
300
  blockingPreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
300
301
  rotation: z.ZodNumber;
301
- targets: z.ZodObject<{
302
- DEF_L: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
303
- DEF_M: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
304
- DEF_R: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>, z.ZodLiteral<"NO_BLOCK">]>>>;
305
- }, z.core.$strip>;
302
+ commit: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>>>;
303
+ noBlock: z.ZodDefault<z.ZodObject<{
304
+ DEF_L: z.ZodDefault<z.ZodArray<z.ZodString>>;
305
+ DEF_M: z.ZodDefault<z.ZodArray<z.ZodString>>;
306
+ DEF_R: z.ZodDefault<z.ZodArray<z.ZodString>>;
307
+ }, z.core.$strip>>;
306
308
  }, z.core.$strip>>>>;
307
309
  basePositions: z.ZodOptional<z.ZodObject<{
308
310
  coords: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodObject<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volleyballsimtypes",
3
- "version": "0.0.511",
3
+ "version": "0.0.513",
4
4
  "description": "vbsim types",
5
5
  "main": "./dist/cjs/src/index.js",
6
6
  "module": "./dist/esm/src/index.js",