volleyballsimtypes 0.0.462 → 0.0.464

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 (153) hide show
  1. package/dist/cjs/src/api/index.d.ts +14 -1
  2. package/dist/cjs/src/data/init-models.js +17 -0
  3. package/dist/cjs/src/data/models/currency-transaction.d.ts +4 -2
  4. package/dist/cjs/src/data/models/currency-transaction.js +2 -0
  5. package/dist/cjs/src/data/models/index.d.ts +3 -0
  6. package/dist/cjs/src/data/models/index.js +3 -0
  7. package/dist/cjs/src/data/models/match-preset.d.ts +28 -0
  8. package/dist/cjs/src/data/models/match-preset.js +65 -0
  9. package/dist/cjs/src/data/models/match-set.d.ts +2 -0
  10. package/dist/cjs/src/data/models/match-set.js +5 -0
  11. package/dist/cjs/src/data/models/match.d.ts +5 -2
  12. package/dist/cjs/src/data/models/match.js +4 -0
  13. package/dist/cjs/src/data/models/physician.d.ts +25 -0
  14. package/dist/cjs/src/data/models/physician.js +57 -0
  15. package/dist/cjs/src/data/models/physiotherapist.d.ts +25 -0
  16. package/dist/cjs/src/data/models/physiotherapist.js +57 -0
  17. package/dist/cjs/src/data/models/player.d.ts +8 -0
  18. package/dist/cjs/src/data/models/player.js +17 -0
  19. package/dist/cjs/src/data/models/tactics.d.ts +9 -1
  20. package/dist/cjs/src/data/models/tactics.js +9 -0
  21. package/dist/cjs/src/data/transformers/division.js +1 -1
  22. package/dist/cjs/src/data/transformers/lineup-preset.js +7 -1
  23. package/dist/cjs/src/data/transformers/lineup-preset.test.js +15 -2
  24. package/dist/cjs/src/data/transformers/match-preset-override.test.d.ts +1 -0
  25. package/dist/cjs/src/data/transformers/match-preset-override.test.js +275 -0
  26. package/dist/cjs/src/data/transformers/match-set.js +2 -0
  27. package/dist/cjs/src/data/transformers/match.d.ts +2 -1
  28. package/dist/cjs/src/data/transformers/match.js +21 -5
  29. package/dist/cjs/src/data/transformers/player.js +15 -1
  30. package/dist/cjs/src/data/transformers/qualifier.js +1 -1
  31. package/dist/cjs/src/data/transformers/rally-event-injury.test.d.ts +1 -0
  32. package/dist/cjs/src/data/transformers/rally-event-injury.test.js +33 -0
  33. package/dist/cjs/src/data/transformers/rally-event.d.ts +1 -0
  34. package/dist/cjs/src/data/transformers/rally-event.js +4 -1
  35. package/dist/cjs/src/data/transformers/season.js +1 -1
  36. package/dist/cjs/src/data/transformers/tactics.d.ts +3 -2
  37. package/dist/cjs/src/data/transformers/tactics.js +106 -3
  38. package/dist/cjs/src/data/transformers/team.d.ts +7 -1
  39. package/dist/cjs/src/data/transformers/team.js +22 -4
  40. package/dist/cjs/src/data/transformers/tournament.js +1 -1
  41. package/dist/cjs/src/service/event/schemas/substitution.z.d.ts +1 -0
  42. package/dist/cjs/src/service/event/schemas/substitution.z.js +2 -1
  43. package/dist/cjs/src/service/event/substitution.d.ts +1 -0
  44. package/dist/cjs/src/service/event/substitution.js +4 -2
  45. package/dist/cjs/src/service/match/match-set-forfeit.test.d.ts +1 -0
  46. package/dist/cjs/src/service/match/match-set-forfeit.test.js +76 -0
  47. package/dist/cjs/src/service/match/match-set.d.ts +8 -0
  48. package/dist/cjs/src/service/match/match-set.js +23 -1
  49. package/dist/cjs/src/service/match/match.d.ts +2 -0
  50. package/dist/cjs/src/service/match/match.js +2 -1
  51. package/dist/cjs/src/service/match/schemas/match-set.z.d.ts +1 -0
  52. package/dist/cjs/src/service/match/schemas/match-set.z.js +30 -2
  53. package/dist/cjs/src/service/match/schemas/match.z.d.ts +4 -0
  54. package/dist/cjs/src/service/match/schemas/match.z.js +4 -1
  55. package/dist/cjs/src/service/player/index.d.ts +1 -0
  56. package/dist/cjs/src/service/player/index.js +1 -0
  57. package/dist/cjs/src/service/player/injury.d.ts +14 -0
  58. package/dist/cjs/src/service/player/injury.js +39 -0
  59. package/dist/cjs/src/service/player/injury.test.d.ts +1 -0
  60. package/dist/cjs/src/service/player/injury.test.js +78 -0
  61. package/dist/cjs/src/service/player/player-generator.js +3 -1
  62. package/dist/cjs/src/service/player/player.d.ts +3 -0
  63. package/dist/cjs/src/service/player/player.js +3 -1
  64. package/dist/cjs/src/service/player/schemas/player.z.d.ts +6 -0
  65. package/dist/cjs/src/service/player/schemas/player.z.js +7 -0
  66. package/dist/cjs/src/service/team/index.d.ts +1 -0
  67. package/dist/cjs/src/service/team/index.js +1 -0
  68. package/dist/cjs/src/service/team/reserve-priority.d.ts +6 -0
  69. package/dist/cjs/src/service/team/reserve-priority.js +2 -0
  70. package/dist/cjs/src/service/team/schemas/reserve-priority.z.test.d.ts +1 -0
  71. package/dist/cjs/src/service/team/schemas/reserve-priority.z.test.js +125 -0
  72. package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +5 -0
  73. package/dist/cjs/src/service/team/schemas/tactics.z.js +32 -1
  74. package/dist/cjs/src/service/team/schemas/team.z.d.ts +5 -0
  75. package/dist/cjs/src/service/team/tactics.d.ts +5 -0
  76. package/dist/cjs/src/service/team/tactics.js +3 -1
  77. package/dist/esm/src/api/index.d.ts +14 -1
  78. package/dist/esm/src/data/init-models.js +18 -1
  79. package/dist/esm/src/data/models/currency-transaction.d.ts +4 -2
  80. package/dist/esm/src/data/models/currency-transaction.js +2 -0
  81. package/dist/esm/src/data/models/index.d.ts +3 -0
  82. package/dist/esm/src/data/models/index.js +3 -0
  83. package/dist/esm/src/data/models/match-preset.d.ts +28 -0
  84. package/dist/esm/src/data/models/match-preset.js +61 -0
  85. package/dist/esm/src/data/models/match-set.d.ts +2 -0
  86. package/dist/esm/src/data/models/match-set.js +5 -0
  87. package/dist/esm/src/data/models/match.d.ts +5 -2
  88. package/dist/esm/src/data/models/match.js +4 -0
  89. package/dist/esm/src/data/models/physician.d.ts +25 -0
  90. package/dist/esm/src/data/models/physician.js +53 -0
  91. package/dist/esm/src/data/models/physiotherapist.d.ts +25 -0
  92. package/dist/esm/src/data/models/physiotherapist.js +53 -0
  93. package/dist/esm/src/data/models/player.d.ts +8 -0
  94. package/dist/esm/src/data/models/player.js +17 -0
  95. package/dist/esm/src/data/models/tactics.d.ts +9 -1
  96. package/dist/esm/src/data/models/tactics.js +9 -0
  97. package/dist/esm/src/data/transformers/division.js +1 -1
  98. package/dist/esm/src/data/transformers/lineup-preset.js +7 -1
  99. package/dist/esm/src/data/transformers/lineup-preset.test.js +15 -2
  100. package/dist/esm/src/data/transformers/match-preset-override.test.d.ts +1 -0
  101. package/dist/esm/src/data/transformers/match-preset-override.test.js +273 -0
  102. package/dist/esm/src/data/transformers/match-set.js +2 -0
  103. package/dist/esm/src/data/transformers/match.d.ts +2 -1
  104. package/dist/esm/src/data/transformers/match.js +21 -5
  105. package/dist/esm/src/data/transformers/player.js +15 -1
  106. package/dist/esm/src/data/transformers/qualifier.js +1 -1
  107. package/dist/esm/src/data/transformers/rally-event-injury.test.d.ts +1 -0
  108. package/dist/esm/src/data/transformers/rally-event-injury.test.js +31 -0
  109. package/dist/esm/src/data/transformers/rally-event.d.ts +1 -0
  110. package/dist/esm/src/data/transformers/rally-event.js +4 -1
  111. package/dist/esm/src/data/transformers/season.js +1 -1
  112. package/dist/esm/src/data/transformers/tactics.d.ts +3 -2
  113. package/dist/esm/src/data/transformers/tactics.js +106 -4
  114. package/dist/esm/src/data/transformers/team.d.ts +7 -1
  115. package/dist/esm/src/data/transformers/team.js +20 -5
  116. package/dist/esm/src/data/transformers/tournament.js +1 -1
  117. package/dist/esm/src/service/event/schemas/substitution.z.d.ts +1 -0
  118. package/dist/esm/src/service/event/schemas/substitution.z.js +2 -1
  119. package/dist/esm/src/service/event/substitution.d.ts +1 -0
  120. package/dist/esm/src/service/event/substitution.js +4 -2
  121. package/dist/esm/src/service/match/match-set-forfeit.test.d.ts +1 -0
  122. package/dist/esm/src/service/match/match-set-forfeit.test.js +74 -0
  123. package/dist/esm/src/service/match/match-set.d.ts +8 -0
  124. package/dist/esm/src/service/match/match-set.js +23 -1
  125. package/dist/esm/src/service/match/match.d.ts +2 -0
  126. package/dist/esm/src/service/match/match.js +2 -1
  127. package/dist/esm/src/service/match/schemas/match-set.z.d.ts +1 -0
  128. package/dist/esm/src/service/match/schemas/match-set.z.js +30 -2
  129. package/dist/esm/src/service/match/schemas/match.z.d.ts +4 -0
  130. package/dist/esm/src/service/match/schemas/match.z.js +4 -1
  131. package/dist/esm/src/service/player/index.d.ts +1 -0
  132. package/dist/esm/src/service/player/index.js +1 -0
  133. package/dist/esm/src/service/player/injury.d.ts +14 -0
  134. package/dist/esm/src/service/player/injury.js +33 -0
  135. package/dist/esm/src/service/player/injury.test.d.ts +1 -0
  136. package/dist/esm/src/service/player/injury.test.js +76 -0
  137. package/dist/esm/src/service/player/player-generator.js +3 -1
  138. package/dist/esm/src/service/player/player.d.ts +3 -0
  139. package/dist/esm/src/service/player/player.js +3 -1
  140. package/dist/esm/src/service/player/schemas/player.z.d.ts +6 -0
  141. package/dist/esm/src/service/player/schemas/player.z.js +7 -0
  142. package/dist/esm/src/service/team/index.d.ts +1 -0
  143. package/dist/esm/src/service/team/index.js +1 -0
  144. package/dist/esm/src/service/team/reserve-priority.d.ts +6 -0
  145. package/dist/esm/src/service/team/reserve-priority.js +1 -0
  146. package/dist/esm/src/service/team/schemas/reserve-priority.z.test.d.ts +1 -0
  147. package/dist/esm/src/service/team/schemas/reserve-priority.z.test.js +123 -0
  148. package/dist/esm/src/service/team/schemas/tactics.z.d.ts +5 -0
  149. package/dist/esm/src/service/team/schemas/tactics.z.js +32 -1
  150. package/dist/esm/src/service/team/schemas/team.z.d.ts +5 -0
  151. package/dist/esm/src/service/team/tactics.d.ts +5 -0
  152. package/dist/esm/src/service/team/tactics.js +3 -1
  153. package/package.json +1 -1
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.transformToTeam = transformToObject;
4
7
  exports.transformFromTeam = transformToAttributes;
5
8
  const service_1 = require("../../service");
6
9
  const _1 = require(".");
10
+ const logger_1 = __importDefault(require("../../logger"));
7
11
  function transformToAttributes(team) {
8
12
  return {
9
13
  team_id: team.id,
@@ -16,11 +20,25 @@ function transformToAttributes(team) {
16
20
  division_id: team.divisionId
17
21
  };
18
22
  }
19
- function transformToObject(model, currentIteration) {
23
+ function transformToObject(model, currentIteration, tacticsOverride) {
20
24
  const roster = (model.PlayerTeams ?? []).map((pt) => (0, _1.transformToPlayer)(pt.player, currentIteration));
21
- const tactics = model.tactics != null && roster != null && roster.length > 0
22
- ? (0, _1.transformToTactics)(model.tactics, roster)
23
- : undefined;
25
+ let tactics;
26
+ if (tacticsOverride != null && roster.length > 0) {
27
+ try {
28
+ tactics = (0, _1.transformToTactics)((0, _1.apiTacticsConfigToTacticsAttributes)(tacticsOverride.config, model.team_id), roster);
29
+ }
30
+ catch (err) {
31
+ if (tacticsOverride.onFallback != null)
32
+ tacticsOverride.onFallback(tacticsOverride.presetId, err);
33
+ else
34
+ logger_1.default.warn(`MATCH_PRESET_FALLBACK: team=${model.team_id} preset=${tacticsOverride.presetId} failed to hydrate; using active tactics`);
35
+ }
36
+ }
37
+ if (tactics == null) {
38
+ tactics = model.tactics != null && roster.length > 0
39
+ ? (0, _1.transformToTactics)(model.tactics, roster)
40
+ : undefined;
41
+ }
24
42
  return service_1.Team.create({
25
43
  id: model.team_id,
26
44
  name: model.name,
@@ -45,7 +45,7 @@ function transformToObject(model) {
45
45
  id: model.competition_id,
46
46
  iteration: (0, _1.transformToIteration)(model.Iteration),
47
47
  matches: (model.CompetitionMatches ?? []).map(_1.transformToTournamentMatch),
48
- teams: (0, _1.sortTeamsByCompetitionIndex)(model.Teams ?? []).map(_1.transformToTeam),
48
+ teams: (0, _1.sortTeamsByCompetitionIndex)(model.Teams ?? []).map(team => (0, _1.transformToTeam)(team)),
49
49
  status: model.status,
50
50
  champion
51
51
  });
@@ -2,5 +2,6 @@ import { z } from 'zod';
2
2
  export declare const SubstitutionInputSchema: z.ZodObject<{
3
3
  playerId: z.ZodUUID;
4
4
  playerOut: z.ZodUUID;
5
+ injury: z.ZodOptional<z.ZodBoolean>;
5
6
  }, z.core.$strip>;
6
7
  export type SubstitutionInput = z.infer<typeof SubstitutionInputSchema>;
@@ -4,7 +4,8 @@ exports.SubstitutionInputSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  exports.SubstitutionInputSchema = zod_1.z.object({
6
6
  playerId: zod_1.z.uuid(),
7
- playerOut: zod_1.z.uuid()
7
+ playerOut: zod_1.z.uuid(),
8
+ injury: zod_1.z.boolean().optional()
8
9
  }).superRefine((data, ctx) => {
9
10
  if (data.playerId === data.playerOut) {
10
11
  ctx.addIssue({
@@ -1,6 +1,7 @@
1
1
  import { RallyEvent } from './rally-event';
2
2
  export declare class Substitution extends RallyEvent {
3
3
  readonly playerOut: string;
4
+ readonly injury?: boolean;
4
5
  static create(input: unknown): Substitution;
5
6
  private constructor();
6
7
  toString(): string;
@@ -17,15 +17,17 @@ class Substitution extends rally_event_1.RallyEvent {
17
17
  }
18
18
  return new Substitution(result.data);
19
19
  }
20
- constructor({ playerOut, playerId }) {
20
+ constructor({ playerOut, playerId, injury }) {
21
21
  super({ eventType, playerId });
22
22
  this.playerOut = playerOut;
23
+ this.injury = injury;
23
24
  }
24
25
  toString() {
25
26
  const playerId = `"playerId":"${this.playerId}"`;
26
27
  const playerOut = `"playerOut":"${this.playerOut}"`;
27
28
  const eventType = `"eventType":${this.eventType}`;
28
- return `{${playerId},${playerOut},${eventType}}`;
29
+ const injury = this.injury === true ? ',"injury":true' : '';
30
+ return `{${playerId},${playerOut},${eventType}${injury}}`;
29
31
  }
30
32
  }
31
33
  exports.Substitution = Substitution;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ const uuid_1 = require("uuid");
5
+ const match_set_1 = require("./match-set");
6
+ const match_team_1 = require("./match-team");
7
+ function makeSet(homeScore, awayScore, isTieBreak = false, forfeited) {
8
+ return match_set_1.MatchSet.create({
9
+ id: (0, uuid_1.v4)(),
10
+ order: isTieBreak ? 4 : 0,
11
+ isTieBreak,
12
+ homePlayerPosition: [],
13
+ awayPlayerPosition: [],
14
+ homeScore,
15
+ awayScore,
16
+ boxScores: [],
17
+ rallies: [],
18
+ ...(forfeited != null ? { forfeited } : {})
19
+ });
20
+ }
21
+ // Mid-set states are not valid CREATION states (the schema wants 0-0 or a finished score), so build them the
22
+ // way the sim does: create at 0-0 and play to the score.
23
+ function playedTo(homeScore, awayScore, isTieBreak = false) {
24
+ const set = makeSet(0, 0, isTieBreak);
25
+ for (let i = 0; i < homeScore; i++)
26
+ set.increaseScore(match_team_1.MatchTeam.HOME);
27
+ for (let i = 0; i < awayScore; i++)
28
+ set.increaseScore(match_team_1.MatchTeam.AWAY);
29
+ return set;
30
+ }
31
+ (0, globals_1.describe)('MatchSet — forfeit (FIVB 6.4.3)', () => {
32
+ (0, globals_1.it)('defaults forfeited to false', () => {
33
+ (0, globals_1.expect)(makeSet(25, 20).forfeited).toBe(false);
34
+ });
35
+ (0, globals_1.it)('accepts a persisted forfeited set with an awarded score', () => {
36
+ const set = makeSet(25, 13, false, true);
37
+ (0, globals_1.expect)(set.forfeited).toBe(true);
38
+ (0, globals_1.expect)(set.isOver()).toBe(true);
39
+ (0, globals_1.expect)(set.getWinner()).toBe(match_team_1.MatchTeam.HOME);
40
+ });
41
+ (0, globals_1.it)('awards the threshold when the loser is below deuce (18-23, home granted)', () => {
42
+ const set = playedTo(18, 23);
43
+ // Score order in makeSet is home, away; away leads 23-18 but HOME is granted the forfeit win.
44
+ set.forfeit(match_team_1.MatchTeam.HOME);
45
+ (0, globals_1.expect)(set.homeScore).toBe(25);
46
+ (0, globals_1.expect)(set.awayScore).toBe(23);
47
+ (0, globals_1.expect)(set.forfeited).toBe(true);
48
+ (0, globals_1.expect)(set.isOver()).toBe(true);
49
+ (0, globals_1.expect)(set.getWinner()).toBe(match_team_1.MatchTeam.HOME);
50
+ });
51
+ (0, globals_1.it)('awards loser + 2 beyond deuce (24-24, away granted -> 24-26)', () => {
52
+ const set = playedTo(24, 24);
53
+ set.forfeit(match_team_1.MatchTeam.AWAY);
54
+ (0, globals_1.expect)(set.homeScore).toBe(24);
55
+ (0, globals_1.expect)(set.awayScore).toBe(26);
56
+ (0, globals_1.expect)(set.isOver()).toBe(true);
57
+ (0, globals_1.expect)(set.getWinner()).toBe(match_team_1.MatchTeam.AWAY);
58
+ });
59
+ (0, globals_1.it)('keeps the forfeiting side points (0-0 fully-awarded set -> 25-0)', () => {
60
+ const set = makeSet(0, 0);
61
+ set.forfeit(match_team_1.MatchTeam.HOME);
62
+ (0, globals_1.expect)(set.homeScore).toBe(25);
63
+ (0, globals_1.expect)(set.awayScore).toBe(0);
64
+ (0, globals_1.expect)(set.isOver()).toBe(true);
65
+ });
66
+ (0, globals_1.it)('awards 15 on a tie break', () => {
67
+ const set = playedTo(10, 13, true);
68
+ set.forfeit(match_team_1.MatchTeam.HOME);
69
+ (0, globals_1.expect)(set.homeScore).toBe(15);
70
+ (0, globals_1.expect)(set.awayScore).toBe(13);
71
+ (0, globals_1.expect)(set.isOver()).toBe(true);
72
+ });
73
+ (0, globals_1.it)('rejects a forfeited set whose score never reached the awarding rule (below threshold)', () => {
74
+ (0, globals_1.expect)(() => makeSet(18, 12, false, true)).toThrow(/INVALID_MATCH_SET/);
75
+ });
76
+ });
@@ -13,10 +13,18 @@ export declare class MatchSet {
13
13
  readonly scoreThreshold: ScoreThreshold;
14
14
  private _homeScore;
15
15
  private _awayScore;
16
+ private _forfeited;
16
17
  static create(input: unknown): MatchSet;
17
18
  private constructor();
18
19
  get homeScore(): number;
19
20
  get awayScore(): number;
21
+ get forfeited(): boolean;
22
+ /**
23
+ * End the set by forfeit (FIVB 6.4.3, incomplete team). The forfeiting side keeps its points; the winner
24
+ * is granted exactly the points needed to win the set (the threshold, or loser + 2 beyond deuce), so the
25
+ * final score always satisfies the normal reached-threshold and win-by-2 rules. Marks the set forfeited.
26
+ */
27
+ forfeit(winner: MatchTeam): void;
20
28
  increaseScore(team: MatchTeam): void;
21
29
  addRally(rally: Rally): void;
22
30
  /**
@@ -16,11 +16,12 @@ class MatchSet {
16
16
  }
17
17
  return new MatchSet(result.data);
18
18
  }
19
- constructor({ id, order, isTieBreak, homePlayerPosition, awayPlayerPosition, boxScores, rallies, homeScore, awayScore }) {
19
+ constructor({ id, order, isTieBreak, homePlayerPosition, awayPlayerPosition, boxScores, rallies, homeScore, awayScore, forfeited = false }) {
20
20
  this.id = id;
21
21
  this.order = order;
22
22
  this._homeScore = homeScore;
23
23
  this._awayScore = awayScore;
24
+ this._forfeited = forfeited;
24
25
  this.homePlayerPosition = homePlayerPosition;
25
26
  this.awayPlayerPosition = awayPlayerPosition;
26
27
  this.boxScores = boxScores;
@@ -34,6 +35,27 @@ class MatchSet {
34
35
  get awayScore() {
35
36
  return this._awayScore;
36
37
  }
38
+ get forfeited() {
39
+ return this._forfeited;
40
+ }
41
+ /**
42
+ * End the set by forfeit (FIVB 6.4.3, incomplete team). The forfeiting side keeps its points; the winner
43
+ * is granted exactly the points needed to win the set (the threshold, or loser + 2 beyond deuce), so the
44
+ * final score always satisfies the normal reached-threshold and win-by-2 rules. Marks the set forfeited.
45
+ */
46
+ forfeit(winner) {
47
+ // Forfeiting an already-decided (or already-forfeited) set would silently rewrite the winner with a
48
+ // schema-legal score: surface the caller bug instead (e.g. both teams forfeited in the same rally).
49
+ if (this._forfeited || this.isOver())
50
+ throw new Error('SET_ALREADY_DECIDED');
51
+ if (winner === match_team_1.MatchTeam.HOME)
52
+ this._homeScore = Math.max(this.scoreThreshold, this._awayScore + 2);
53
+ else if (winner === match_team_1.MatchTeam.AWAY)
54
+ this._awayScore = Math.max(this.scoreThreshold, this._homeScore + 2);
55
+ else
56
+ throw new Error('UNKNOWN_TEAM');
57
+ this._forfeited = true;
58
+ }
37
59
  increaseScore(team) {
38
60
  if (team === match_team_1.MatchTeam.HOME)
39
61
  this._homeScore++;
@@ -3,6 +3,7 @@ import { Team } from '../team';
3
3
  import { MatchTeam } from './match-team';
4
4
  import { StatusEnum } from '../../data';
5
5
  import { VPER } from './vper';
6
+ export type MatchForfeitType = 'INCOMPLETE' | 'DEFAULT';
6
7
  export type MatchScore = '3-0' | '3-1' | '3-2' | '2-3' | '1-3' | '0-3';
7
8
  export type SetScore = 0 | 1 | 2 | 3;
8
9
  export declare class Match {
@@ -16,6 +17,7 @@ export declare class Match {
16
17
  homeScore?: number;
17
18
  awayScore?: number;
18
19
  winner?: Team;
20
+ forfeitType?: MatchForfeitType;
19
21
  static create(input: unknown): Match;
20
22
  private constructor();
21
23
  addSet(set: MatchSet): void;
@@ -17,7 +17,7 @@ class Match {
17
17
  }
18
18
  return new Match(result.data);
19
19
  }
20
- constructor({ id, homeTeam, awayTeam, scheduledDate, sets, status, homeScore, awayScore, winner, VPERs }) {
20
+ constructor({ id, homeTeam, awayTeam, scheduledDate, sets, status, homeScore, awayScore, winner, VPERs, forfeitType }) {
21
21
  if (homeTeam.id === awayTeam.id)
22
22
  throw new Error(`FAILED_TO_CREATE_MATCH. TEAMS_WITH_SAME_ID: ${homeTeam.id}`);
23
23
  this.id = id;
@@ -30,6 +30,7 @@ class Match {
30
30
  this.awayScore = awayScore;
31
31
  this.winner = winner;
32
32
  this.VPERs = VPERs ?? [];
33
+ this.forfeitType = forfeitType;
33
34
  }
34
35
  addSet(set) {
35
36
  if (this.sets.length >= 5)
@@ -11,5 +11,6 @@ export declare const MatchSetInputSchema: z.ZodObject<{
11
11
  awayScore: z.ZodNumber;
12
12
  boxScores: z.ZodArray<z.ZodCustom<BoxScore, BoxScore>>;
13
13
  rallies: z.ZodArray<z.ZodCustom<Rally, Rally>>;
14
+ forfeited: z.ZodDefault<z.ZodBoolean>;
14
15
  }, z.core.$strip>;
15
16
  export type MatchSetInput = z.infer<typeof MatchSetInputSchema>;
@@ -27,7 +27,10 @@ exports.MatchSetInputSchema = zod_1.z.object({
27
27
  })),
28
28
  rallies: zod_1.z.array(zod_1.z.custom((v) => v instanceof rally_1.Rally, {
29
29
  message: 'INVALID_RALLY_INSTANCE'
30
- }))
30
+ })),
31
+ // FIVB 6.4.3 set forfeit (injuries: team could not field six). The awarding rule (winner granted the points
32
+ // needed to win) keeps the threshold rule below satisfied; only the rallies-vs-score equality is relaxed.
33
+ forfeited: zod_1.z.boolean().default(false)
31
34
  }).superRefine((data, ctx) => {
32
35
  const { order, isTieBreak, rallies, homeScore, awayScore } = data;
33
36
  // isTieBreak <-> order === 4
@@ -56,6 +59,20 @@ exports.MatchSetInputSchema = zod_1.z.object({
56
59
  path: ['homeScore']
57
60
  });
58
61
  }
62
+ // A forfeited score must be exactly what forfeit() awards: winner = max(threshold, loser + 2). This rejects
63
+ // over-awarded scores (30-10), missing-award states (0-0 or 25-24 forfeited) and any hand-rolled corruption
64
+ // at the validation boundary instead of letting it flip winners downstream.
65
+ if (data.forfeited) {
66
+ const winner = Math.max(homeScore, awayScore);
67
+ const loser = Math.min(homeScore, awayScore);
68
+ if (winner !== Math.max(scoreThreshold, loser + 2)) {
69
+ ctx.addIssue({
70
+ code: 'custom',
71
+ message: 'FORFEITED_SCORE_MUST_MATCH_AWARDING_RULE',
72
+ path: ['homeScore']
73
+ });
74
+ }
75
+ }
59
76
  // If rallies exist, score cannot be 0-0 and must match the number of rallies.
60
77
  if (hasRallies) {
61
78
  if (isZeroZero) {
@@ -65,12 +82,23 @@ exports.MatchSetInputSchema = zod_1.z.object({
65
82
  path: ['rallies']
66
83
  });
67
84
  }
68
- if (homeScore + awayScore !== rallies.length) {
85
+ // A forfeited set keeps the rallies actually played and is topped up with awarded points, so the sum
86
+ // exceeds the rally count; anything else (more rallies than points) is still invalid. The loser's points
87
+ // were all EARNED, so when rallies are present they must at least cover them (rally-less persisted sets,
88
+ // the bot-vs-bot path, skip this whole block).
89
+ if (data.forfeited ? rallies.length > homeScore + awayScore : homeScore + awayScore !== rallies.length) {
69
90
  ctx.addIssue({
70
91
  code: 'custom',
71
92
  message: 'RALLIES_LENGTH_MUST_EQUAL_SCORE_SUM',
72
93
  path: ['rallies']
73
94
  });
74
95
  }
96
+ if (data.forfeited && rallies.length < Math.min(homeScore, awayScore)) {
97
+ ctx.addIssue({
98
+ code: 'custom',
99
+ message: 'FORFEITED_LOSER_POINTS_REQUIRE_RALLIES',
100
+ path: ['rallies']
101
+ });
102
+ }
75
103
  }
76
104
  });
@@ -18,5 +18,9 @@ export declare const MatchInputSchema: z.ZodObject<{
18
18
  homeScore: z.ZodOptional<z.ZodNumber>;
19
19
  awayScore: z.ZodOptional<z.ZodNumber>;
20
20
  winner: z.ZodOptional<z.ZodCustom<Team, Team>>;
21
+ forfeitType: z.ZodOptional<z.ZodEnum<{
22
+ INCOMPLETE: "INCOMPLETE";
23
+ DEFAULT: "DEFAULT";
24
+ }>>;
21
25
  }, z.core.$strip>;
22
26
  export type MatchInput = z.infer<typeof MatchInputSchema>;
@@ -23,7 +23,10 @@ exports.MatchInputSchema = zod_1.z.object({
23
23
  })).optional(),
24
24
  homeScore: zod_1.z.number().int().optional(),
25
25
  awayScore: zod_1.z.number().int().optional(),
26
- winner: teamSchema.optional()
26
+ winner: teamSchema.optional(),
27
+ // Abnormal ending marker (see MatchForfeitType). The match still validates as a normal COMPLETE result
28
+ // because forfeited sets carry FIVB-awarded scores.
29
+ forfeitType: zod_1.z.enum(['INCOMPLETE', 'DEFAULT']).optional()
27
30
  }).superRefine((data, ctx) => {
28
31
  if (data.homeTeam.id === data.awayTeam.id) {
29
32
  ctx.addIssue({
@@ -1,6 +1,7 @@
1
1
  export * from './player';
2
2
  export * from './improvement';
3
3
  export * from './decline';
4
+ export * from './injury';
4
5
  export * from './player-generator';
5
6
  export * from './performance-stats';
6
7
  export * from './trait';
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./player"), exports);
18
18
  __exportStar(require("./improvement"), exports);
19
19
  __exportStar(require("./decline"), exports);
20
+ __exportStar(require("./injury"), exports);
20
21
  __exportStar(require("./player-generator"), exports);
21
22
  __exportStar(require("./performance-stats"), exports);
22
23
  __exportStar(require("./trait"), exports);
@@ -0,0 +1,14 @@
1
+ import { Rarity } from './rarity';
2
+ export declare enum InjurySeverityEnum {
3
+ MINOR = 1,
4
+ MODERATE = 2,
5
+ SEVERE = 3
6
+ }
7
+ export interface PlayerInjury {
8
+ readonly severity: number;
9
+ readonly injuredUntil: Date;
10
+ readonly debuff?: number;
11
+ }
12
+ export declare function hasActiveInjury(injury: PlayerInjury | undefined, at: Date): boolean;
13
+ export declare function getPhysiotherapistRiskMultiplier(rarity: Rarity): number;
14
+ export declare function getPhysicianRecoveryMultiplier(rarity: Rarity): number;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InjurySeverityEnum = void 0;
4
+ exports.hasActiveInjury = hasActiveInjury;
5
+ exports.getPhysiotherapistRiskMultiplier = getPhysiotherapistRiskMultiplier;
6
+ exports.getPhysicianRecoveryMultiplier = getPhysicianRecoveryMultiplier;
7
+ const rarity_1 = require("./rarity");
8
+ var InjurySeverityEnum;
9
+ (function (InjurySeverityEnum) {
10
+ InjurySeverityEnum[InjurySeverityEnum["MINOR"] = 1] = "MINOR";
11
+ InjurySeverityEnum[InjurySeverityEnum["MODERATE"] = 2] = "MODERATE";
12
+ InjurySeverityEnum[InjurySeverityEnum["SEVERE"] = 3] = "SEVERE";
13
+ })(InjurySeverityEnum || (exports.InjurySeverityEnum = InjurySeverityEnum = {}));
14
+ // A long-term injury is active while `at` is before injuredUntil.
15
+ function hasActiveInjury(injury, at) {
16
+ return injury != null && injury.injuredUntil.getTime() > at.getTime();
17
+ }
18
+ // Staff effect curves (same Record-plus-accessor shape as getCoachMultiplier). COMMON = neutral baseline.
19
+ // Physiotherapist scales the per-action injury CHANCE; physician scales long-term recovery TIME.
20
+ const PHYSIOTHERAPIST_RISK_MULTIPLIERS = {
21
+ [rarity_1.RarityEnum.COMMON]: 1.0,
22
+ [rarity_1.RarityEnum.RARE]: 0.92,
23
+ [rarity_1.RarityEnum.LEGENDARY]: 0.84,
24
+ [rarity_1.RarityEnum.MYTHIC]: 0.75,
25
+ [rarity_1.RarityEnum.SPECIAL]: 0.65
26
+ };
27
+ const PHYSICIAN_RECOVERY_MULTIPLIERS = {
28
+ [rarity_1.RarityEnum.COMMON]: 1.0,
29
+ [rarity_1.RarityEnum.RARE]: 0.9,
30
+ [rarity_1.RarityEnum.LEGENDARY]: 0.8,
31
+ [rarity_1.RarityEnum.MYTHIC]: 0.65,
32
+ [rarity_1.RarityEnum.SPECIAL]: 0.5
33
+ };
34
+ function getPhysiotherapistRiskMultiplier(rarity) {
35
+ return PHYSIOTHERAPIST_RISK_MULTIPLIERS[rarity] ?? 1.0;
36
+ }
37
+ function getPhysicianRecoveryMultiplier(rarity) {
38
+ return PHYSICIAN_RECOVERY_MULTIPLIERS[rarity] ?? 1.0;
39
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ const rarity_1 = require("./rarity");
5
+ const injury_1 = require("./injury");
6
+ const player_z_1 = require("./schemas/player.z");
7
+ const test_helpers_1 = require("../test-helpers");
8
+ (0, globals_1.describe)('Player injury fields', () => {
9
+ const country = (0, test_helpers_1.makeCountry)();
10
+ (0, globals_1.it)('defaults injuryProneProfile to 0.5 for legacy Player.create callers', () => {
11
+ const player = (0, test_helpers_1.makePlayer)(country);
12
+ (0, globals_1.expect)(player.injuryProneProfile).toBe(0.5);
13
+ (0, globals_1.expect)(player.injury).toBeUndefined();
14
+ });
15
+ (0, globals_1.it)('accepts an injury object and coerces injuredUntil from an ISO string', () => {
16
+ const base = player_z_1.PlayerInputSchema.safeParse({
17
+ ...validInput(),
18
+ injuryProneProfile: 0.8,
19
+ injury: { severity: 2, injuredUntil: '2026-07-04T10:00:00.000Z', debuff: 0.75 }
20
+ });
21
+ (0, globals_1.expect)(base.success).toBe(true);
22
+ if (base.success) {
23
+ (0, globals_1.expect)(base.data.injuryProneProfile).toBe(0.8);
24
+ (0, globals_1.expect)(base.data.injury?.injuredUntil).toBeInstanceOf(Date);
25
+ (0, globals_1.expect)(base.data.injury?.severity).toBe(2);
26
+ }
27
+ });
28
+ (0, globals_1.it)('rejects out-of-range severity and profile', () => {
29
+ (0, globals_1.expect)(player_z_1.PlayerInputSchema.safeParse({ ...validInput(), injury: { severity: 4, injuredUntil: new Date() } }).success).toBe(false);
30
+ (0, globals_1.expect)(player_z_1.PlayerInputSchema.safeParse({ ...validInput(), injury: { severity: 0, injuredUntil: new Date() } }).success).toBe(false);
31
+ (0, globals_1.expect)(player_z_1.PlayerInputSchema.safeParse({ ...validInput(), injuryProneProfile: 1.2 }).success).toBe(false);
32
+ (0, globals_1.expect)(player_z_1.PlayerInputSchema.safeParse({ ...validInput(), injuryProneProfile: -0.1 }).success).toBe(false);
33
+ });
34
+ function validInput() {
35
+ const p = (0, test_helpers_1.makePlayer)(country);
36
+ return {
37
+ id: p.id,
38
+ name: { first: p.name.first, last: p.name.last },
39
+ country,
40
+ stats: p.stats,
41
+ roles: p.roles,
42
+ traits: p.traits,
43
+ rarity: p.rarity,
44
+ age: p.age,
45
+ birthAge: p.birthAge,
46
+ birthIteration: p.birthIteration,
47
+ declineProfile: p.declineProfile
48
+ };
49
+ }
50
+ });
51
+ (0, globals_1.describe)('hasActiveInjury', () => {
52
+ const at = new Date('2026-07-03T12:00:00.000Z');
53
+ (0, globals_1.it)('is false for a healthy player', () => {
54
+ (0, globals_1.expect)((0, injury_1.hasActiveInjury)(undefined, at)).toBe(false);
55
+ });
56
+ (0, globals_1.it)('is true while injuredUntil is in the future', () => {
57
+ (0, globals_1.expect)((0, injury_1.hasActiveInjury)({ severity: 1, injuredUntil: new Date('2026-07-03T18:00:00.000Z') }, at)).toBe(true);
58
+ });
59
+ (0, globals_1.it)('is false once injuredUntil has passed (lazy expiry)', () => {
60
+ (0, globals_1.expect)((0, injury_1.hasActiveInjury)({ severity: 3, injuredUntil: new Date('2026-07-03T11:59:59.000Z') }, at)).toBe(false);
61
+ });
62
+ });
63
+ (0, globals_1.describe)('staff multipliers', () => {
64
+ (0, globals_1.it)('physiotherapist reduces risk monotonically from COMMON to SPECIAL', () => {
65
+ (0, globals_1.expect)((0, injury_1.getPhysiotherapistRiskMultiplier)(rarity_1.RarityEnum.COMMON)).toBe(1.0);
66
+ (0, globals_1.expect)((0, injury_1.getPhysiotherapistRiskMultiplier)(rarity_1.RarityEnum.SPECIAL)).toBe(0.65);
67
+ const ladder = [rarity_1.RarityEnum.COMMON, rarity_1.RarityEnum.RARE, rarity_1.RarityEnum.LEGENDARY, rarity_1.RarityEnum.MYTHIC, rarity_1.RarityEnum.SPECIAL];
68
+ const values = ladder.map(injury_1.getPhysiotherapistRiskMultiplier);
69
+ (0, globals_1.expect)([...values].sort((a, b) => b - a)).toEqual(values);
70
+ });
71
+ (0, globals_1.it)('physician reduces recovery monotonically and halves it at SPECIAL', () => {
72
+ (0, globals_1.expect)((0, injury_1.getPhysicianRecoveryMultiplier)(rarity_1.RarityEnum.COMMON)).toBe(1.0);
73
+ (0, globals_1.expect)((0, injury_1.getPhysicianRecoveryMultiplier)(rarity_1.RarityEnum.SPECIAL)).toBe(0.5);
74
+ const ladder = [rarity_1.RarityEnum.COMMON, rarity_1.RarityEnum.RARE, rarity_1.RarityEnum.LEGENDARY, rarity_1.RarityEnum.MYTHIC, rarity_1.RarityEnum.SPECIAL];
75
+ const values = ladder.map(injury_1.getPhysicianRecoveryMultiplier);
76
+ (0, globals_1.expect)([...values].sort((a, b) => b - a)).toEqual(values);
77
+ });
78
+ });
@@ -308,7 +308,9 @@ class PlayerGenerator {
308
308
  const traits = (0, trait_1.assignTraits)(roles, rarity, maxTraits);
309
309
  const birthAge = (0, node_crypto_1.randomInt)(15, 21);
310
310
  const declineProfile = decline_1.declineProfiles[(0, node_crypto_1.randomInt)(0, decline_1.declineProfiles.length)];
311
- return player_1.Player.create({ id: (0, uuid_1.v4)(), name, country, stats, roles, traits, rarity, age: birthAge, birthAge, birthIteration, declineProfile });
311
+ // Uniform 0-1 (mean-neutral: the sim maps it to a 0.6-1.4 risk factor centered on 1.0).
312
+ const injuryProneProfile = (0, node_crypto_1.randomInt)(0, 101) / 100;
313
+ return player_1.Player.create({ id: (0, uuid_1.v4)(), name, country, stats, roles, traits, rarity, age: birthAge, birthAge, birthIteration, declineProfile, injuryProneProfile });
312
314
  }
313
315
  static generatePlayers(count, countries) {
314
316
  const players = [];
@@ -6,6 +6,7 @@ import { Stats } from './stats';
6
6
  import { Role } from './role';
7
7
  import { BoxScore, BoxScoreTotals } from '../match';
8
8
  import { DeclineProfile } from './decline';
9
+ import { PlayerInjury } from './injury';
9
10
  export declare class Stat {
10
11
  readonly name: Stats;
11
12
  readonly value: number;
@@ -36,6 +37,8 @@ export declare class Player {
36
37
  readonly birthAge: number;
37
38
  readonly birthIteration: number;
38
39
  readonly declineProfile: DeclineProfile;
40
+ readonly injuryProneProfile: number;
41
+ readonly injury?: PlayerInjury;
39
42
  readonly boxScores?: BoxScore[];
40
43
  readonly boxScoreTotals?: BoxScoreTotals;
41
44
  static create(input: unknown): Player;
@@ -54,7 +54,7 @@ class Player {
54
54
  stats: performance_stats_1.PerformanceStats.create(result.data.stats)
55
55
  });
56
56
  }
57
- constructor({ id, name, nickname, country, stats, roles, traits, rarity, age, birthAge, birthIteration, declineProfile, boxScores, boxScoreTotals }) {
57
+ constructor({ id, name, nickname, country, stats, roles, traits, rarity, age, birthAge, birthIteration, declineProfile, injuryProneProfile, injury, boxScores, boxScoreTotals }) {
58
58
  this.id = id;
59
59
  this.name = name;
60
60
  this.nickname = nickname;
@@ -67,6 +67,8 @@ class Player {
67
67
  this.birthAge = birthAge;
68
68
  this.birthIteration = birthIteration;
69
69
  this.declineProfile = declineProfile;
70
+ this.injuryProneProfile = injuryProneProfile;
71
+ this.injury = injury;
70
72
  this.boxScores = boxScores;
71
73
  this.boxScoreTotals = boxScoreTotals;
72
74
  this.generalStats = Object.values(stats_1.StatsEnum).map((stat) => ({
@@ -79,6 +79,12 @@ export declare const PlayerInputSchema: z.ZodObject<{
79
79
  VETERAN: DeclineProfileEnum.VETERAN;
80
80
  IRONMAN: DeclineProfileEnum.IRONMAN;
81
81
  }>;
82
+ injuryProneProfile: z.ZodDefault<z.ZodNumber>;
83
+ injury: z.ZodOptional<z.ZodObject<{
84
+ severity: z.ZodNumber;
85
+ injuredUntil: z.ZodCoercedDate<unknown>;
86
+ debuff: z.ZodOptional<z.ZodNumber>;
87
+ }, z.core.$strip>>;
82
88
  boxScores: z.ZodOptional<z.ZodArray<z.ZodObject<{
83
89
  playerId: z.ZodUUID;
84
90
  matchSetId: z.ZodUUID;
@@ -38,6 +38,13 @@ exports.PlayerInputSchema = zod_1.z.object({
38
38
  birthAge: zod_1.z.number().int().min(0),
39
39
  birthIteration: zod_1.z.number().int().min(1),
40
40
  declineProfile: zod_1.z.enum(Object.values(decline_1.DeclineProfileEnum)),
41
+ // Defaulted so every existing Player.create caller (generator, transformers, test helpers) stays valid.
42
+ injuryProneProfile: zod_1.z.number().min(0).max(1).default(0.5),
43
+ injury: zod_1.z.object({
44
+ severity: zod_1.z.number().int().min(1).max(3),
45
+ injuredUntil: zod_1.z.coerce.date(),
46
+ debuff: zod_1.z.number().min(0).max(1).optional()
47
+ }).optional(),
41
48
  boxScores: zod_1.z.array(box_score_z_1.BoxScoreInputSchema).optional(),
42
49
  boxScoreTotals: BoxScoreTotalsInputSchema.optional()
43
50
  });
@@ -11,3 +11,4 @@ export * from './rotation-system';
11
11
  export * from './lineup-function';
12
12
  export * from './base-position';
13
13
  export * from './offensive-preference';
14
+ export * from './reserve-priority';
@@ -27,3 +27,4 @@ __exportStar(require("./rotation-system"), exports);
27
27
  __exportStar(require("./lineup-function"), exports);
28
28
  __exportStar(require("./base-position"), exports);
29
29
  __exportStar(require("./offensive-preference"), exports);
30
+ __exportStar(require("./reserve-priority"), exports);
@@ -0,0 +1,6 @@
1
+ import { CourtPosition } from '../match';
2
+ import { Player } from '../player';
3
+ export interface ReservePriority {
4
+ readonly position: CourtPosition;
5
+ readonly reserves: Player[];
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });