volleyballsimtypes 0.0.396 → 0.0.398

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 (27) hide show
  1. package/dist/cjs/src/api/index.d.ts +4 -3
  2. package/dist/cjs/src/data/models/tactics.d.ts +4 -2
  3. package/dist/cjs/src/data/transformers/tactics.js +17 -3
  4. package/dist/cjs/src/service/team/designated-sub.d.ts +4 -3
  5. package/dist/cjs/src/service/team/energy-band.d.ts +2 -0
  6. package/dist/cjs/src/service/team/energy-band.js +2 -1
  7. package/dist/cjs/src/service/team/pinch-condition.d.ts +7 -2
  8. package/dist/cjs/src/service/team/pinch-condition.js +2 -1
  9. package/dist/cjs/src/service/team/schemas/designated-sub.z.d.ts +18 -2
  10. package/dist/cjs/src/service/team/schemas/designated-sub.z.js +8 -5
  11. package/dist/cjs/src/service/team/schemas/designated-sub.z.test.js +20 -6
  12. package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +10 -2
  13. package/dist/cjs/src/service/team/schemas/team.z.d.ts +10 -2
  14. package/dist/esm/src/api/index.d.ts +4 -3
  15. package/dist/esm/src/data/models/tactics.d.ts +4 -2
  16. package/dist/esm/src/data/transformers/tactics.js +17 -3
  17. package/dist/esm/src/service/team/designated-sub.d.ts +4 -3
  18. package/dist/esm/src/service/team/energy-band.d.ts +2 -0
  19. package/dist/esm/src/service/team/energy-band.js +1 -0
  20. package/dist/esm/src/service/team/pinch-condition.d.ts +7 -2
  21. package/dist/esm/src/service/team/pinch-condition.js +2 -1
  22. package/dist/esm/src/service/team/schemas/designated-sub.z.d.ts +18 -2
  23. package/dist/esm/src/service/team/schemas/designated-sub.z.js +7 -4
  24. package/dist/esm/src/service/team/schemas/designated-sub.z.test.js +20 -6
  25. package/dist/esm/src/service/team/schemas/tactics.z.d.ts +10 -2
  26. package/dist/esm/src/service/team/schemas/team.z.d.ts +10 -2
  27. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { 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, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, CourtPosition, ConditionLogic, PinchCondition, RotationSystemEnum, SubBand } from '../service';
1
+ import { 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, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, CourtPosition, ConditionLogic, EnergyBand, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../service';
2
2
  export type Rally = DataProps<_Rally> & {
3
3
  homePlayerPosition: PlayerPosition[];
4
4
  awayPlayerPosition: PlayerPosition[];
@@ -59,8 +59,9 @@ export interface StartingLineup {
59
59
  export interface ApiDesignatedSub {
60
60
  starterId: string;
61
61
  benchId?: string;
62
- isPinchServer: boolean;
63
- fatigueBand?: SubBand;
62
+ mode: SubMode;
63
+ fatigueBand?: EnergyBand;
64
+ fatigueBandsBySet?: EnergyBand[];
64
65
  conditions?: PinchCondition[];
65
66
  conditionLogic?: ConditionLogic;
66
67
  }
@@ -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 { ConditionLogic, CourtPosition, PinchCondition, RotationSystemEnum, SubBand } from '../../service';
4
+ import { ConditionLogic, CourtPosition, EnergyBand, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../../service';
5
5
  export interface TacticsLineupAttributes {
6
6
  [CourtPosition.LIBERO_ZONE]?: PlayerId;
7
7
  [CourtPosition.LEFT_BACK]: PlayerId;
@@ -16,8 +16,10 @@ export type PinchServerSubsAttributes = Record<PlayerId, PlayerId>;
16
16
  export interface DesignatedSubAttributes {
17
17
  starterId: PlayerId;
18
18
  benchId?: PlayerId;
19
- isPinchServer: boolean;
19
+ mode?: SubMode;
20
+ isPinchServer?: boolean;
20
21
  fatigueBand?: SubBand;
22
+ fatigueBandsBySet?: EnergyBand[];
21
23
  conditions?: PinchCondition[];
22
24
  conditionLogic?: ConditionLogic;
23
25
  }
@@ -3,6 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.transformToTactics = transformToObject;
4
4
  exports.transformFromTactics = transformToAttributes;
5
5
  const service_1 = require("../../service");
6
+ // Back-compat: new rows persist `mode`; rows written before the mode change persist `isPinchServer` (+ a
7
+ // 'NEVER' fatigueBand for the old "never fatigue-sub" state). Derive the mode so existing data keeps working
8
+ // until the next tactics save rewrites it in the new shape.
9
+ function subModeFromAttributes(d) {
10
+ if (d.mode != null)
11
+ return d.mode;
12
+ if (d.isPinchServer === true)
13
+ return 'PINCH';
14
+ if (d.fatigueBand === 'NEVER')
15
+ return 'NEVER';
16
+ return 'FATIGUE';
17
+ }
6
18
  function findPlayer(id, roster) {
7
19
  const player = roster.find((p) => p.id === id);
8
20
  if (player == null)
@@ -45,8 +57,9 @@ function transformToAttributes(tactics, teamId) {
45
57
  designated_subs: tactics.designatedSubs.map(ds => ({
46
58
  starterId: ds.starter.id,
47
59
  benchId: ds.bench?.id,
48
- isPinchServer: ds.isPinchServer,
60
+ mode: ds.mode,
49
61
  fatigueBand: ds.fatigueBand,
62
+ fatigueBandsBySet: ds.fatigueBandsBySet,
50
63
  conditions: ds.conditions,
51
64
  conditionLogic: ds.conditionLogic
52
65
  })),
@@ -76,8 +89,9 @@ function transformToObject(model, roster) {
76
89
  designatedSubs: (model.designated_subs ?? []).map(d => ({
77
90
  starter: findPlayer(d.starterId, roster),
78
91
  bench: d.benchId != null ? findPlayer(d.benchId, roster) : undefined,
79
- isPinchServer: d.isPinchServer,
80
- fatigueBand: d.fatigueBand,
92
+ mode: subModeFromAttributes(d),
93
+ fatigueBand: d.fatigueBand === 'NEVER' ? undefined : d.fatigueBand,
94
+ fatigueBandsBySet: d.fatigueBandsBySet,
81
95
  conditions: d.conditions,
82
96
  conditionLogic: d.conditionLogic
83
97
  })),
@@ -1,11 +1,12 @@
1
1
  import { Player } from '../player';
2
- import { SubBand } from './energy-band';
2
+ import { EnergyBand, SubMode } from './energy-band';
3
3
  import { ConditionLogic, PinchCondition } from './pinch-condition';
4
4
  export interface DesignatedSub {
5
5
  readonly starter: Player;
6
6
  readonly bench?: Player;
7
- readonly isPinchServer: boolean;
8
- readonly fatigueBand?: SubBand;
7
+ readonly mode: SubMode;
8
+ readonly fatigueBand?: EnergyBand;
9
+ readonly fatigueBandsBySet?: EnergyBand[];
9
10
  readonly conditions?: PinchCondition[];
10
11
  readonly conditionLogic?: ConditionLogic;
11
12
  }
@@ -5,4 +5,6 @@ export declare enum EnergyBand {
5
5
  EXHAUSTED = "EXHAUSTED"
6
6
  }
7
7
  export type SubBand = EnergyBand | 'NEVER';
8
+ export type SubMode = 'FATIGUE' | 'PINCH' | 'NEVER';
9
+ export declare const SUB_MODES: readonly SubMode[];
8
10
  export declare const ENERGY_BAND_ORDER: readonly EnergyBand[];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ENERGY_BAND_ORDER = exports.EnergyBand = void 0;
3
+ exports.ENERGY_BAND_ORDER = exports.SUB_MODES = exports.EnergyBand = void 0;
4
4
  // The named fatigue bands a player's energy falls into. The numeric cutoffs and per-band performance
5
5
  // multipliers live in the simulator (VolleyballSim court-player.ts ENERGY_BANDS); this enum is the shared
6
6
  // vocabulary used by tactics configuration (substitution thresholds) and, later, the UI.
@@ -11,6 +11,7 @@ var EnergyBand;
11
11
  EnergyBand["TIRED"] = "TIRED";
12
12
  EnergyBand["EXHAUSTED"] = "EXHAUSTED";
13
13
  })(EnergyBand || (exports.EnergyBand = EnergyBand = {}));
14
+ exports.SUB_MODES = ['FATIGUE', 'PINCH', 'NEVER'];
14
15
  // Ordered best -> worst. Used to compare "is this player at-or-below the configured sub band".
15
16
  exports.ENERGY_BAND_ORDER = [
16
17
  EnergyBand.ENERGETIC,
@@ -5,7 +5,8 @@ export declare enum PinchConditionType {
5
5
  SCORE_DIFF = "SCORE_DIFF",// team trailing/leading by at least `points`
6
6
  AFTER_ROTATIONS = "AFTER_ROTATIONS",// from the team's Nth serve turn in the set onward
7
7
  FROM_SET = "FROM_SET",// only set N or later
8
- MIN_OWN_SCORE = "MIN_OWN_SCORE"
8
+ MIN_OWN_SCORE = "MIN_OWN_SCORE",// only once own score >= N
9
+ MIN_OPPONENT_SCORE = "MIN_OPPONENT_SCORE"
9
10
  }
10
11
  export declare enum OpponentRelation {
11
12
  AHEAD = "AHEAD",
@@ -45,7 +46,11 @@ export interface MinOwnScoreCondition {
45
46
  readonly type: PinchConditionType.MIN_OWN_SCORE;
46
47
  readonly score: number;
47
48
  }
48
- export type PinchCondition = AsapCondition | TeamSetPointCondition | OpponentSetPointCondition | ScoreDiffCondition | AfterRotationsCondition | FromSetCondition | MinOwnScoreCondition;
49
+ export interface MinOpponentScoreCondition {
50
+ readonly type: PinchConditionType.MIN_OPPONENT_SCORE;
51
+ readonly score: number;
52
+ }
53
+ export type PinchCondition = AsapCondition | TeamSetPointCondition | OpponentSetPointCondition | ScoreDiffCondition | AfterRotationsCondition | FromSetCondition | MinOwnScoreCondition | MinOpponentScoreCondition;
49
54
  export type ConditionLogic = 'ALL' | 'ANY';
50
55
  export interface PinchSetState {
51
56
  readonly ownScore: number;
@@ -12,7 +12,8 @@ var PinchConditionType;
12
12
  PinchConditionType["SCORE_DIFF"] = "SCORE_DIFF";
13
13
  PinchConditionType["AFTER_ROTATIONS"] = "AFTER_ROTATIONS";
14
14
  PinchConditionType["FROM_SET"] = "FROM_SET";
15
- PinchConditionType["MIN_OWN_SCORE"] = "MIN_OWN_SCORE"; // only once own score >= N
15
+ PinchConditionType["MIN_OWN_SCORE"] = "MIN_OWN_SCORE";
16
+ PinchConditionType["MIN_OPPONENT_SCORE"] = "MIN_OPPONENT_SCORE"; // only once opponent score >= N
16
17
  })(PinchConditionType || (exports.PinchConditionType = PinchConditionType = {}));
17
18
  var OpponentRelation;
18
19
  (function (OpponentRelation) {
@@ -25,13 +25,26 @@ export declare const PinchConditionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
25
25
  }, z.core.$strip>, z.ZodObject<{
26
26
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
27
27
  score: z.ZodNumber;
28
+ }, z.core.$strip>, z.ZodObject<{
29
+ type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
30
+ score: z.ZodNumber;
28
31
  }, z.core.$strip>], "type">;
29
32
  export declare const ConditionLogicSchema: z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>;
33
+ export declare const SubModeSchema: z.ZodEnum<{
34
+ NEVER: "NEVER";
35
+ FATIGUE: "FATIGUE";
36
+ PINCH: "PINCH";
37
+ }>;
30
38
  export declare const DesignatedSubSchema: z.ZodObject<{
31
39
  starter: z.ZodCustom<Player, Player>;
32
40
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
33
- isPinchServer: z.ZodBoolean;
34
- fatigueBand: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<typeof EnergyBand>, z.ZodLiteral<"NEVER">]>>;
41
+ mode: z.ZodEnum<{
42
+ NEVER: "NEVER";
43
+ FATIGUE: "FATIGUE";
44
+ PINCH: "PINCH";
45
+ }>;
46
+ fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
47
+ fatigueBandsBySet: z.ZodOptional<z.ZodArray<z.ZodEnum<typeof EnergyBand>>>;
35
48
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
36
49
  type: z.ZodLiteral<PinchConditionType.ASAP>;
37
50
  }, z.core.$strip>, z.ZodObject<{
@@ -54,6 +67,9 @@ export declare const DesignatedSubSchema: z.ZodObject<{
54
67
  }, z.core.$strip>, z.ZodObject<{
55
68
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
56
69
  score: z.ZodNumber;
70
+ }, z.core.$strip>, z.ZodObject<{
71
+ type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
72
+ score: z.ZodNumber;
57
73
  }, z.core.$strip>], "type">>>;
58
74
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
59
75
  }, z.core.$strip>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DesignatedSubSchema = exports.ConditionLogicSchema = exports.PinchConditionSchema = exports.SubBandSchema = void 0;
3
+ exports.DesignatedSubSchema = exports.SubModeSchema = exports.ConditionLogicSchema = exports.PinchConditionSchema = exports.SubBandSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const player_1 = require("../../player");
6
6
  const energy_band_1 = require("../energy-band");
@@ -16,18 +16,21 @@ exports.PinchConditionSchema = zod_1.z.discriminatedUnion('type', [
16
16
  zod_1.z.object({ type: zod_1.z.literal(pinch_condition_1.PinchConditionType.SCORE_DIFF), direction: zod_1.z.nativeEnum(pinch_condition_1.ScoreDirection), points: zod_1.z.number().int().min(1).max(25) }),
17
17
  zod_1.z.object({ type: zod_1.z.literal(pinch_condition_1.PinchConditionType.AFTER_ROTATIONS), rotations: zod_1.z.number().int().min(1).max(6) }),
18
18
  zod_1.z.object({ type: zod_1.z.literal(pinch_condition_1.PinchConditionType.FROM_SET), set: zod_1.z.number().int().min(1).max(5) }),
19
- zod_1.z.object({ type: zod_1.z.literal(pinch_condition_1.PinchConditionType.MIN_OWN_SCORE), score: zod_1.z.number().int().min(0).max(40) })
19
+ zod_1.z.object({ type: zod_1.z.literal(pinch_condition_1.PinchConditionType.MIN_OWN_SCORE), score: zod_1.z.number().int().min(0).max(40) }),
20
+ zod_1.z.object({ type: zod_1.z.literal(pinch_condition_1.PinchConditionType.MIN_OPPONENT_SCORE), score: zod_1.z.number().int().min(0).max(40) })
20
21
  ]);
21
22
  exports.ConditionLogicSchema = zod_1.z.union([zod_1.z.literal('ALL'), zod_1.z.literal('ANY')]);
23
+ exports.SubModeSchema = zod_1.z.enum(['FATIGUE', 'PINCH', 'NEVER']);
22
24
  exports.DesignatedSubSchema = zod_1.z.object({
23
25
  starter: playerInstanceSchema,
24
26
  bench: playerInstanceSchema.optional(),
25
- isPinchServer: zod_1.z.boolean(),
26
- fatigueBand: exports.SubBandSchema.optional(),
27
+ mode: exports.SubModeSchema,
28
+ fatigueBand: zod_1.z.nativeEnum(energy_band_1.EnergyBand).optional(),
29
+ fatigueBandsBySet: zod_1.z.array(zod_1.z.nativeEnum(energy_band_1.EnergyBand)).max(5).optional(),
27
30
  conditions: zod_1.z.array(exports.PinchConditionSchema).optional(),
28
31
  conditionLogic: exports.ConditionLogicSchema.optional()
29
32
  }).superRefine((data, ctx) => {
30
- if (data.isPinchServer) {
33
+ if (data.mode === 'PINCH') {
31
34
  // Pinch mode needs a designated bench player (the server) and at least one condition.
32
35
  if (data.bench == null) {
33
36
  ctx.addIssue({ code: 'custom', message: 'PINCH_SERVER_REQUIRES_BENCH', path: ['bench'] });
@@ -67,7 +67,7 @@ const test_helpers_1 = require("../../test-helpers");
67
67
  const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
68
68
  starter,
69
69
  bench,
70
- isPinchServer: false,
70
+ mode: 'FATIGUE',
71
71
  fatigueBand: energy_band_1.EnergyBand.TIRED
72
72
  });
73
73
  (0, globals_1.expect)(res.success).toBe(true);
@@ -75,15 +75,29 @@ const test_helpers_1 = require("../../test-helpers");
75
75
  (0, globals_1.it)('accepts a fatigue-mode entry with no bench (falls back to closest sub)', () => {
76
76
  const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
77
77
  starter,
78
- isPinchServer: false
78
+ mode: 'FATIGUE'
79
79
  });
80
80
  (0, globals_1.expect)(res.success).toBe(true);
81
81
  });
82
+ (0, globals_1.it)('accepts a never-mode entry with no bench, band, or conditions', () => {
83
+ const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
84
+ starter,
85
+ mode: 'NEVER'
86
+ });
87
+ (0, globals_1.expect)(res.success).toBe(true);
88
+ });
89
+ (0, globals_1.it)('rejects an unknown mode', () => {
90
+ const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
91
+ starter,
92
+ mode: 'SOMETIMES'
93
+ });
94
+ (0, globals_1.expect)(res.success).toBe(false);
95
+ });
82
96
  (0, globals_1.it)('accepts a pinch-mode entry with a bench server and conditions', () => {
83
97
  const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
84
98
  starter,
85
99
  bench,
86
- isPinchServer: true,
100
+ mode: 'PINCH',
87
101
  conditions: [{ type: pinch_condition_1.PinchConditionType.ASAP }],
88
102
  conditionLogic: 'ALL'
89
103
  });
@@ -92,7 +106,7 @@ const test_helpers_1 = require("../../test-helpers");
92
106
  (0, globals_1.it)('rejects a pinch-mode entry without a bench server', () => {
93
107
  const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
94
108
  starter,
95
- isPinchServer: true,
109
+ mode: 'PINCH',
96
110
  conditions: [{ type: pinch_condition_1.PinchConditionType.ASAP }]
97
111
  });
98
112
  (0, globals_1.expect)(res.success).toBe(false);
@@ -104,7 +118,7 @@ const test_helpers_1 = require("../../test-helpers");
104
118
  const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
105
119
  starter,
106
120
  bench,
107
- isPinchServer: true,
121
+ mode: 'PINCH',
108
122
  conditions: []
109
123
  });
110
124
  (0, globals_1.expect)(res.success).toBe(false);
@@ -115,7 +129,7 @@ const test_helpers_1 = require("../../test-helpers");
115
129
  (0, globals_1.it)('rejects a starter that is not a Player instance', () => {
116
130
  const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
117
131
  starter: { id: 'not-a-player' },
118
- isPinchServer: false
132
+ mode: 'FATIGUE'
119
133
  });
120
134
  (0, globals_1.expect)(res.success).toBe(false);
121
135
  });
@@ -20,8 +20,13 @@ export declare const TacticsInputSchema: z.ZodObject<{
20
20
  designatedSubs: z.ZodDefault<z.ZodArray<z.ZodObject<{
21
21
  starter: z.ZodCustom<Player, Player>;
22
22
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
23
- isPinchServer: z.ZodBoolean;
24
- fatigueBand: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<typeof EnergyBand>, z.ZodLiteral<"NEVER">]>>;
23
+ mode: z.ZodEnum<{
24
+ NEVER: "NEVER";
25
+ FATIGUE: "FATIGUE";
26
+ PINCH: "PINCH";
27
+ }>;
28
+ fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
29
+ fatigueBandsBySet: z.ZodOptional<z.ZodArray<z.ZodEnum<typeof EnergyBand>>>;
25
30
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
26
31
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
27
32
  }, z.core.$strip>, z.ZodObject<{
@@ -44,6 +49,9 @@ export declare const TacticsInputSchema: z.ZodObject<{
44
49
  }, z.core.$strip>, z.ZodObject<{
45
50
  type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
46
51
  score: z.ZodNumber;
52
+ }, z.core.$strip>, z.ZodObject<{
53
+ type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
54
+ score: z.ZodNumber;
47
55
  }, z.core.$strip>], "type">>>;
48
56
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
49
57
  }, z.core.$strip>>>;
@@ -28,8 +28,13 @@ export declare const TeamInputSchema: z.ZodObject<{
28
28
  designatedSubs: z.ZodDefault<z.ZodArray<z.ZodObject<{
29
29
  starter: z.ZodCustom<Player, Player>;
30
30
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
31
- isPinchServer: z.ZodBoolean;
32
- fatigueBand: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<typeof import("..").EnergyBand>, z.ZodLiteral<"NEVER">]>>;
31
+ mode: z.ZodEnum<{
32
+ NEVER: "NEVER";
33
+ FATIGUE: "FATIGUE";
34
+ PINCH: "PINCH";
35
+ }>;
36
+ fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
37
+ fatigueBandsBySet: z.ZodOptional<z.ZodArray<z.ZodEnum<typeof import("..").EnergyBand>>>;
33
38
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
34
39
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
35
40
  }, z.core.$strip>, z.ZodObject<{
@@ -52,6 +57,9 @@ export declare const TeamInputSchema: z.ZodObject<{
52
57
  }, z.core.$strip>, z.ZodObject<{
53
58
  type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
54
59
  score: z.ZodNumber;
60
+ }, z.core.$strip>, z.ZodObject<{
61
+ type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
62
+ score: z.ZodNumber;
55
63
  }, z.core.$strip>], "type">>>;
56
64
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
57
65
  }, z.core.$strip>>>;
@@ -1,4 +1,4 @@
1
- import { 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, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, CourtPosition, ConditionLogic, PinchCondition, RotationSystemEnum, SubBand } from '../service';
1
+ import { 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, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, CourtPosition, ConditionLogic, EnergyBand, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../service';
2
2
  export type Rally = DataProps<_Rally> & {
3
3
  homePlayerPosition: PlayerPosition[];
4
4
  awayPlayerPosition: PlayerPosition[];
@@ -59,8 +59,9 @@ export interface StartingLineup {
59
59
  export interface ApiDesignatedSub {
60
60
  starterId: string;
61
61
  benchId?: string;
62
- isPinchServer: boolean;
63
- fatigueBand?: SubBand;
62
+ mode: SubMode;
63
+ fatigueBand?: EnergyBand;
64
+ fatigueBandsBySet?: EnergyBand[];
64
65
  conditions?: PinchCondition[];
65
66
  conditionLogic?: ConditionLogic;
66
67
  }
@@ -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 { ConditionLogic, CourtPosition, PinchCondition, RotationSystemEnum, SubBand } from '../../service';
4
+ import { ConditionLogic, CourtPosition, EnergyBand, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../../service';
5
5
  export interface TacticsLineupAttributes {
6
6
  [CourtPosition.LIBERO_ZONE]?: PlayerId;
7
7
  [CourtPosition.LEFT_BACK]: PlayerId;
@@ -16,8 +16,10 @@ export type PinchServerSubsAttributes = Record<PlayerId, PlayerId>;
16
16
  export interface DesignatedSubAttributes {
17
17
  starterId: PlayerId;
18
18
  benchId?: PlayerId;
19
- isPinchServer: boolean;
19
+ mode?: SubMode;
20
+ isPinchServer?: boolean;
20
21
  fatigueBand?: SubBand;
22
+ fatigueBandsBySet?: EnergyBand[];
21
23
  conditions?: PinchCondition[];
22
24
  conditionLogic?: ConditionLogic;
23
25
  }
@@ -1,4 +1,16 @@
1
1
  import { CourtPosition, Tactics } from '../../service';
2
+ // Back-compat: new rows persist `mode`; rows written before the mode change persist `isPinchServer` (+ a
3
+ // 'NEVER' fatigueBand for the old "never fatigue-sub" state). Derive the mode so existing data keeps working
4
+ // until the next tactics save rewrites it in the new shape.
5
+ function subModeFromAttributes(d) {
6
+ if (d.mode != null)
7
+ return d.mode;
8
+ if (d.isPinchServer === true)
9
+ return 'PINCH';
10
+ if (d.fatigueBand === 'NEVER')
11
+ return 'NEVER';
12
+ return 'FATIGUE';
13
+ }
2
14
  function findPlayer(id, roster) {
3
15
  const player = roster.find((p) => p.id === id);
4
16
  if (player == null)
@@ -41,8 +53,9 @@ function transformToAttributes(tactics, teamId) {
41
53
  designated_subs: tactics.designatedSubs.map(ds => ({
42
54
  starterId: ds.starter.id,
43
55
  benchId: ds.bench?.id,
44
- isPinchServer: ds.isPinchServer,
56
+ mode: ds.mode,
45
57
  fatigueBand: ds.fatigueBand,
58
+ fatigueBandsBySet: ds.fatigueBandsBySet,
46
59
  conditions: ds.conditions,
47
60
  conditionLogic: ds.conditionLogic
48
61
  })),
@@ -72,8 +85,9 @@ function transformToObject(model, roster) {
72
85
  designatedSubs: (model.designated_subs ?? []).map(d => ({
73
86
  starter: findPlayer(d.starterId, roster),
74
87
  bench: d.benchId != null ? findPlayer(d.benchId, roster) : undefined,
75
- isPinchServer: d.isPinchServer,
76
- fatigueBand: d.fatigueBand,
88
+ mode: subModeFromAttributes(d),
89
+ fatigueBand: d.fatigueBand === 'NEVER' ? undefined : d.fatigueBand,
90
+ fatigueBandsBySet: d.fatigueBandsBySet,
77
91
  conditions: d.conditions,
78
92
  conditionLogic: d.conditionLogic
79
93
  })),
@@ -1,11 +1,12 @@
1
1
  import { Player } from '../player';
2
- import { SubBand } from './energy-band';
2
+ import { EnergyBand, SubMode } from './energy-band';
3
3
  import { ConditionLogic, PinchCondition } from './pinch-condition';
4
4
  export interface DesignatedSub {
5
5
  readonly starter: Player;
6
6
  readonly bench?: Player;
7
- readonly isPinchServer: boolean;
8
- readonly fatigueBand?: SubBand;
7
+ readonly mode: SubMode;
8
+ readonly fatigueBand?: EnergyBand;
9
+ readonly fatigueBandsBySet?: EnergyBand[];
9
10
  readonly conditions?: PinchCondition[];
10
11
  readonly conditionLogic?: ConditionLogic;
11
12
  }
@@ -5,4 +5,6 @@ export declare enum EnergyBand {
5
5
  EXHAUSTED = "EXHAUSTED"
6
6
  }
7
7
  export type SubBand = EnergyBand | 'NEVER';
8
+ export type SubMode = 'FATIGUE' | 'PINCH' | 'NEVER';
9
+ export declare const SUB_MODES: readonly SubMode[];
8
10
  export declare const ENERGY_BAND_ORDER: readonly EnergyBand[];
@@ -8,6 +8,7 @@ export var EnergyBand;
8
8
  EnergyBand["TIRED"] = "TIRED";
9
9
  EnergyBand["EXHAUSTED"] = "EXHAUSTED";
10
10
  })(EnergyBand || (EnergyBand = {}));
11
+ export const SUB_MODES = ['FATIGUE', 'PINCH', 'NEVER'];
11
12
  // Ordered best -> worst. Used to compare "is this player at-or-below the configured sub band".
12
13
  export const ENERGY_BAND_ORDER = [
13
14
  EnergyBand.ENERGETIC,
@@ -5,7 +5,8 @@ export declare enum PinchConditionType {
5
5
  SCORE_DIFF = "SCORE_DIFF",// team trailing/leading by at least `points`
6
6
  AFTER_ROTATIONS = "AFTER_ROTATIONS",// from the team's Nth serve turn in the set onward
7
7
  FROM_SET = "FROM_SET",// only set N or later
8
- MIN_OWN_SCORE = "MIN_OWN_SCORE"
8
+ MIN_OWN_SCORE = "MIN_OWN_SCORE",// only once own score >= N
9
+ MIN_OPPONENT_SCORE = "MIN_OPPONENT_SCORE"
9
10
  }
10
11
  export declare enum OpponentRelation {
11
12
  AHEAD = "AHEAD",
@@ -45,7 +46,11 @@ export interface MinOwnScoreCondition {
45
46
  readonly type: PinchConditionType.MIN_OWN_SCORE;
46
47
  readonly score: number;
47
48
  }
48
- export type PinchCondition = AsapCondition | TeamSetPointCondition | OpponentSetPointCondition | ScoreDiffCondition | AfterRotationsCondition | FromSetCondition | MinOwnScoreCondition;
49
+ export interface MinOpponentScoreCondition {
50
+ readonly type: PinchConditionType.MIN_OPPONENT_SCORE;
51
+ readonly score: number;
52
+ }
53
+ export type PinchCondition = AsapCondition | TeamSetPointCondition | OpponentSetPointCondition | ScoreDiffCondition | AfterRotationsCondition | FromSetCondition | MinOwnScoreCondition | MinOpponentScoreCondition;
49
54
  export type ConditionLogic = 'ALL' | 'ANY';
50
55
  export interface PinchSetState {
51
56
  readonly ownScore: number;
@@ -9,7 +9,8 @@ export var PinchConditionType;
9
9
  PinchConditionType["SCORE_DIFF"] = "SCORE_DIFF";
10
10
  PinchConditionType["AFTER_ROTATIONS"] = "AFTER_ROTATIONS";
11
11
  PinchConditionType["FROM_SET"] = "FROM_SET";
12
- PinchConditionType["MIN_OWN_SCORE"] = "MIN_OWN_SCORE"; // only once own score >= N
12
+ PinchConditionType["MIN_OWN_SCORE"] = "MIN_OWN_SCORE";
13
+ PinchConditionType["MIN_OPPONENT_SCORE"] = "MIN_OPPONENT_SCORE"; // only once opponent score >= N
13
14
  })(PinchConditionType || (PinchConditionType = {}));
14
15
  export var OpponentRelation;
15
16
  (function (OpponentRelation) {
@@ -25,13 +25,26 @@ export declare const PinchConditionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
25
25
  }, z.core.$strip>, z.ZodObject<{
26
26
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
27
27
  score: z.ZodNumber;
28
+ }, z.core.$strip>, z.ZodObject<{
29
+ type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
30
+ score: z.ZodNumber;
28
31
  }, z.core.$strip>], "type">;
29
32
  export declare const ConditionLogicSchema: z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>;
33
+ export declare const SubModeSchema: z.ZodEnum<{
34
+ NEVER: "NEVER";
35
+ FATIGUE: "FATIGUE";
36
+ PINCH: "PINCH";
37
+ }>;
30
38
  export declare const DesignatedSubSchema: z.ZodObject<{
31
39
  starter: z.ZodCustom<Player, Player>;
32
40
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
33
- isPinchServer: z.ZodBoolean;
34
- fatigueBand: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<typeof EnergyBand>, z.ZodLiteral<"NEVER">]>>;
41
+ mode: z.ZodEnum<{
42
+ NEVER: "NEVER";
43
+ FATIGUE: "FATIGUE";
44
+ PINCH: "PINCH";
45
+ }>;
46
+ fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
47
+ fatigueBandsBySet: z.ZodOptional<z.ZodArray<z.ZodEnum<typeof EnergyBand>>>;
35
48
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
36
49
  type: z.ZodLiteral<PinchConditionType.ASAP>;
37
50
  }, z.core.$strip>, z.ZodObject<{
@@ -54,6 +67,9 @@ export declare const DesignatedSubSchema: z.ZodObject<{
54
67
  }, z.core.$strip>, z.ZodObject<{
55
68
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
56
69
  score: z.ZodNumber;
70
+ }, z.core.$strip>, z.ZodObject<{
71
+ type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
72
+ score: z.ZodNumber;
57
73
  }, z.core.$strip>], "type">>>;
58
74
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
59
75
  }, z.core.$strip>;
@@ -13,18 +13,21 @@ export const PinchConditionSchema = z.discriminatedUnion('type', [
13
13
  z.object({ type: z.literal(PinchConditionType.SCORE_DIFF), direction: z.nativeEnum(ScoreDirection), points: z.number().int().min(1).max(25) }),
14
14
  z.object({ type: z.literal(PinchConditionType.AFTER_ROTATIONS), rotations: z.number().int().min(1).max(6) }),
15
15
  z.object({ type: z.literal(PinchConditionType.FROM_SET), set: z.number().int().min(1).max(5) }),
16
- z.object({ type: z.literal(PinchConditionType.MIN_OWN_SCORE), score: z.number().int().min(0).max(40) })
16
+ z.object({ type: z.literal(PinchConditionType.MIN_OWN_SCORE), score: z.number().int().min(0).max(40) }),
17
+ z.object({ type: z.literal(PinchConditionType.MIN_OPPONENT_SCORE), score: z.number().int().min(0).max(40) })
17
18
  ]);
18
19
  export const ConditionLogicSchema = z.union([z.literal('ALL'), z.literal('ANY')]);
20
+ export const SubModeSchema = z.enum(['FATIGUE', 'PINCH', 'NEVER']);
19
21
  export const DesignatedSubSchema = z.object({
20
22
  starter: playerInstanceSchema,
21
23
  bench: playerInstanceSchema.optional(),
22
- isPinchServer: z.boolean(),
23
- fatigueBand: SubBandSchema.optional(),
24
+ mode: SubModeSchema,
25
+ fatigueBand: z.nativeEnum(EnergyBand).optional(),
26
+ fatigueBandsBySet: z.array(z.nativeEnum(EnergyBand)).max(5).optional(),
24
27
  conditions: z.array(PinchConditionSchema).optional(),
25
28
  conditionLogic: ConditionLogicSchema.optional()
26
29
  }).superRefine((data, ctx) => {
27
- if (data.isPinchServer) {
30
+ if (data.mode === 'PINCH') {
28
31
  // Pinch mode needs a designated bench player (the server) and at least one condition.
29
32
  if (data.bench == null) {
30
33
  ctx.addIssue({ code: 'custom', message: 'PINCH_SERVER_REQUIRES_BENCH', path: ['bench'] });
@@ -65,7 +65,7 @@ describe('DesignatedSubSchema', () => {
65
65
  const res = DesignatedSubSchema.safeParse({
66
66
  starter,
67
67
  bench,
68
- isPinchServer: false,
68
+ mode: 'FATIGUE',
69
69
  fatigueBand: EnergyBand.TIRED
70
70
  });
71
71
  expect(res.success).toBe(true);
@@ -73,15 +73,29 @@ describe('DesignatedSubSchema', () => {
73
73
  it('accepts a fatigue-mode entry with no bench (falls back to closest sub)', () => {
74
74
  const res = DesignatedSubSchema.safeParse({
75
75
  starter,
76
- isPinchServer: false
76
+ mode: 'FATIGUE'
77
77
  });
78
78
  expect(res.success).toBe(true);
79
79
  });
80
+ it('accepts a never-mode entry with no bench, band, or conditions', () => {
81
+ const res = DesignatedSubSchema.safeParse({
82
+ starter,
83
+ mode: 'NEVER'
84
+ });
85
+ expect(res.success).toBe(true);
86
+ });
87
+ it('rejects an unknown mode', () => {
88
+ const res = DesignatedSubSchema.safeParse({
89
+ starter,
90
+ mode: 'SOMETIMES'
91
+ });
92
+ expect(res.success).toBe(false);
93
+ });
80
94
  it('accepts a pinch-mode entry with a bench server and conditions', () => {
81
95
  const res = DesignatedSubSchema.safeParse({
82
96
  starter,
83
97
  bench,
84
- isPinchServer: true,
98
+ mode: 'PINCH',
85
99
  conditions: [{ type: PinchConditionType.ASAP }],
86
100
  conditionLogic: 'ALL'
87
101
  });
@@ -90,7 +104,7 @@ describe('DesignatedSubSchema', () => {
90
104
  it('rejects a pinch-mode entry without a bench server', () => {
91
105
  const res = DesignatedSubSchema.safeParse({
92
106
  starter,
93
- isPinchServer: true,
107
+ mode: 'PINCH',
94
108
  conditions: [{ type: PinchConditionType.ASAP }]
95
109
  });
96
110
  expect(res.success).toBe(false);
@@ -102,7 +116,7 @@ describe('DesignatedSubSchema', () => {
102
116
  const res = DesignatedSubSchema.safeParse({
103
117
  starter,
104
118
  bench,
105
- isPinchServer: true,
119
+ mode: 'PINCH',
106
120
  conditions: []
107
121
  });
108
122
  expect(res.success).toBe(false);
@@ -113,7 +127,7 @@ describe('DesignatedSubSchema', () => {
113
127
  it('rejects a starter that is not a Player instance', () => {
114
128
  const res = DesignatedSubSchema.safeParse({
115
129
  starter: { id: 'not-a-player' },
116
- isPinchServer: false
130
+ mode: 'FATIGUE'
117
131
  });
118
132
  expect(res.success).toBe(false);
119
133
  });
@@ -20,8 +20,13 @@ export declare const TacticsInputSchema: z.ZodObject<{
20
20
  designatedSubs: z.ZodDefault<z.ZodArray<z.ZodObject<{
21
21
  starter: z.ZodCustom<Player, Player>;
22
22
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
23
- isPinchServer: z.ZodBoolean;
24
- fatigueBand: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<typeof EnergyBand>, z.ZodLiteral<"NEVER">]>>;
23
+ mode: z.ZodEnum<{
24
+ NEVER: "NEVER";
25
+ FATIGUE: "FATIGUE";
26
+ PINCH: "PINCH";
27
+ }>;
28
+ fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
29
+ fatigueBandsBySet: z.ZodOptional<z.ZodArray<z.ZodEnum<typeof EnergyBand>>>;
25
30
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
26
31
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
27
32
  }, z.core.$strip>, z.ZodObject<{
@@ -44,6 +49,9 @@ export declare const TacticsInputSchema: z.ZodObject<{
44
49
  }, z.core.$strip>, z.ZodObject<{
45
50
  type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
46
51
  score: z.ZodNumber;
52
+ }, z.core.$strip>, z.ZodObject<{
53
+ type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
54
+ score: z.ZodNumber;
47
55
  }, z.core.$strip>], "type">>>;
48
56
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
49
57
  }, z.core.$strip>>>;
@@ -28,8 +28,13 @@ export declare const TeamInputSchema: z.ZodObject<{
28
28
  designatedSubs: z.ZodDefault<z.ZodArray<z.ZodObject<{
29
29
  starter: z.ZodCustom<Player, Player>;
30
30
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
31
- isPinchServer: z.ZodBoolean;
32
- fatigueBand: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<typeof import("..").EnergyBand>, z.ZodLiteral<"NEVER">]>>;
31
+ mode: z.ZodEnum<{
32
+ NEVER: "NEVER";
33
+ FATIGUE: "FATIGUE";
34
+ PINCH: "PINCH";
35
+ }>;
36
+ fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
37
+ fatigueBandsBySet: z.ZodOptional<z.ZodArray<z.ZodEnum<typeof import("..").EnergyBand>>>;
33
38
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
34
39
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
35
40
  }, z.core.$strip>, z.ZodObject<{
@@ -52,6 +57,9 @@ export declare const TeamInputSchema: z.ZodObject<{
52
57
  }, z.core.$strip>, z.ZodObject<{
53
58
  type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
54
59
  score: z.ZodNumber;
60
+ }, z.core.$strip>, z.ZodObject<{
61
+ type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
62
+ score: z.ZodNumber;
55
63
  }, z.core.$strip>], "type">>>;
56
64
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
57
65
  }, z.core.$strip>>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volleyballsimtypes",
3
- "version": "0.0.396",
3
+ "version": "0.0.398",
4
4
  "description": "vbsim types",
5
5
  "main": "./dist/cjs/src/index.js",
6
6
  "module": "./dist/esm/src/index.js",