volleyballsimtypes 0.0.463 → 0.0.465

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 (129) hide show
  1. package/dist/cjs/src/api/index.d.ts +13 -1
  2. package/dist/cjs/src/data/init-models.js +8 -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 +2 -0
  6. package/dist/cjs/src/data/models/index.js +2 -0
  7. package/dist/cjs/src/data/models/match-set.d.ts +2 -0
  8. package/dist/cjs/src/data/models/match-set.js +5 -0
  9. package/dist/cjs/src/data/models/match.d.ts +3 -1
  10. package/dist/cjs/src/data/models/match.js +4 -0
  11. package/dist/cjs/src/data/models/physician.d.ts +25 -0
  12. package/dist/cjs/src/data/models/physician.js +57 -0
  13. package/dist/cjs/src/data/models/physiotherapist.d.ts +25 -0
  14. package/dist/cjs/src/data/models/physiotherapist.js +57 -0
  15. package/dist/cjs/src/data/models/player.d.ts +8 -0
  16. package/dist/cjs/src/data/models/player.js +17 -0
  17. package/dist/cjs/src/data/models/tactics.d.ts +10 -1
  18. package/dist/cjs/src/data/models/tactics.js +9 -0
  19. package/dist/cjs/src/data/transformers/lineup-preset.js +7 -1
  20. package/dist/cjs/src/data/transformers/lineup-preset.test.js +15 -2
  21. package/dist/cjs/src/data/transformers/match-set.js +2 -0
  22. package/dist/cjs/src/data/transformers/match.js +6 -2
  23. package/dist/cjs/src/data/transformers/player.js +15 -1
  24. package/dist/cjs/src/data/transformers/rally-event-injury.test.d.ts +1 -0
  25. package/dist/cjs/src/data/transformers/rally-event-injury.test.js +33 -0
  26. package/dist/cjs/src/data/transformers/rally-event.d.ts +1 -0
  27. package/dist/cjs/src/data/transformers/rally-event.js +4 -1
  28. package/dist/cjs/src/data/transformers/tactics.js +83 -4
  29. package/dist/cjs/src/service/event/schemas/substitution.z.d.ts +1 -0
  30. package/dist/cjs/src/service/event/schemas/substitution.z.js +2 -1
  31. package/dist/cjs/src/service/event/substitution.d.ts +1 -0
  32. package/dist/cjs/src/service/event/substitution.js +4 -2
  33. package/dist/cjs/src/service/match/match-set-forfeit.test.d.ts +1 -0
  34. package/dist/cjs/src/service/match/match-set-forfeit.test.js +76 -0
  35. package/dist/cjs/src/service/match/match-set.d.ts +8 -0
  36. package/dist/cjs/src/service/match/match-set.js +23 -1
  37. package/dist/cjs/src/service/match/match.d.ts +2 -0
  38. package/dist/cjs/src/service/match/match.js +2 -1
  39. package/dist/cjs/src/service/match/schemas/match-set.z.d.ts +1 -0
  40. package/dist/cjs/src/service/match/schemas/match-set.z.js +30 -2
  41. package/dist/cjs/src/service/match/schemas/match.z.d.ts +4 -0
  42. package/dist/cjs/src/service/match/schemas/match.z.js +4 -1
  43. package/dist/cjs/src/service/player/index.d.ts +1 -0
  44. package/dist/cjs/src/service/player/index.js +1 -0
  45. package/dist/cjs/src/service/player/injury.d.ts +14 -0
  46. package/dist/cjs/src/service/player/injury.js +39 -0
  47. package/dist/cjs/src/service/player/injury.test.d.ts +1 -0
  48. package/dist/cjs/src/service/player/injury.test.js +78 -0
  49. package/dist/cjs/src/service/player/player-generator.js +3 -1
  50. package/dist/cjs/src/service/player/player.d.ts +3 -0
  51. package/dist/cjs/src/service/player/player.js +3 -1
  52. package/dist/cjs/src/service/player/schemas/player.z.d.ts +6 -0
  53. package/dist/cjs/src/service/player/schemas/player.z.js +7 -0
  54. package/dist/cjs/src/service/team/index.d.ts +1 -0
  55. package/dist/cjs/src/service/team/index.js +1 -0
  56. package/dist/cjs/src/service/team/injury-replacement.d.ts +6 -0
  57. package/dist/cjs/src/service/team/injury-replacement.js +2 -0
  58. package/dist/cjs/src/service/team/schemas/injury-replacement.z.test.d.ts +1 -0
  59. package/dist/cjs/src/service/team/schemas/injury-replacement.z.test.js +142 -0
  60. package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +6 -0
  61. package/dist/cjs/src/service/team/schemas/tactics.z.js +47 -1
  62. package/dist/cjs/src/service/team/schemas/team.z.d.ts +6 -0
  63. package/dist/cjs/src/service/team/tactics.d.ts +5 -0
  64. package/dist/cjs/src/service/team/tactics.js +3 -1
  65. package/dist/esm/src/api/index.d.ts +13 -1
  66. package/dist/esm/src/data/init-models.js +9 -1
  67. package/dist/esm/src/data/models/currency-transaction.d.ts +4 -2
  68. package/dist/esm/src/data/models/currency-transaction.js +2 -0
  69. package/dist/esm/src/data/models/index.d.ts +2 -0
  70. package/dist/esm/src/data/models/index.js +2 -0
  71. package/dist/esm/src/data/models/match-set.d.ts +2 -0
  72. package/dist/esm/src/data/models/match-set.js +5 -0
  73. package/dist/esm/src/data/models/match.d.ts +3 -1
  74. package/dist/esm/src/data/models/match.js +4 -0
  75. package/dist/esm/src/data/models/physician.d.ts +25 -0
  76. package/dist/esm/src/data/models/physician.js +53 -0
  77. package/dist/esm/src/data/models/physiotherapist.d.ts +25 -0
  78. package/dist/esm/src/data/models/physiotherapist.js +53 -0
  79. package/dist/esm/src/data/models/player.d.ts +8 -0
  80. package/dist/esm/src/data/models/player.js +17 -0
  81. package/dist/esm/src/data/models/tactics.d.ts +10 -1
  82. package/dist/esm/src/data/models/tactics.js +9 -0
  83. package/dist/esm/src/data/transformers/lineup-preset.js +7 -1
  84. package/dist/esm/src/data/transformers/lineup-preset.test.js +15 -2
  85. package/dist/esm/src/data/transformers/match-set.js +2 -0
  86. package/dist/esm/src/data/transformers/match.js +6 -2
  87. package/dist/esm/src/data/transformers/player.js +15 -1
  88. package/dist/esm/src/data/transformers/rally-event-injury.test.d.ts +1 -0
  89. package/dist/esm/src/data/transformers/rally-event-injury.test.js +31 -0
  90. package/dist/esm/src/data/transformers/rally-event.d.ts +1 -0
  91. package/dist/esm/src/data/transformers/rally-event.js +4 -1
  92. package/dist/esm/src/data/transformers/tactics.js +83 -4
  93. package/dist/esm/src/service/event/schemas/substitution.z.d.ts +1 -0
  94. package/dist/esm/src/service/event/schemas/substitution.z.js +2 -1
  95. package/dist/esm/src/service/event/substitution.d.ts +1 -0
  96. package/dist/esm/src/service/event/substitution.js +4 -2
  97. package/dist/esm/src/service/match/match-set-forfeit.test.d.ts +1 -0
  98. package/dist/esm/src/service/match/match-set-forfeit.test.js +74 -0
  99. package/dist/esm/src/service/match/match-set.d.ts +8 -0
  100. package/dist/esm/src/service/match/match-set.js +23 -1
  101. package/dist/esm/src/service/match/match.d.ts +2 -0
  102. package/dist/esm/src/service/match/match.js +2 -1
  103. package/dist/esm/src/service/match/schemas/match-set.z.d.ts +1 -0
  104. package/dist/esm/src/service/match/schemas/match-set.z.js +30 -2
  105. package/dist/esm/src/service/match/schemas/match.z.d.ts +4 -0
  106. package/dist/esm/src/service/match/schemas/match.z.js +4 -1
  107. package/dist/esm/src/service/player/index.d.ts +1 -0
  108. package/dist/esm/src/service/player/index.js +1 -0
  109. package/dist/esm/src/service/player/injury.d.ts +14 -0
  110. package/dist/esm/src/service/player/injury.js +33 -0
  111. package/dist/esm/src/service/player/injury.test.d.ts +1 -0
  112. package/dist/esm/src/service/player/injury.test.js +76 -0
  113. package/dist/esm/src/service/player/player-generator.js +3 -1
  114. package/dist/esm/src/service/player/player.d.ts +3 -0
  115. package/dist/esm/src/service/player/player.js +3 -1
  116. package/dist/esm/src/service/player/schemas/player.z.d.ts +6 -0
  117. package/dist/esm/src/service/player/schemas/player.z.js +7 -0
  118. package/dist/esm/src/service/team/index.d.ts +1 -0
  119. package/dist/esm/src/service/team/index.js +1 -0
  120. package/dist/esm/src/service/team/injury-replacement.d.ts +6 -0
  121. package/dist/esm/src/service/team/injury-replacement.js +1 -0
  122. package/dist/esm/src/service/team/schemas/injury-replacement.z.test.d.ts +1 -0
  123. package/dist/esm/src/service/team/schemas/injury-replacement.z.test.js +140 -0
  124. package/dist/esm/src/service/team/schemas/tactics.z.d.ts +6 -0
  125. package/dist/esm/src/service/team/schemas/tactics.z.js +47 -1
  126. package/dist/esm/src/service/team/schemas/team.z.d.ts +6 -0
  127. package/dist/esm/src/service/team/tactics.d.ts +5 -0
  128. package/dist/esm/src/service/team/tactics.js +3 -1
  129. package/package.json +1 -1
@@ -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 './injury-replacement';
@@ -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("./injury-replacement"), exports);
@@ -0,0 +1,6 @@
1
+ import { Player } from '../player';
2
+ export interface InjuryReplacement {
3
+ readonly starter: Player;
4
+ readonly replacement: Player;
5
+ readonly benchBackfill?: Player;
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ const tactics_z_1 = require("./tactics.z");
5
+ const test_helpers_1 = require("../../test-helpers");
6
+ (0, globals_1.describe)('TacticsInputSchema — injuryReplacements (owner rules 2026-07-03)', () => {
7
+ const country = (0, test_helpers_1.makeCountry)();
8
+ const p1 = (0, test_helpers_1.makePlayer)(country);
9
+ const p2 = (0, test_helpers_1.makePlayer)(country);
10
+ const p3 = (0, test_helpers_1.makePlayer)(country);
11
+ const p4 = (0, test_helpers_1.makePlayer)(country);
12
+ const p5 = (0, test_helpers_1.makePlayer)(country);
13
+ const p6 = (0, test_helpers_1.makePlayer)(country);
14
+ const benchA = (0, test_helpers_1.makePlayer)(country);
15
+ const benchB = (0, test_helpers_1.makePlayer)(country);
16
+ const libero = (0, test_helpers_1.makePlayer)(country);
17
+ const reserveA = (0, test_helpers_1.makePlayer)(country);
18
+ const reserveB = (0, test_helpers_1.makePlayer)(country);
19
+ function baseInput(overrides = {}) {
20
+ return {
21
+ lineup: {
22
+ 4: p4,
23
+ 3: p3,
24
+ 2: p2,
25
+ 5: p5,
26
+ 6: p6,
27
+ 1: p1,
28
+ 0: libero,
29
+ bench: [benchA, benchB]
30
+ },
31
+ liberoReplacements: [],
32
+ ...overrides
33
+ };
34
+ }
35
+ (0, globals_1.it)('defaults to true / empty', () => {
36
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput());
37
+ (0, globals_1.expect)(res.success).toBe(true);
38
+ if (res.success) {
39
+ (0, globals_1.expect)(res.data.replaceInjuredImmediately).toBe(true);
40
+ (0, globals_1.expect)(res.data.injuryReplacements).toEqual([]);
41
+ }
42
+ });
43
+ (0, globals_1.it)('accepts a bench replacement with a reserve backfill', () => {
44
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
45
+ injuryReplacements: [{ starter: p1, replacement: benchA, benchBackfill: reserveA }]
46
+ }));
47
+ (0, globals_1.expect)(res.success).toBe(true);
48
+ });
49
+ (0, globals_1.it)('accepts a bench replacement WITHOUT a backfill (empty bench seat is allowed by design)', () => {
50
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
51
+ injuryReplacements: [{ starter: p1, replacement: benchA }]
52
+ }));
53
+ (0, globals_1.expect)(res.success).toBe(true);
54
+ });
55
+ (0, globals_1.it)('accepts a reserve replacement (no backfill possible)', () => {
56
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
57
+ injuryReplacements: [{ starter: p1, replacement: reserveA }]
58
+ }));
59
+ (0, globals_1.expect)(res.success).toBe(true);
60
+ });
61
+ (0, globals_1.it)('rejects a starter as replacement', () => {
62
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
63
+ injuryReplacements: [{ starter: p1, replacement: p2 }]
64
+ }));
65
+ (0, globals_1.expect)(res.success).toBe(false);
66
+ if (!res.success) {
67
+ (0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'INJURY_REPLACEMENT_MUST_BE_BENCH_OR_RESERVE')).toBe(true);
68
+ }
69
+ });
70
+ (0, globals_1.it)('rejects the libero as replacement', () => {
71
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
72
+ injuryReplacements: [{ starter: p1, replacement: libero }]
73
+ }));
74
+ (0, globals_1.expect)(res.success).toBe(false);
75
+ if (!res.success) {
76
+ (0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'INJURY_REPLACEMENT_MUST_BE_BENCH_OR_RESERVE')).toBe(true);
77
+ }
78
+ });
79
+ (0, globals_1.it)('rejects a non-starter as the starter side', () => {
80
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
81
+ injuryReplacements: [{ starter: benchA, replacement: reserveA }]
82
+ }));
83
+ (0, globals_1.expect)(res.success).toBe(false);
84
+ if (!res.success) {
85
+ (0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'INJURY_REPLACEMENT_STARTER_NOT_A_STARTER')).toBe(true);
86
+ }
87
+ });
88
+ (0, globals_1.it)('rejects a backfill when the replacement is a reserve', () => {
89
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
90
+ injuryReplacements: [{ starter: p1, replacement: reserveA, benchBackfill: reserveB }]
91
+ }));
92
+ (0, globals_1.expect)(res.success).toBe(false);
93
+ if (!res.success) {
94
+ (0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'BENCH_BACKFILL_REQUIRES_BENCH_REPLACEMENT')).toBe(true);
95
+ }
96
+ });
97
+ (0, globals_1.it)('rejects a bench player as backfill (must be a reserve)', () => {
98
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
99
+ injuryReplacements: [{ starter: p1, replacement: benchA, benchBackfill: benchB }]
100
+ }));
101
+ (0, globals_1.expect)(res.success).toBe(false);
102
+ if (!res.success) {
103
+ (0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'BENCH_BACKFILL_MUST_BE_RESERVE')).toBe(true);
104
+ }
105
+ });
106
+ (0, globals_1.it)('rejects duplicate starter entries', () => {
107
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
108
+ injuryReplacements: [
109
+ { starter: p1, replacement: benchA },
110
+ { starter: p1, replacement: reserveA }
111
+ ]
112
+ }));
113
+ (0, globals_1.expect)(res.success).toBe(false);
114
+ if (!res.success) {
115
+ (0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'INJURY_REPLACEMENT_DUPLICATE_STARTER')).toBe(true);
116
+ }
117
+ });
118
+ (0, globals_1.it)('rejects the same incoming player used twice', () => {
119
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
120
+ injuryReplacements: [
121
+ { starter: p1, replacement: reserveA },
122
+ { starter: p2, replacement: reserveA }
123
+ ]
124
+ }));
125
+ (0, globals_1.expect)(res.success).toBe(false);
126
+ if (!res.success) {
127
+ (0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'INJURY_REPLACEMENT_PLAYER_USED_TWICE')).toBe(true);
128
+ }
129
+ });
130
+ (0, globals_1.it)('rejects a player used as replacement in one entry and backfill in another', () => {
131
+ const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
132
+ injuryReplacements: [
133
+ { starter: p1, replacement: reserveA },
134
+ { starter: p2, replacement: benchA, benchBackfill: reserveA }
135
+ ]
136
+ }));
137
+ (0, globals_1.expect)(res.success).toBe(false);
138
+ if (!res.success) {
139
+ (0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'INJURY_REPLACEMENT_PLAYER_USED_TWICE')).toBe(true);
140
+ }
141
+ });
142
+ });
@@ -123,5 +123,11 @@ export declare const TacticsInputSchema: z.ZodObject<{
123
123
  rotation: z.ZodNumber;
124
124
  order: z.ZodArray<z.ZodCustom<Player, Player>>;
125
125
  }, z.core.$strip>>>>;
126
+ replaceInjuredImmediately: z.ZodDefault<z.ZodBoolean>;
127
+ injuryReplacements: z.ZodDefault<z.ZodArray<z.ZodObject<{
128
+ starter: z.ZodCustom<Player, Player>;
129
+ replacement: z.ZodCustom<Player, Player>;
130
+ benchBackfill: z.ZodOptional<z.ZodCustom<Player, Player>>;
131
+ }, z.core.$strip>>>;
126
132
  }, z.core.$strip>;
127
133
  export type TacticsInput = z.infer<typeof TacticsInputSchema>;
@@ -80,7 +80,16 @@ exports.TacticsInputSchema = zod_1.z.object({
80
80
  // Per-set overrides (5 entries, index 0 = set 1; absent => the flat fields apply to all sets). Each entry is
81
81
  // validated with the same cross-field rules as the flat fields (see superRefine below).
82
82
  systemSets: zod_1.z.array(systemSetSchema).max(5).optional(),
83
- offensivePreferenceSets: zod_1.z.array(zod_1.z.array(offensivePreferenceSchema)).max(5).optional()
83
+ offensivePreferenceSets: zod_1.z.array(zod_1.z.array(offensivePreferenceSchema)).max(5).optional(),
84
+ // Injuries. Defaults keep every existing Tactics.create({...}) call valid. injuryReplacements is the
85
+ // per-starter explicit replacement mapping (owner rules: followed verbatim, bench-only auto fallback,
86
+ // reserves never auto-promoted); cross-field rules in the superRefine below.
87
+ replaceInjuredImmediately: zod_1.z.boolean().default(true),
88
+ injuryReplacements: zod_1.z.array(zod_1.z.object({
89
+ starter: playerInstanceSchema,
90
+ replacement: playerInstanceSchema,
91
+ benchBackfill: playerInstanceSchema.optional()
92
+ })).default([])
84
93
  }).superRefine((data, ctx) => {
85
94
  // Cross-field rules for the rotation system. These keep an invalid config from ever reaching the sim (the
86
95
  // pinch-server outage was caused by an unvalidated tactics payload).
@@ -178,4 +187,41 @@ exports.TacticsInputSchema = zod_1.z.object({
178
187
  }
179
188
  }
180
189
  }
190
+ // Injury replacements (owner rules 2026-07-03). starter = a court starter, at most one entry each.
191
+ // replacement = a bench player or a reserve (never another starter, the libero, or the L2). benchBackfill
192
+ // only when the replacement came from the bench, and must be a RESERVE (outside the whole lineup, not the
193
+ // L2). No player appears as a replacement or backfill more than once across all entries.
194
+ const benchIds = new Set(data.lineup.bench.map((p) => p.id));
195
+ const liberoId = data.lineup[LIBERO_ZONE]?.id;
196
+ const seenInjuryStarters = new Set();
197
+ const seenIncoming = new Set();
198
+ for (const ir of data.injuryReplacements) {
199
+ if (!starterIds.has(ir.starter.id)) {
200
+ ctx.addIssue({ code: 'custom', message: 'INJURY_REPLACEMENT_STARTER_NOT_A_STARTER', path: ['injuryReplacements'] });
201
+ }
202
+ if (seenInjuryStarters.has(ir.starter.id)) {
203
+ ctx.addIssue({ code: 'custom', message: 'INJURY_REPLACEMENT_DUPLICATE_STARTER', path: ['injuryReplacements'] });
204
+ }
205
+ seenInjuryStarters.add(ir.starter.id);
206
+ const isReplacementBench = benchIds.has(ir.replacement.id);
207
+ if (starterIds.has(ir.replacement.id) || ir.replacement.id === liberoId || ir.replacement.id === data.secondLibero?.id) {
208
+ ctx.addIssue({ code: 'custom', message: 'INJURY_REPLACEMENT_MUST_BE_BENCH_OR_RESERVE', path: ['injuryReplacements'] });
209
+ }
210
+ if (seenIncoming.has(ir.replacement.id)) {
211
+ ctx.addIssue({ code: 'custom', message: 'INJURY_REPLACEMENT_PLAYER_USED_TWICE', path: ['injuryReplacements'] });
212
+ }
213
+ seenIncoming.add(ir.replacement.id);
214
+ if (ir.benchBackfill != null) {
215
+ if (!isReplacementBench) {
216
+ ctx.addIssue({ code: 'custom', message: 'BENCH_BACKFILL_REQUIRES_BENCH_REPLACEMENT', path: ['injuryReplacements'] });
217
+ }
218
+ if (inLineupIds.has(ir.benchBackfill.id) || ir.benchBackfill.id === data.secondLibero?.id) {
219
+ ctx.addIssue({ code: 'custom', message: 'BENCH_BACKFILL_MUST_BE_RESERVE', path: ['injuryReplacements'] });
220
+ }
221
+ if (seenIncoming.has(ir.benchBackfill.id)) {
222
+ ctx.addIssue({ code: 'custom', message: 'INJURY_REPLACEMENT_PLAYER_USED_TWICE', path: ['injuryReplacements'] });
223
+ }
224
+ seenIncoming.add(ir.benchBackfill.id);
225
+ }
226
+ }
181
227
  });
@@ -131,6 +131,12 @@ export declare const TeamInputSchema: z.ZodObject<{
131
131
  rotation: z.ZodNumber;
132
132
  order: z.ZodArray<z.ZodCustom<Player, Player>>;
133
133
  }, z.core.$strip>>>>;
134
+ replaceInjuredImmediately: z.ZodDefault<z.ZodBoolean>;
135
+ injuryReplacements: z.ZodDefault<z.ZodArray<z.ZodObject<{
136
+ starter: z.ZodCustom<Player, Player>;
137
+ replacement: z.ZodCustom<Player, Player>;
138
+ benchBackfill: z.ZodOptional<z.ZodCustom<Player, Player>>;
139
+ }, z.core.$strip>>>;
134
140
  }, z.core.$strip>>;
135
141
  }, z.core.$strip>;
136
142
  export type TeamInput = z.infer<typeof TeamInputSchema>;
@@ -5,6 +5,7 @@ import { LiberoSubConfig } from './libero-sub';
5
5
  import { SubBand } from './energy-band';
6
6
  import { RotationSystemEnum } from './rotation-system';
7
7
  import { OffensivePreference } from './offensive-preference';
8
+ import { InjuryReplacement } from './injury-replacement';
8
9
  export interface StartingLineup {
9
10
  readonly [CourtPosition.LEFT_FRONT]: Player;
10
11
  readonly [CourtPosition.MIDDLE_FRONT]: Player;
@@ -33,6 +34,8 @@ export interface TacticsOpts {
33
34
  readonly offensivePreferences: OffensivePreference[];
34
35
  readonly systemSets?: SystemSet[];
35
36
  readonly offensivePreferenceSets?: OffensivePreference[][];
37
+ readonly replaceInjuredImmediately: boolean;
38
+ readonly injuryReplacements: InjuryReplacement[];
36
39
  }
37
40
  export declare class Tactics {
38
41
  readonly lineup: StartingLineup;
@@ -48,6 +51,8 @@ export declare class Tactics {
48
51
  readonly offensivePreferences: OffensivePreference[];
49
52
  readonly systemSets?: SystemSet[];
50
53
  readonly offensivePreferenceSets?: OffensivePreference[][];
54
+ readonly replaceInjuredImmediately: boolean;
55
+ readonly injuryReplacements: InjuryReplacement[];
51
56
  static create(input: unknown): Tactics;
52
57
  private constructor();
53
58
  findPlayerInLineup(id: string): CourtPosition | undefined;
@@ -18,7 +18,7 @@ class Tactics {
18
18
  }
19
19
  return new Tactics(result.data);
20
20
  }
21
- constructor({ lineup, liberoReplacements, receiveRotationOffset = false, designatedSubs = [], substitutionBand = energy_band_1.EnergyBand.TIRED, secondLibero, liberoSub, liberoReplacementSets, rotationSystem = rotation_system_1.RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [], systemSets, offensivePreferenceSets }) {
21
+ constructor({ lineup, liberoReplacements, receiveRotationOffset = false, designatedSubs = [], substitutionBand = energy_band_1.EnergyBand.TIRED, secondLibero, liberoSub, liberoReplacementSets, rotationSystem = rotation_system_1.RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [], systemSets, offensivePreferenceSets, replaceInjuredImmediately = true, injuryReplacements = [] }) {
22
22
  this.lineup = lineup;
23
23
  this.liberoReplacements = liberoReplacements;
24
24
  this.receiveRotationOffset = receiveRotationOffset;
@@ -32,6 +32,8 @@ class Tactics {
32
32
  this.offensivePreferences = offensivePreferences;
33
33
  this.systemSets = systemSets;
34
34
  this.offensivePreferenceSets = offensivePreferenceSets;
35
+ this.replaceInjuredImmediately = replaceInjuredImmediately;
36
+ this.injuryReplacements = injuryReplacements;
35
37
  }
36
38
  findPlayerInLineup(id) {
37
39
  if (this.lineup.bench.some(p => p.id === id))
@@ -54,7 +54,11 @@ export type Match = Omit<DataProps<_Match>, 'sets' | 'homeTeam' | 'awayTeam' | '
54
54
  export type RallyEvent = DataProps<_RallyEvent> & {
55
55
  teamId?: string;
56
56
  };
57
- export type Player = Omit<DataProps<_Player>, 'stats' | 'birthAge' | 'birthIteration' | 'declineProfile' | 'nickname'> & {
57
+ export interface ApiPlayerInjuryStatus {
58
+ severity: number;
59
+ injuredUntil: string;
60
+ }
61
+ export type Player = Omit<DataProps<_Player>, 'stats' | 'birthAge' | 'birthIteration' | 'declineProfile' | 'nickname' | 'injuryProneProfile' | 'injury'> & {
58
62
  -readonly [K in 'birthAge' | 'birthIteration']?: DataProps<_Player>[K];
59
63
  } & {
60
64
  stats?: PerformanceStats;
@@ -62,6 +66,7 @@ export type Player = Omit<DataProps<_Player>, 'stats' | 'birthAge' | 'birthItera
62
66
  jerseyNumber: number;
63
67
  locked?: boolean;
64
68
  declineStatus?: DeclineStatus;
69
+ injuryStatus?: ApiPlayerInjuryStatus;
65
70
  nickname?: string;
66
71
  };
67
72
  export interface StartingLineup {
@@ -100,6 +105,11 @@ export interface ApiSystemSet {
100
105
  rotationSystem: RotationSystemEnum;
101
106
  designatedSetters: string[];
102
107
  }
108
+ export interface ApiInjuryReplacement {
109
+ starterId: string;
110
+ replacementId: string;
111
+ benchBackfillId?: string;
112
+ }
103
113
  export interface ApiLiberoSetSubConfig {
104
114
  mode: LiberoSubMode;
105
115
  fatigueBand?: EnergyBand;
@@ -121,6 +131,8 @@ export interface Tactics {
121
131
  offensivePreferences: ApiOffensivePreference[];
122
132
  systemSets?: ApiSystemSet[];
123
133
  offensivePreferenceSets?: ApiOffensivePreference[][];
134
+ replaceInjuredImmediately: boolean;
135
+ injuryReplacements?: ApiInjuryReplacement[];
124
136
  }
125
137
  export interface ApiLineupPreset {
126
138
  presetId: string;
@@ -1,7 +1,9 @@
1
- import { AuthIdentityModel, AuthSessionModel, AuthUserModel, BoxScoreModel, CoachModel, ScoutModel, BoxScoreTotalsModel, CompetitionChampionModel, CurrencyTransactionModel, GachaPityModel, GachaPullHistoryModel, WishlistModel, UserSettingsModel, UserDeviceTokenModel, CompetitionMatchModel, CompetitionMVPModel, CompetitionModel, CompetitionStandingsMatchModel, CompetitionStandingsModel, CompetitionTeamsModel, CountryModel, DivisionModel, DivisionSeasonModel, IterationModel, LeagueModel, MatchModel, MatchRatingModel, MatchResultModel, MatchSetModel, PerformanceStatsModel, PlayerModel, PlayerImprovementLogModel, PlayerDeclineLogModel, PlayerTeamModel, RetiredPlayerModel, PromotionMatchModel, FriendlyModel, RegionQualifierModel, NationalCountryModel, RallyModel, TacticsModel, LineupPresetModel, MatchPresetModel, TeamModel, TeamReplacementModel, UserCurrencyModel, UserPlayerProgressModel, UserPullGrantModel, UserTeamModel, VPERModel, NotificationModel, AppConfigModel, LegacyTeamFlagModel } from './models';
1
+ import { AuthIdentityModel, AuthSessionModel, AuthUserModel, BoxScoreModel, CoachModel, ScoutModel, PhysicianModel, PhysiotherapistModel, BoxScoreTotalsModel, CompetitionChampionModel, CurrencyTransactionModel, GachaPityModel, GachaPullHistoryModel, WishlistModel, UserSettingsModel, UserDeviceTokenModel, CompetitionMatchModel, CompetitionMVPModel, CompetitionModel, CompetitionStandingsMatchModel, CompetitionStandingsModel, CompetitionTeamsModel, CountryModel, DivisionModel, DivisionSeasonModel, IterationModel, LeagueModel, MatchModel, MatchRatingModel, MatchResultModel, MatchSetModel, PerformanceStatsModel, PlayerModel, PlayerImprovementLogModel, PlayerDeclineLogModel, PlayerTeamModel, RetiredPlayerModel, PromotionMatchModel, FriendlyModel, RegionQualifierModel, NationalCountryModel, RallyModel, TacticsModel, LineupPresetModel, MatchPresetModel, TeamModel, TeamReplacementModel, UserCurrencyModel, UserPlayerProgressModel, UserPullGrantModel, UserTeamModel, VPERModel, NotificationModel, AppConfigModel, LegacyTeamFlagModel } from './models';
2
2
  export function initModels(sequelize) {
3
3
  const Coach = CoachModel.initModel(sequelize);
4
4
  const Scout = ScoutModel.initModel(sequelize);
5
+ const Physician = PhysicianModel.initModel(sequelize);
6
+ const Physiotherapist = PhysiotherapistModel.initModel(sequelize);
5
7
  const CurrencyTransaction = CurrencyTransactionModel.initModel(sequelize);
6
8
  const Tactics = TacticsModel.initModel(sequelize);
7
9
  const LineupPreset = LineupPresetModel.initModel(sequelize);
@@ -208,6 +210,10 @@ export function initModels(sequelize) {
208
210
  Coach.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
209
211
  Team.hasOne(Scout, { as: 'Scout', foreignKey: 'team_id' });
210
212
  Scout.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
213
+ Team.hasOne(Physician, { as: 'Physician', foreignKey: 'team_id' });
214
+ Physician.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
215
+ Team.hasOne(Physiotherapist, { as: 'Physiotherapist', foreignKey: 'team_id' });
216
+ Physiotherapist.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
211
217
  Team.hasOne(Tactics, { as: 'tactics', foreignKey: 'team_id' });
212
218
  // Lineup presets are loaded only by the management API; deliberately NOT in Team.defaultScope so the
213
219
  // simulator's team loads are unchanged (it reads the active config from the mirrored Tactics row).
@@ -307,6 +313,8 @@ export function initModels(sequelize) {
307
313
  Notification,
308
314
  Coach,
309
315
  Scout,
316
+ Physician,
317
+ Physiotherapist,
310
318
  CurrencyTransaction,
311
319
  VPER,
312
320
  LegacyTeamFlag,
@@ -15,9 +15,11 @@ export declare enum CurrencyTransactionSourceEnum {
15
15
  PLAYER_RETIRE = "PLAYER_RETIRE",
16
16
  ADMIN_GRANT = "ADMIN_GRANT",
17
17
  SEASON_REWARD = "SEASON_REWARD",
18
- COMPETITION_REWARD = "COMPETITION_REWARD"
18
+ COMPETITION_REWARD = "COMPETITION_REWARD",
19
+ PHYSICIAN_UPGRADE = "PHYSICIAN_UPGRADE",
20
+ PHYSIOTHERAPIST_UPGRADE = "PHYSIOTHERAPIST_UPGRADE"
19
21
  }
20
- export type CurrencyTransactionSource = CurrencyTransactionSourceEnum.MATCH_REWARD | CurrencyTransactionSourceEnum.GACHA_PULL | CurrencyTransactionSourceEnum.COACH_UPGRADE | CurrencyTransactionSourceEnum.SCOUT_UPGRADE | CurrencyTransactionSourceEnum.PLAYER_DISMISS | CurrencyTransactionSourceEnum.PLAYER_RETIRE | CurrencyTransactionSourceEnum.ADMIN_GRANT | CurrencyTransactionSourceEnum.SEASON_REWARD | CurrencyTransactionSourceEnum.COMPETITION_REWARD;
22
+ export type CurrencyTransactionSource = CurrencyTransactionSourceEnum.MATCH_REWARD | CurrencyTransactionSourceEnum.GACHA_PULL | CurrencyTransactionSourceEnum.COACH_UPGRADE | CurrencyTransactionSourceEnum.SCOUT_UPGRADE | CurrencyTransactionSourceEnum.PLAYER_DISMISS | CurrencyTransactionSourceEnum.PLAYER_RETIRE | CurrencyTransactionSourceEnum.ADMIN_GRANT | CurrencyTransactionSourceEnum.SEASON_REWARD | CurrencyTransactionSourceEnum.COMPETITION_REWARD | CurrencyTransactionSourceEnum.PHYSICIAN_UPGRADE | CurrencyTransactionSourceEnum.PHYSIOTHERAPIST_UPGRADE;
21
23
  export interface CurrencyTransactionAttributes {
22
24
  transaction_id: string;
23
25
  user_id: string;
@@ -15,6 +15,8 @@ export var CurrencyTransactionSourceEnum;
15
15
  CurrencyTransactionSourceEnum["ADMIN_GRANT"] = "ADMIN_GRANT";
16
16
  CurrencyTransactionSourceEnum["SEASON_REWARD"] = "SEASON_REWARD";
17
17
  CurrencyTransactionSourceEnum["COMPETITION_REWARD"] = "COMPETITION_REWARD";
18
+ CurrencyTransactionSourceEnum["PHYSICIAN_UPGRADE"] = "PHYSICIAN_UPGRADE";
19
+ CurrencyTransactionSourceEnum["PHYSIOTHERAPIST_UPGRADE"] = "PHYSIOTHERAPIST_UPGRADE";
18
20
  })(CurrencyTransactionSourceEnum || (CurrencyTransactionSourceEnum = {}));
19
21
  export class CurrencyTransactionModel extends Model {
20
22
  static initModel(sequelize) {
@@ -32,6 +32,8 @@ export * from './region-qualifier';
32
32
  export * from './national-country';
33
33
  export * from './coach';
34
34
  export * from './scout';
35
+ export * from './physician';
36
+ export * from './physiotherapist';
35
37
  export * from './team';
36
38
  export * from './team-replacement';
37
39
  export * from './tactics';
@@ -32,6 +32,8 @@ export * from './region-qualifier';
32
32
  export * from './national-country';
33
33
  export * from './coach';
34
34
  export * from './scout';
35
+ export * from './physician';
36
+ export * from './physiotherapist';
35
37
  export * from './team';
36
38
  export * from './team-replacement';
37
39
  export * from './tactics';
@@ -9,6 +9,7 @@ export interface MatchSetAttributes {
9
9
  order: number;
10
10
  home_score: number;
11
11
  away_score: number;
12
+ forfeited?: boolean;
12
13
  player_positions: PlayerPositionAttributes[];
13
14
  Rallies?: RallyAttributes[];
14
15
  BoxScores?: BoxScoreAttributes[];
@@ -23,6 +24,7 @@ export declare class MatchSetModel extends Model<MatchSetAttributes, MatchSetCre
23
24
  order: number;
24
25
  home_score: number;
25
26
  away_score: number;
27
+ forfeited: boolean;
26
28
  player_positions: PlayerPositionAttributes[];
27
29
  match: MatchModel;
28
30
  getMatch: Sequelize.BelongsToGetAssociationMixin<MatchModel>;
@@ -32,6 +32,11 @@ export class MatchSetModel extends Model {
32
32
  type: DataTypes.INTEGER,
33
33
  allowNull: false
34
34
  },
35
+ forfeited: {
36
+ type: DataTypes.BOOLEAN,
37
+ allowNull: false,
38
+ defaultValue: false
39
+ },
35
40
  player_positions: {
36
41
  type: DataTypes.JSONB,
37
42
  allowNull: false,
@@ -2,7 +2,7 @@ import * as Sequelize from 'sequelize';
2
2
  import { Model } from 'sequelize';
3
3
  import { Status } from '../common';
4
4
  import { CompetitionMatchId, CompetitionMatchModel, MatchRatingId, MatchRatingModel, MatchResultId, MatchResultModel, MatchPresetModel, MatchSetAttributes, MatchSetId, MatchSetModel, VPERAttributes, VPERId, VPERModel, TeamId, TeamModel } from '.';
5
- import { MatchRallyAggregates } from '../../service';
5
+ import { MatchForfeitType, MatchRallyAggregates } from '../../service';
6
6
  export interface MatchAttributes {
7
7
  match_id: string;
8
8
  home_team: string;
@@ -13,6 +13,7 @@ export interface MatchAttributes {
13
13
  in_progress_at?: Date;
14
14
  complete_at?: Date;
15
15
  rally_aggregates?: MatchRallyAggregates | null;
16
+ forfeit_type?: MatchForfeitType | null;
16
17
  MatchSets?: MatchSetAttributes[];
17
18
  VPERs?: VPERAttributes[];
18
19
  }
@@ -29,6 +30,7 @@ export declare class MatchModel extends Model<MatchAttributes, MatchCreationAttr
29
30
  in_progress_at?: Date;
30
31
  complete_at?: Date;
31
32
  rally_aggregates?: MatchRallyAggregates | null;
33
+ forfeit_type?: MatchForfeitType | null;
32
34
  CompetitionMatch: CompetitionMatchModel;
33
35
  getCompetitionMatch: Sequelize.HasOneGetAssociationMixin<CompetitionMatchModel>;
34
36
  setCompetitionMatch: Sequelize.HasOneSetAssociationMixin<CompetitionMatchModel, CompetitionMatchId>;
@@ -48,6 +48,10 @@ export class MatchModel extends Model {
48
48
  rally_aggregates: {
49
49
  type: DataTypes.JSONB,
50
50
  allowNull: true
51
+ },
52
+ forfeit_type: {
53
+ type: DataTypes.TEXT,
54
+ allowNull: true
51
55
  }
52
56
  }, {
53
57
  sequelize,
@@ -0,0 +1,25 @@
1
+ import * as Sequelize from 'sequelize';
2
+ import { Model } from 'sequelize';
3
+ import { TeamId, TeamModel } from '.';
4
+ export interface PhysicianAttributes {
5
+ physician_id: string;
6
+ team_id: string;
7
+ name: string;
8
+ rarity: string;
9
+ currency_invested: number;
10
+ }
11
+ export type PhysicianPk = 'physician_id';
12
+ export type PhysicianId = PhysicianModel[PhysicianPk];
13
+ export type PhysicianCreationAttributes = PhysicianAttributes;
14
+ export declare class PhysicianModel extends Model<PhysicianAttributes, PhysicianCreationAttributes> implements PhysicianAttributes {
15
+ physician_id: string;
16
+ team_id: string;
17
+ name: string;
18
+ rarity: string;
19
+ currency_invested: number;
20
+ team: TeamModel;
21
+ getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
22
+ setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
23
+ createTeam: Sequelize.BelongsToCreateAssociationMixin<TeamModel>;
24
+ static initModel(sequelize: Sequelize.Sequelize): typeof PhysicianModel;
25
+ }
@@ -0,0 +1,53 @@
1
+ import { DataTypes, Model } from 'sequelize';
2
+ export class PhysicianModel extends Model {
3
+ static initModel(sequelize) {
4
+ return PhysicianModel.init({
5
+ physician_id: {
6
+ type: DataTypes.UUID,
7
+ allowNull: false,
8
+ primaryKey: true
9
+ },
10
+ team_id: {
11
+ type: DataTypes.UUID,
12
+ allowNull: false,
13
+ unique: true,
14
+ references: {
15
+ model: 'Team',
16
+ key: 'team_id'
17
+ }
18
+ },
19
+ name: {
20
+ type: DataTypes.STRING,
21
+ allowNull: false,
22
+ defaultValue: ''
23
+ },
24
+ rarity: {
25
+ type: DataTypes.STRING,
26
+ allowNull: false,
27
+ defaultValue: 'COMMON'
28
+ },
29
+ currency_invested: {
30
+ type: DataTypes.INTEGER,
31
+ allowNull: false,
32
+ defaultValue: 0
33
+ }
34
+ }, {
35
+ sequelize,
36
+ tableName: 'Physician',
37
+ schema: 'public',
38
+ timestamps: false,
39
+ indexes: [
40
+ {
41
+ name: 'Physician_pk',
42
+ unique: true,
43
+ fields: [{ name: 'physician_id' }]
44
+ },
45
+ {
46
+ name: 'Physician_team_id_unique',
47
+ unique: true,
48
+ fields: [{ name: 'team_id' }]
49
+ }
50
+ ]
51
+ });
52
+ }
53
+ }
@@ -0,0 +1,25 @@
1
+ import * as Sequelize from 'sequelize';
2
+ import { Model } from 'sequelize';
3
+ import { TeamId, TeamModel } from '.';
4
+ export interface PhysiotherapistAttributes {
5
+ physiotherapist_id: string;
6
+ team_id: string;
7
+ name: string;
8
+ rarity: string;
9
+ currency_invested: number;
10
+ }
11
+ export type PhysiotherapistPk = 'physiotherapist_id';
12
+ export type PhysiotherapistId = PhysiotherapistModel[PhysiotherapistPk];
13
+ export type PhysiotherapistCreationAttributes = PhysiotherapistAttributes;
14
+ export declare class PhysiotherapistModel extends Model<PhysiotherapistAttributes, PhysiotherapistCreationAttributes> implements PhysiotherapistAttributes {
15
+ physiotherapist_id: string;
16
+ team_id: string;
17
+ name: string;
18
+ rarity: string;
19
+ currency_invested: number;
20
+ team: TeamModel;
21
+ getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
22
+ setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
23
+ createTeam: Sequelize.BelongsToCreateAssociationMixin<TeamModel>;
24
+ static initModel(sequelize: Sequelize.Sequelize): typeof PhysiotherapistModel;
25
+ }