volleyballsimtypes 0.0.500 → 0.0.502
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/data/models/box-score.d.ts +12 -0
- package/dist/cjs/src/data/models/box-score.js +30 -0
- package/dist/cjs/src/data/models/views/box-score-totals.d.ts +12 -0
- package/dist/cjs/src/data/models/views/box-score-totals.js +24 -0
- package/dist/cjs/src/data/transformers/box-score.js +13 -1
- package/dist/cjs/src/data/transformers/player.js +7 -1
- package/dist/cjs/src/data/transformers/rally-event-positional.test.d.ts +1 -0
- package/dist/cjs/src/data/transformers/rally-event-positional.test.js +56 -0
- package/dist/cjs/src/data/transformers/rally-event.d.ts +4 -0
- package/dist/cjs/src/data/transformers/rally-event.js +34 -6
- package/dist/cjs/src/service/competition/event-rewards.d.ts +3 -2
- package/dist/cjs/src/service/competition/event-rewards.js +5 -3
- package/dist/cjs/src/service/competition/event-rewards.test.js +9 -9
- package/dist/cjs/src/service/event/block.js +2 -2
- package/dist/cjs/src/service/event/free-ball.js +2 -2
- package/dist/cjs/src/service/event/in-play-event.d.ts +19 -1
- package/dist/cjs/src/service/event/in-play-event.js +17 -2
- package/dist/cjs/src/service/event/reception.js +2 -2
- package/dist/cjs/src/service/event/schemas/block.z.d.ts +10 -0
- package/dist/cjs/src/service/event/schemas/block.z.js +3 -1
- package/dist/cjs/src/service/event/schemas/free-ball.z.d.ts +10 -0
- package/dist/cjs/src/service/event/schemas/free-ball.z.js +3 -1
- package/dist/cjs/src/service/event/schemas/positional.z.d.ts +17 -0
- package/dist/cjs/src/service/event/schemas/positional.z.js +18 -0
- package/dist/cjs/src/service/event/schemas/reception.z.d.ts +10 -0
- package/dist/cjs/src/service/event/schemas/reception.z.js +3 -1
- package/dist/cjs/src/service/event/schemas/serve.z.d.ts +10 -0
- package/dist/cjs/src/service/event/schemas/serve.z.js +3 -1
- package/dist/cjs/src/service/event/schemas/set.z.d.ts +10 -0
- package/dist/cjs/src/service/event/schemas/set.z.js +3 -1
- package/dist/cjs/src/service/event/schemas/spike.z.d.ts +10 -0
- package/dist/cjs/src/service/event/schemas/spike.z.js +3 -1
- package/dist/cjs/src/service/event/serve.js +2 -2
- package/dist/cjs/src/service/event/set.js +2 -2
- package/dist/cjs/src/service/event/spike.js +2 -2
- package/dist/cjs/src/service/match/box-score.d.ts +6 -0
- package/dist/cjs/src/service/match/box-score.js +7 -1
- package/dist/cjs/src/service/match/schemas/box-score.z.d.ts +12 -0
- package/dist/cjs/src/service/match/schemas/box-score.z.js +15 -2
- package/dist/cjs/src/service/player/schemas/player.z.d.ts +12 -0
- package/dist/cjs/src/service/player/trait.d.ts +1 -0
- package/dist/cjs/src/stat-config/traits.json +55 -11
- package/dist/esm/src/data/models/box-score.d.ts +12 -0
- package/dist/esm/src/data/models/box-score.js +30 -0
- package/dist/esm/src/data/models/views/box-score-totals.d.ts +12 -0
- package/dist/esm/src/data/models/views/box-score-totals.js +24 -0
- package/dist/esm/src/data/transformers/box-score.js +13 -1
- package/dist/esm/src/data/transformers/player.js +7 -1
- package/dist/esm/src/data/transformers/rally-event-positional.test.d.ts +1 -0
- package/dist/esm/src/data/transformers/rally-event-positional.test.js +54 -0
- package/dist/esm/src/data/transformers/rally-event.d.ts +4 -0
- package/dist/esm/src/data/transformers/rally-event.js +34 -6
- package/dist/esm/src/service/competition/event-rewards.d.ts +3 -2
- package/dist/esm/src/service/competition/event-rewards.js +5 -3
- package/dist/esm/src/service/competition/event-rewards.test.js +9 -9
- package/dist/esm/src/service/event/block.js +2 -2
- package/dist/esm/src/service/event/free-ball.js +2 -2
- package/dist/esm/src/service/event/in-play-event.d.ts +19 -1
- package/dist/esm/src/service/event/in-play-event.js +16 -1
- package/dist/esm/src/service/event/reception.js +2 -2
- package/dist/esm/src/service/event/schemas/block.z.d.ts +10 -0
- package/dist/esm/src/service/event/schemas/block.z.js +3 -1
- package/dist/esm/src/service/event/schemas/free-ball.z.d.ts +10 -0
- package/dist/esm/src/service/event/schemas/free-ball.z.js +3 -1
- package/dist/esm/src/service/event/schemas/positional.z.d.ts +17 -0
- package/dist/esm/src/service/event/schemas/positional.z.js +15 -0
- package/dist/esm/src/service/event/schemas/reception.z.d.ts +10 -0
- package/dist/esm/src/service/event/schemas/reception.z.js +3 -1
- package/dist/esm/src/service/event/schemas/serve.z.d.ts +10 -0
- package/dist/esm/src/service/event/schemas/serve.z.js +3 -1
- package/dist/esm/src/service/event/schemas/set.z.d.ts +10 -0
- package/dist/esm/src/service/event/schemas/set.z.js +3 -1
- package/dist/esm/src/service/event/schemas/spike.z.d.ts +10 -0
- package/dist/esm/src/service/event/schemas/spike.z.js +3 -1
- package/dist/esm/src/service/event/serve.js +2 -2
- package/dist/esm/src/service/event/set.js +2 -2
- package/dist/esm/src/service/event/spike.js +2 -2
- package/dist/esm/src/service/match/box-score.d.ts +6 -0
- package/dist/esm/src/service/match/box-score.js +7 -1
- package/dist/esm/src/service/match/schemas/box-score.z.d.ts +12 -0
- package/dist/esm/src/service/match/schemas/box-score.z.js +15 -2
- package/dist/esm/src/service/player/schemas/player.z.d.ts +12 -0
- package/dist/esm/src/service/player/trait.d.ts +1 -0
- package/dist/esm/src/stat-config/traits.json +55 -11
- package/package.json +1 -1
|
@@ -36,6 +36,11 @@ export class BoxScoreModel extends Model {
|
|
|
36
36
|
allowNull: false,
|
|
37
37
|
defaultValue: 0
|
|
38
38
|
},
|
|
39
|
+
attack_blocked: {
|
|
40
|
+
type: DataTypes.SMALLINT,
|
|
41
|
+
allowNull: false,
|
|
42
|
+
defaultValue: 0
|
|
43
|
+
},
|
|
39
44
|
// Serve
|
|
40
45
|
serve_attempts: {
|
|
41
46
|
type: DataTypes.SMALLINT,
|
|
@@ -68,6 +73,16 @@ export class BoxScoreModel extends Model {
|
|
|
68
73
|
allowNull: false,
|
|
69
74
|
defaultValue: 0
|
|
70
75
|
},
|
|
76
|
+
reception_ok: {
|
|
77
|
+
type: DataTypes.SMALLINT,
|
|
78
|
+
allowNull: false,
|
|
79
|
+
defaultValue: 0
|
|
80
|
+
},
|
|
81
|
+
reception_negative: {
|
|
82
|
+
type: DataTypes.SMALLINT,
|
|
83
|
+
allowNull: false,
|
|
84
|
+
defaultValue: 0
|
|
85
|
+
},
|
|
71
86
|
reception_overpasses: {
|
|
72
87
|
type: DataTypes.SMALLINT,
|
|
73
88
|
allowNull: false,
|
|
@@ -121,6 +136,21 @@ export class BoxScoreModel extends Model {
|
|
|
121
136
|
allowNull: false,
|
|
122
137
|
defaultValue: 0
|
|
123
138
|
},
|
|
139
|
+
defense_digs_perfect: {
|
|
140
|
+
type: DataTypes.SMALLINT,
|
|
141
|
+
allowNull: false,
|
|
142
|
+
defaultValue: 0
|
|
143
|
+
},
|
|
144
|
+
defense_digs_mediocre: {
|
|
145
|
+
type: DataTypes.SMALLINT,
|
|
146
|
+
allowNull: false,
|
|
147
|
+
defaultValue: 0
|
|
148
|
+
},
|
|
149
|
+
defense_digs_bad: {
|
|
150
|
+
type: DataTypes.SMALLINT,
|
|
151
|
+
allowNull: false,
|
|
152
|
+
defaultValue: 0
|
|
153
|
+
},
|
|
124
154
|
defense_errors: {
|
|
125
155
|
type: DataTypes.SMALLINT,
|
|
126
156
|
allowNull: false,
|
|
@@ -6,12 +6,15 @@ export interface BoxScoreTotalsAttributes {
|
|
|
6
6
|
attack_attempts: number;
|
|
7
7
|
attack_kills: number;
|
|
8
8
|
attack_errors: number;
|
|
9
|
+
attack_blocked: number;
|
|
9
10
|
serve_attempts: number;
|
|
10
11
|
serve_aces: number;
|
|
11
12
|
serve_errors: number;
|
|
12
13
|
reception_attempts: number;
|
|
13
14
|
reception_perfect: number;
|
|
14
15
|
reception_positive: number;
|
|
16
|
+
reception_ok: number;
|
|
17
|
+
reception_negative: number;
|
|
15
18
|
reception_overpasses: number;
|
|
16
19
|
reception_errors: number;
|
|
17
20
|
block_solo: number;
|
|
@@ -22,6 +25,9 @@ export interface BoxScoreTotalsAttributes {
|
|
|
22
25
|
setting_errors: number;
|
|
23
26
|
setting_pass: number;
|
|
24
27
|
defense_digs: number;
|
|
28
|
+
defense_digs_perfect: number;
|
|
29
|
+
defense_digs_mediocre: number;
|
|
30
|
+
defense_digs_bad: number;
|
|
25
31
|
defense_errors: number;
|
|
26
32
|
misc_points_played: number;
|
|
27
33
|
misc_points_won_on_court: number;
|
|
@@ -39,12 +45,15 @@ export declare class BoxScoreTotalsModel extends Model<BoxScoreTotalsAttributes,
|
|
|
39
45
|
attack_attempts: number;
|
|
40
46
|
attack_kills: number;
|
|
41
47
|
attack_errors: number;
|
|
48
|
+
attack_blocked: number;
|
|
42
49
|
serve_attempts: number;
|
|
43
50
|
serve_aces: number;
|
|
44
51
|
serve_errors: number;
|
|
45
52
|
reception_attempts: number;
|
|
46
53
|
reception_perfect: number;
|
|
47
54
|
reception_positive: number;
|
|
55
|
+
reception_ok: number;
|
|
56
|
+
reception_negative: number;
|
|
48
57
|
reception_overpasses: number;
|
|
49
58
|
reception_errors: number;
|
|
50
59
|
block_solo: number;
|
|
@@ -55,6 +64,9 @@ export declare class BoxScoreTotalsModel extends Model<BoxScoreTotalsAttributes,
|
|
|
55
64
|
setting_errors: number;
|
|
56
65
|
setting_pass: number;
|
|
57
66
|
defense_digs: number;
|
|
67
|
+
defense_digs_perfect: number;
|
|
68
|
+
defense_digs_mediocre: number;
|
|
69
|
+
defense_digs_bad: number;
|
|
58
70
|
defense_errors: number;
|
|
59
71
|
misc_points_played: number;
|
|
60
72
|
misc_points_won_on_court: number;
|
|
@@ -23,6 +23,10 @@ export class BoxScoreTotalsModel extends Model {
|
|
|
23
23
|
type: DataTypes.INTEGER,
|
|
24
24
|
allowNull: false
|
|
25
25
|
},
|
|
26
|
+
attack_blocked: {
|
|
27
|
+
type: DataTypes.INTEGER,
|
|
28
|
+
allowNull: false
|
|
29
|
+
},
|
|
26
30
|
serve_attempts: {
|
|
27
31
|
type: DataTypes.INTEGER,
|
|
28
32
|
allowNull: false
|
|
@@ -47,6 +51,14 @@ export class BoxScoreTotalsModel extends Model {
|
|
|
47
51
|
type: DataTypes.INTEGER,
|
|
48
52
|
allowNull: false
|
|
49
53
|
},
|
|
54
|
+
reception_ok: {
|
|
55
|
+
type: DataTypes.INTEGER,
|
|
56
|
+
allowNull: false
|
|
57
|
+
},
|
|
58
|
+
reception_negative: {
|
|
59
|
+
type: DataTypes.INTEGER,
|
|
60
|
+
allowNull: false
|
|
61
|
+
},
|
|
50
62
|
reception_overpasses: {
|
|
51
63
|
type: DataTypes.INTEGER,
|
|
52
64
|
allowNull: false
|
|
@@ -87,6 +99,18 @@ export class BoxScoreTotalsModel extends Model {
|
|
|
87
99
|
type: DataTypes.INTEGER,
|
|
88
100
|
allowNull: false
|
|
89
101
|
},
|
|
102
|
+
defense_digs_perfect: {
|
|
103
|
+
type: DataTypes.INTEGER,
|
|
104
|
+
allowNull: false
|
|
105
|
+
},
|
|
106
|
+
defense_digs_mediocre: {
|
|
107
|
+
type: DataTypes.INTEGER,
|
|
108
|
+
allowNull: false
|
|
109
|
+
},
|
|
110
|
+
defense_digs_bad: {
|
|
111
|
+
type: DataTypes.INTEGER,
|
|
112
|
+
allowNull: false
|
|
113
|
+
},
|
|
90
114
|
defense_errors: {
|
|
91
115
|
type: DataTypes.INTEGER,
|
|
92
116
|
allowNull: false
|
|
@@ -6,12 +6,15 @@ function transformToAttributes(boxScore) {
|
|
|
6
6
|
attack_attempts: boxScore.attack.attempts,
|
|
7
7
|
attack_kills: boxScore.attack.kills,
|
|
8
8
|
attack_errors: boxScore.attack.errors,
|
|
9
|
+
attack_blocked: boxScore.attack.blocked,
|
|
9
10
|
serve_attempts: boxScore.serve.attempts,
|
|
10
11
|
serve_aces: boxScore.serve.aces,
|
|
11
12
|
serve_errors: boxScore.serve.errors,
|
|
12
13
|
reception_attempts: boxScore.reception.attempts,
|
|
13
14
|
reception_perfect: boxScore.reception.perfect,
|
|
14
15
|
reception_positive: boxScore.reception.positive,
|
|
16
|
+
reception_ok: boxScore.reception.ok,
|
|
17
|
+
reception_negative: boxScore.reception.negative,
|
|
15
18
|
reception_overpasses: boxScore.reception.overpasses,
|
|
16
19
|
reception_errors: boxScore.reception.errors,
|
|
17
20
|
block_solo: boxScore.block.solo,
|
|
@@ -22,6 +25,9 @@ function transformToAttributes(boxScore) {
|
|
|
22
25
|
setting_errors: boxScore.setting.errors,
|
|
23
26
|
setting_pass: boxScore.setting.pass,
|
|
24
27
|
defense_digs: boxScore.defense.digs,
|
|
28
|
+
defense_digs_perfect: boxScore.defense.digsPerfect,
|
|
29
|
+
defense_digs_mediocre: boxScore.defense.digsMediocre,
|
|
30
|
+
defense_digs_bad: boxScore.defense.digsBad,
|
|
25
31
|
defense_errors: boxScore.defense.errors,
|
|
26
32
|
misc_points_played: boxScore.misc.pointsPlayed,
|
|
27
33
|
misc_points_won_on_court: boxScore.misc.pointsWonOnCourt,
|
|
@@ -37,7 +43,8 @@ function transformToObject(model) {
|
|
|
37
43
|
attack: {
|
|
38
44
|
attempts: model.attack_attempts,
|
|
39
45
|
kills: model.attack_kills,
|
|
40
|
-
errors: model.attack_errors
|
|
46
|
+
errors: model.attack_errors,
|
|
47
|
+
blocked: model.attack_blocked
|
|
41
48
|
},
|
|
42
49
|
serve: {
|
|
43
50
|
attempts: model.serve_attempts,
|
|
@@ -48,6 +55,8 @@ function transformToObject(model) {
|
|
|
48
55
|
attempts: model.reception_attempts,
|
|
49
56
|
perfect: model.reception_perfect,
|
|
50
57
|
positive: model.reception_positive,
|
|
58
|
+
ok: model.reception_ok,
|
|
59
|
+
negative: model.reception_negative,
|
|
51
60
|
overpasses: model.reception_overpasses,
|
|
52
61
|
errors: model.reception_errors
|
|
53
62
|
},
|
|
@@ -64,6 +73,9 @@ function transformToObject(model) {
|
|
|
64
73
|
},
|
|
65
74
|
defense: {
|
|
66
75
|
digs: model.defense_digs,
|
|
76
|
+
digsPerfect: model.defense_digs_perfect,
|
|
77
|
+
digsMediocre: model.defense_digs_mediocre,
|
|
78
|
+
digsBad: model.defense_digs_bad,
|
|
67
79
|
errors: model.defense_errors
|
|
68
80
|
},
|
|
69
81
|
misc: {
|
|
@@ -33,7 +33,8 @@ function transformToBoxScoreTotals(model) {
|
|
|
33
33
|
attack: {
|
|
34
34
|
attempts: toNumberStrict(model.attack_attempts, 'attack_attempts'),
|
|
35
35
|
kills: toNumberStrict(model.attack_kills, 'attack_kills'),
|
|
36
|
-
errors: toNumberStrict(model.attack_errors, 'attack_errors')
|
|
36
|
+
errors: toNumberStrict(model.attack_errors, 'attack_errors'),
|
|
37
|
+
blocked: toNumberStrict(model.attack_blocked, 'attack_blocked')
|
|
37
38
|
},
|
|
38
39
|
serve: {
|
|
39
40
|
attempts: toNumberStrict(model.serve_attempts, 'serve_attempts'),
|
|
@@ -44,6 +45,8 @@ function transformToBoxScoreTotals(model) {
|
|
|
44
45
|
attempts: toNumberStrict(model.reception_attempts, 'reception_attempts'),
|
|
45
46
|
perfect: toNumberStrict(model.reception_perfect, 'reception_perfect'),
|
|
46
47
|
positive: toNumberStrict(model.reception_positive, 'reception_positive'),
|
|
48
|
+
ok: toNumberStrict(model.reception_ok, 'reception_ok'),
|
|
49
|
+
negative: toNumberStrict(model.reception_negative, 'reception_negative'),
|
|
47
50
|
overpasses: toNumberStrict(model.reception_overpasses, 'reception_overpasses'),
|
|
48
51
|
errors: toNumberStrict(model.reception_errors, 'reception_errors')
|
|
49
52
|
},
|
|
@@ -60,6 +63,9 @@ function transformToBoxScoreTotals(model) {
|
|
|
60
63
|
},
|
|
61
64
|
defense: {
|
|
62
65
|
digs: toNumberStrict(model.defense_digs, 'defense_digs'),
|
|
66
|
+
digsPerfect: toNumberStrict(model.defense_digs_perfect, 'defense_digs_perfect'),
|
|
67
|
+
digsMediocre: toNumberStrict(model.defense_digs_mediocre, 'defense_digs_mediocre'),
|
|
68
|
+
digsBad: toNumberStrict(model.defense_digs_bad, 'defense_digs_bad'),
|
|
63
69
|
errors: toNumberStrict(model.defense_errors, 'defense_errors')
|
|
64
70
|
},
|
|
65
71
|
misc: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import { DigQualityEnum, Reception, ReceptionFailureEnum, ReceptionTypeEnum, Spike, SpikeFailureEnum, SpikeTypeEnum } from '../../service';
|
|
4
|
+
import { transformToCompact, transformToReception, transformToSpike } from './rally-event';
|
|
5
|
+
describe('rally-event compact round trip -- positional fields (coords / speed / dig quality)', () => {
|
|
6
|
+
const playerId = uuidv4();
|
|
7
|
+
const roster = [playerId];
|
|
8
|
+
const playerIndex = new Map(roster.map((id, i) => [id, i]));
|
|
9
|
+
it('packs contact/landing/speed and round-trips them, 2-dp rounded', () => {
|
|
10
|
+
const spike = Spike.create({
|
|
11
|
+
playerId,
|
|
12
|
+
score: 60,
|
|
13
|
+
target: 4,
|
|
14
|
+
failure: SpikeFailureEnum.NO_FAILURE,
|
|
15
|
+
type: SpikeTypeEnum.CROSS_SHOT,
|
|
16
|
+
contact: { x: 1.234, y: -0.5 },
|
|
17
|
+
landing: { x: -2.5, y: 6.789 },
|
|
18
|
+
speed: 92.456
|
|
19
|
+
});
|
|
20
|
+
const compact = transformToCompact(spike, playerIndex);
|
|
21
|
+
expect(compact.c).toEqual([1.23, -0.5]);
|
|
22
|
+
expect(compact.g).toEqual([-2.5, 6.79]);
|
|
23
|
+
expect(compact.v).toBe(92.46);
|
|
24
|
+
const decoded = transformToSpike(compact, roster);
|
|
25
|
+
expect(decoded.contact).toEqual({ x: 1.23, y: -0.5 });
|
|
26
|
+
expect(decoded.landing).toEqual({ x: -2.5, y: 6.79 });
|
|
27
|
+
expect(decoded.speed).toBe(92.46);
|
|
28
|
+
});
|
|
29
|
+
it('packs dig quality on a reception and round-trips it', () => {
|
|
30
|
+
const dig = Reception.create({
|
|
31
|
+
playerId,
|
|
32
|
+
score: 40,
|
|
33
|
+
target: 3,
|
|
34
|
+
failure: ReceptionFailureEnum.NO_FAILURE,
|
|
35
|
+
type: ReceptionTypeEnum.DIG,
|
|
36
|
+
digQuality: DigQualityEnum.MEDIOCRE
|
|
37
|
+
});
|
|
38
|
+
const compact = transformToCompact(dig, playerIndex);
|
|
39
|
+
expect(compact.q).toBe(DigQualityEnum.MEDIOCRE);
|
|
40
|
+
const decoded = transformToReception(compact, roster);
|
|
41
|
+
expect(decoded.digQuality).toBe(DigQualityEnum.MEDIOCRE);
|
|
42
|
+
});
|
|
43
|
+
it('omits the positional keys when absent, and a legacy event decodes them as undefined', () => {
|
|
44
|
+
const spike = Spike.create({ playerId, score: 60, target: 4, failure: SpikeFailureEnum.NO_FAILURE, type: SpikeTypeEnum.CROSS_SHOT });
|
|
45
|
+
const compact = transformToCompact(spike, playerIndex);
|
|
46
|
+
expect('c' in compact).toBe(false);
|
|
47
|
+
expect('v' in compact).toBe(false);
|
|
48
|
+
expect('q' in compact).toBe(false);
|
|
49
|
+
const decoded = transformToSpike(compact, roster);
|
|
50
|
+
expect(decoded.contact).toBeUndefined();
|
|
51
|
+
expect(decoded.speed).toBeUndefined();
|
|
52
|
+
expect(decoded.digQuality).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -13,6 +13,10 @@ export interface CompactEvent {
|
|
|
13
13
|
i?: number;
|
|
14
14
|
h?: number;
|
|
15
15
|
m?: number;
|
|
16
|
+
c?: number[];
|
|
17
|
+
g?: number[];
|
|
18
|
+
v?: number;
|
|
19
|
+
q?: number;
|
|
16
20
|
}
|
|
17
21
|
export declare function transformToCompact(evt: RallyEvent, playerIndex: Map<string, number>): CompactEvent;
|
|
18
22
|
export declare function transformToBlock(event: CompactEvent, roster: string[]): Block;
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { Block, FreeBall, InPlayEvent, LiberoReplacement, Reception, Serve, Set, Spike, Substitution } from '../../service';
|
|
2
|
+
// Rebuild the optional positional fields (contact / landing / speed / digQuality) from a compact event, for the
|
|
3
|
+
// event .create() inputs. Loosely typed since create() validates via its Zod schema.
|
|
4
|
+
function positionalFromCompact(e) {
|
|
5
|
+
const out = {};
|
|
6
|
+
if (e.c != null)
|
|
7
|
+
out.contact = { x: e.c[0], y: e.c[1] };
|
|
8
|
+
if (e.g != null)
|
|
9
|
+
out.landing = { x: e.g[0], y: e.g[1] };
|
|
10
|
+
if (e.v != null)
|
|
11
|
+
out.speed = e.v;
|
|
12
|
+
if (e.q != null)
|
|
13
|
+
out.digQuality = e.q;
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
2
16
|
// Incident packing for the compact key 'i': values below 10 are injury severities, 10+ are knock tiers.
|
|
3
17
|
function incidentToCompact(incident) {
|
|
4
18
|
if (incident == null)
|
|
@@ -31,6 +45,14 @@ export function transformToCompact(evt, playerIndex) {
|
|
|
31
45
|
out.h = playerIndex.get(evt.incident.playerId);
|
|
32
46
|
}
|
|
33
47
|
}
|
|
48
|
+
if (evt.contact != null)
|
|
49
|
+
out.c = [evt.contact.x, evt.contact.y];
|
|
50
|
+
if (evt.landing != null)
|
|
51
|
+
out.g = [evt.landing.x, evt.landing.y];
|
|
52
|
+
if (evt.speed != null)
|
|
53
|
+
out.v = evt.speed;
|
|
54
|
+
if (evt.digQuality != null)
|
|
55
|
+
out.q = evt.digQuality;
|
|
34
56
|
if (evt instanceof Block) {
|
|
35
57
|
out.b = evt.blockers.map(id => playerIndex.get(id));
|
|
36
58
|
}
|
|
@@ -57,7 +79,8 @@ export function transformToBlock(event, roster) {
|
|
|
57
79
|
target: event.a,
|
|
58
80
|
blockers: (event.b ?? []).map(i => roster[i]),
|
|
59
81
|
score: event.s,
|
|
60
|
-
incident: incidentFromCompact(event.i, event.h, roster)
|
|
82
|
+
incident: incidentFromCompact(event.i, event.h, roster),
|
|
83
|
+
...positionalFromCompact(event)
|
|
61
84
|
});
|
|
62
85
|
}
|
|
63
86
|
export function transformToLiberoReplacement(event, roster) {
|
|
@@ -74,7 +97,8 @@ export function transformToReception(event, roster) {
|
|
|
74
97
|
playerId: roster[event.p],
|
|
75
98
|
target: event.a,
|
|
76
99
|
score: event.s,
|
|
77
|
-
incident: incidentFromCompact(event.i, event.h, roster)
|
|
100
|
+
incident: incidentFromCompact(event.i, event.h, roster),
|
|
101
|
+
...positionalFromCompact(event)
|
|
78
102
|
});
|
|
79
103
|
}
|
|
80
104
|
export function transformToServe(event, roster) {
|
|
@@ -84,7 +108,8 @@ export function transformToServe(event, roster) {
|
|
|
84
108
|
playerId: roster[event.p],
|
|
85
109
|
target: event.a,
|
|
86
110
|
score: event.s,
|
|
87
|
-
incident: incidentFromCompact(event.i, event.h, roster)
|
|
111
|
+
incident: incidentFromCompact(event.i, event.h, roster),
|
|
112
|
+
...positionalFromCompact(event)
|
|
88
113
|
});
|
|
89
114
|
}
|
|
90
115
|
export function transformToSet(event, roster) {
|
|
@@ -95,7 +120,8 @@ export function transformToSet(event, roster) {
|
|
|
95
120
|
target: event.a,
|
|
96
121
|
score: event.s,
|
|
97
122
|
incident: incidentFromCompact(event.i, event.h, roster),
|
|
98
|
-
tempo: event.m
|
|
123
|
+
tempo: event.m,
|
|
124
|
+
...positionalFromCompact(event)
|
|
99
125
|
});
|
|
100
126
|
}
|
|
101
127
|
export function transformToSpike(event, roster) {
|
|
@@ -105,7 +131,8 @@ export function transformToSpike(event, roster) {
|
|
|
105
131
|
playerId: roster[event.p],
|
|
106
132
|
target: event.a,
|
|
107
133
|
score: event.s,
|
|
108
|
-
incident: incidentFromCompact(event.i, event.h, roster)
|
|
134
|
+
incident: incidentFromCompact(event.i, event.h, roster),
|
|
135
|
+
...positionalFromCompact(event)
|
|
109
136
|
});
|
|
110
137
|
}
|
|
111
138
|
// FreeBall (Last Resort save) is a plain InPlayEvent with no incident of its own, so it decodes from f/t/a/s
|
|
@@ -116,7 +143,8 @@ export function transformToFreeBall(event, roster) {
|
|
|
116
143
|
type: event.t,
|
|
117
144
|
playerId: roster[event.p],
|
|
118
145
|
target: event.a,
|
|
119
|
-
score: event.s
|
|
146
|
+
score: event.s,
|
|
147
|
+
...positionalFromCompact(event)
|
|
120
148
|
});
|
|
121
149
|
}
|
|
122
150
|
export function transformToSubstitution(event, roster) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type EventRewardBand = 'PARTICIPATION' | '
|
|
1
|
+
export type EventRewardBand = 'PARTICIPATION' | 'FINAL_GROUP' | 'TOP_10' | 'TOP_5' | 'TOP_3' | 'TOP_1';
|
|
2
2
|
export interface EventRewardRow {
|
|
3
3
|
readonly band: EventRewardBand;
|
|
4
4
|
/** Best final position that earns this row; PARTICIPATION has none. */
|
|
@@ -15,7 +15,8 @@ export interface EventRewardRow {
|
|
|
15
15
|
export declare const EVENT_REWARDS: readonly EventRewardRow[];
|
|
16
16
|
/**
|
|
17
17
|
* The band a finish falls into. `position` is the place in the FINAL table; null means the team never reached
|
|
18
|
-
* the final
|
|
18
|
+
* the final (it went out in the group stage) and takes the participation row. The final holds MAX_FINAL_SIZE
|
|
19
|
+
* teams and the widest banded row (FINAL_GROUP) covers exactly that, so every finalist earns a banded row.
|
|
19
20
|
*/
|
|
20
21
|
export declare function eventRewardBandFor(position: number | null): EventRewardBand;
|
|
21
22
|
export interface EventPayout {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// One source of truth for three consumers: the simulator grants from it, the About view lists it, and the
|
|
4
4
|
// accolades page names the band. Rewards are CUMULATIVE, so a team collects every row from Participation down
|
|
5
5
|
// to its own placement.
|
|
6
|
+
import { MAX_FINAL_SIZE } from './event-schedule';
|
|
6
7
|
/**
|
|
7
8
|
* Best band first. A team earns its own row and every row BELOW it, which is what "cumulative" means: first
|
|
8
9
|
* place collects all six.
|
|
@@ -12,18 +13,19 @@ export const EVENT_REWARDS = [
|
|
|
12
13
|
{ band: 'TOP_3', maxPosition: 3, packs: 0, coins: 500 },
|
|
13
14
|
{ band: 'TOP_5', maxPosition: 5, packs: 0, coins: 1000 },
|
|
14
15
|
{ band: 'TOP_10', maxPosition: 10, packs: 1, coins: 0 },
|
|
15
|
-
{ band: '
|
|
16
|
+
{ band: 'FINAL_GROUP', maxPosition: MAX_FINAL_SIZE, packs: 0, coins: 1000 },
|
|
16
17
|
{ band: 'PARTICIPATION', maxPosition: null, packs: 1, coins: 1000 }
|
|
17
18
|
];
|
|
18
19
|
/**
|
|
19
20
|
* The band a finish falls into. `position` is the place in the FINAL table; null means the team never reached
|
|
20
|
-
* the final
|
|
21
|
+
* the final (it went out in the group stage) and takes the participation row. The final holds MAX_FINAL_SIZE
|
|
22
|
+
* teams and the widest banded row (FINAL_GROUP) covers exactly that, so every finalist earns a banded row.
|
|
21
23
|
*/
|
|
22
24
|
export function eventRewardBandFor(position) {
|
|
23
25
|
if (position == null)
|
|
24
26
|
return 'PARTICIPATION';
|
|
25
27
|
// Rows are ordered best-first, so the FIRST row the position satisfies is the narrowest band it earns.
|
|
26
|
-
// Scanning to the end instead would always land on the widest, handing first place a "
|
|
28
|
+
// Scanning to the end instead would always land on the widest, handing first place a "final group" badge.
|
|
27
29
|
for (const row of EVENT_REWARDS) {
|
|
28
30
|
if (row.maxPosition != null && position <= row.maxPosition)
|
|
29
31
|
return row.band;
|
|
@@ -9,11 +9,11 @@ describe('eventRewardBandFor()', () => {
|
|
|
9
9
|
[5, 'TOP_5'],
|
|
10
10
|
[6, 'TOP_10'],
|
|
11
11
|
[10, 'TOP_10'],
|
|
12
|
-
[11, '
|
|
13
|
-
[20, '
|
|
14
|
-
//
|
|
15
|
-
[21, '
|
|
16
|
-
[24, '
|
|
12
|
+
[11, 'FINAL_GROUP'],
|
|
13
|
+
[20, 'FINAL_GROUP'],
|
|
14
|
+
// The final holds 24 teams and FINAL_GROUP covers all of them.
|
|
15
|
+
[21, 'FINAL_GROUP'],
|
|
16
|
+
[24, 'FINAL_GROUP']
|
|
17
17
|
])('places a finish of %i in %s', (position, band) => {
|
|
18
18
|
expect(eventRewardBandFor(position)).toBe(band);
|
|
19
19
|
});
|
|
@@ -24,9 +24,9 @@ describe('eventRewardBandFor()', () => {
|
|
|
24
24
|
describe('eventPayoutFor()', () => {
|
|
25
25
|
it.each([
|
|
26
26
|
// position, packs, coins. Cumulative: every row from Participation down to the finish.
|
|
27
|
-
[null, 1, 1000], // group stage: 1 pack + 1000
|
|
28
|
-
[24, 1,
|
|
29
|
-
[20, 1, 2000], // +
|
|
27
|
+
[null, 1, 1000], // never reached the final (group stage): 1 pack + 1000
|
|
28
|
+
[24, 1, 2000], // last of the 24-team final: final group's 1000 on top of participation
|
|
29
|
+
[20, 1, 2000], // + final group's 1000
|
|
30
30
|
[11, 1, 2000],
|
|
31
31
|
[10, 2, 2000], // + top 10's pack
|
|
32
32
|
[6, 2, 2000],
|
|
@@ -48,7 +48,7 @@ describe('eventPayoutFor()', () => {
|
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
it('adds up to the owner-defined table for a winner', () => {
|
|
51
|
-
// 1x pack + 1000 (participation), +1000 (
|
|
51
|
+
// 1x pack + 1000 (participation), +1000 (final group), +1 pack (top 10), +1000 (top 5), +500 (top 3), +1 pack (top 1)
|
|
52
52
|
const total = EVENT_REWARDS.reduce((acc, row) => ({ packs: acc.packs + row.packs, coins: acc.coins + row.coins }), { packs: 0, coins: 0 });
|
|
53
53
|
expect(eventPayoutFor(1)).toEqual(total);
|
|
54
54
|
expect(total).toEqual({ packs: 3, coins: 3500 });
|
|
@@ -31,8 +31,8 @@ export class Block extends InPlayEvent {
|
|
|
31
31
|
}
|
|
32
32
|
return new Block(result.data);
|
|
33
33
|
}
|
|
34
|
-
constructor({ score, playerId, target, failure, type, blockers, activeTraits, incident }) {
|
|
35
|
-
super({ score, playerId, target, activeTraits, incident, eventType });
|
|
34
|
+
constructor({ score, playerId, target, failure, type, blockers, activeTraits, incident, contact, landing, speed, digQuality }) {
|
|
35
|
+
super({ score, playerId, target, activeTraits, incident, eventType, contact, landing, speed, digQuality });
|
|
36
36
|
this.type = type;
|
|
37
37
|
this.failure = failure;
|
|
38
38
|
this.blockers = blockers;
|
|
@@ -30,8 +30,8 @@ export class FreeBall extends InPlayEvent {
|
|
|
30
30
|
}
|
|
31
31
|
return new FreeBall(result.data);
|
|
32
32
|
}
|
|
33
|
-
constructor({ score, playerId, target, failure, type, activeTraits }) {
|
|
34
|
-
super({ score, playerId, target, activeTraits, eventType });
|
|
33
|
+
constructor({ score, playerId, target, failure, type, activeTraits, contact, landing, speed, digQuality }) {
|
|
34
|
+
super({ score, playerId, target, activeTraits, eventType, contact, landing, speed, digQuality });
|
|
35
35
|
this.type = type;
|
|
36
36
|
this.failure = failure;
|
|
37
37
|
}
|
|
@@ -6,11 +6,25 @@ export interface EventIncident {
|
|
|
6
6
|
readonly severity: number;
|
|
7
7
|
readonly playerId?: string;
|
|
8
8
|
}
|
|
9
|
+
export declare enum DigQualityEnum {
|
|
10
|
+
PERFECT = 0,
|
|
11
|
+
MEDIOCRE = 1,
|
|
12
|
+
BAD = 2
|
|
13
|
+
}
|
|
14
|
+
export type DigQuality = DigQualityEnum.PERFECT | DigQualityEnum.MEDIOCRE | DigQualityEnum.BAD;
|
|
15
|
+
export interface EventCoord {
|
|
16
|
+
readonly x: number;
|
|
17
|
+
readonly y: number;
|
|
18
|
+
}
|
|
9
19
|
export interface InPlayEventOpts extends RallyEventOpts {
|
|
10
20
|
readonly target: CourtTarget;
|
|
11
21
|
readonly score: number;
|
|
12
22
|
readonly activeTraits: Trait[];
|
|
13
23
|
readonly incident?: EventIncident;
|
|
24
|
+
readonly contact?: EventCoord;
|
|
25
|
+
readonly landing?: EventCoord;
|
|
26
|
+
readonly speed?: number;
|
|
27
|
+
readonly digQuality?: DigQuality;
|
|
14
28
|
}
|
|
15
29
|
export declare abstract class InPlayEvent<F, T> extends RallyEvent {
|
|
16
30
|
abstract failure: F;
|
|
@@ -19,5 +33,9 @@ export declare abstract class InPlayEvent<F, T> extends RallyEvent {
|
|
|
19
33
|
readonly score: number;
|
|
20
34
|
readonly activeTraits: Trait[];
|
|
21
35
|
readonly incident?: EventIncident;
|
|
22
|
-
|
|
36
|
+
readonly contact?: EventCoord;
|
|
37
|
+
readonly landing?: EventCoord;
|
|
38
|
+
readonly speed?: number;
|
|
39
|
+
readonly digQuality?: DigQuality;
|
|
40
|
+
protected constructor({ eventType, playerId, target, score, activeTraits, incident, contact, landing, speed, digQuality }: InPlayEventOpts);
|
|
23
41
|
}
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { RallyEvent } from './rally-event';
|
|
2
|
+
// The quality of a DEFENSIVE dig (positional engine, effort gating): a PERFECT dig sets up a clean in-system
|
|
3
|
+
// counter, MEDIOCRE is out of position, BAD is a scramble. Persisted on the Reception (dig) event's 'q' key.
|
|
4
|
+
export var DigQualityEnum;
|
|
5
|
+
(function (DigQualityEnum) {
|
|
6
|
+
DigQualityEnum[DigQualityEnum["PERFECT"] = 0] = "PERFECT";
|
|
7
|
+
DigQualityEnum[DigQualityEnum["MEDIOCRE"] = 1] = "MEDIOCRE";
|
|
8
|
+
DigQualityEnum[DigQualityEnum["BAD"] = 2] = "BAD";
|
|
9
|
+
})(DigQualityEnum || (DigQualityEnum = {}));
|
|
10
|
+
function roundCoord(c) {
|
|
11
|
+
return c != null ? { x: Number(c.x.toFixed(2)), y: Number(c.y.toFixed(2)) } : undefined;
|
|
12
|
+
}
|
|
2
13
|
export class InPlayEvent extends RallyEvent {
|
|
3
|
-
constructor({ eventType, playerId, target, score, activeTraits, incident }) {
|
|
14
|
+
constructor({ eventType, playerId, target, score, activeTraits, incident, contact, landing, speed, digQuality }) {
|
|
4
15
|
super({ playerId, eventType });
|
|
5
16
|
this.target = target;
|
|
6
17
|
this.score = Number(score.toFixed(2));
|
|
7
18
|
this.activeTraits = activeTraits;
|
|
8
19
|
this.incident = incident;
|
|
20
|
+
this.contact = roundCoord(contact);
|
|
21
|
+
this.landing = roundCoord(landing);
|
|
22
|
+
this.speed = speed != null ? Number(speed.toFixed(2)) : undefined;
|
|
23
|
+
this.digQuality = digQuality;
|
|
9
24
|
}
|
|
10
25
|
}
|
|
@@ -33,8 +33,8 @@ export class Reception extends InPlayEvent {
|
|
|
33
33
|
}
|
|
34
34
|
return new Reception(result.data);
|
|
35
35
|
}
|
|
36
|
-
constructor({ score, playerId, target, failure, type, activeTraits, incident }) {
|
|
37
|
-
super({ score, playerId, target, activeTraits, incident, eventType });
|
|
36
|
+
constructor({ score, playerId, target, failure, type, activeTraits, incident, contact, landing, speed, digQuality }) {
|
|
37
|
+
super({ score, playerId, target, activeTraits, incident, eventType, contact, landing, speed, digQuality });
|
|
38
38
|
this.type = type;
|
|
39
39
|
this.failure = failure;
|
|
40
40
|
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { TraitEnum } from '../../player';
|
|
3
3
|
export declare const BlockInputSchema: z.ZodObject<{
|
|
4
|
+
contact: z.ZodOptional<z.ZodObject<{
|
|
5
|
+
x: z.ZodNumber;
|
|
6
|
+
y: z.ZodNumber;
|
|
7
|
+
}, z.core.$strip>>;
|
|
8
|
+
landing: z.ZodOptional<z.ZodObject<{
|
|
9
|
+
x: z.ZodNumber;
|
|
10
|
+
y: z.ZodNumber;
|
|
11
|
+
}, z.core.$strip>>;
|
|
12
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
digQuality: z.ZodOptional<z.ZodNumber>;
|
|
4
14
|
playerId: z.ZodUUID;
|
|
5
15
|
score: z.ZodNumber;
|
|
6
16
|
target: z.ZodNumber;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { TraitEnum } from '../../player';
|
|
3
3
|
import { EventIncidentSchema } from './incident.z';
|
|
4
|
+
import { positionalEventFields } from './positional.z';
|
|
4
5
|
const blockTypeValues = [0, 1, 2, 3];
|
|
5
6
|
const blockFailureValues = [0, 1, 2, 3, 4, 5];
|
|
6
7
|
const courtTargetValues = Array.from({ length: 13 }, (_, i) => i);
|
|
@@ -12,7 +13,8 @@ export const BlockInputSchema = z.object({
|
|
|
12
13
|
failure: z.number().int().refine((v) => blockFailureValues.includes(v), { message: 'INVALID_BLOCK_FAILURE' }),
|
|
13
14
|
type: z.number().int().refine((v) => blockTypeValues.includes(v), { message: 'INVALID_BLOCK_TYPE' }),
|
|
14
15
|
blockers: z.array(z.uuid()),
|
|
15
|
-
incident: EventIncidentSchema.optional()
|
|
16
|
+
incident: EventIncidentSchema.optional(),
|
|
17
|
+
...positionalEventFields
|
|
16
18
|
}).superRefine((data, ctx) => {
|
|
17
19
|
// The block type IS the blocker count (NO_BLOCKER=0, SINGLE=1, DOUBLE=2, TRIPLE=3), so the two must agree.
|
|
18
20
|
if (data.blockers.length !== data.type) {
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { TraitEnum } from '../../player';
|
|
3
3
|
export declare const FreeBallInputSchema: z.ZodObject<{
|
|
4
|
+
contact: z.ZodOptional<z.ZodObject<{
|
|
5
|
+
x: z.ZodNumber;
|
|
6
|
+
y: z.ZodNumber;
|
|
7
|
+
}, z.core.$strip>>;
|
|
8
|
+
landing: z.ZodOptional<z.ZodObject<{
|
|
9
|
+
x: z.ZodNumber;
|
|
10
|
+
y: z.ZodNumber;
|
|
11
|
+
}, z.core.$strip>>;
|
|
12
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
digQuality: z.ZodOptional<z.ZodNumber>;
|
|
4
14
|
playerId: z.ZodUUID;
|
|
5
15
|
score: z.ZodNumber;
|
|
6
16
|
target: z.ZodNumber;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { TraitEnum } from '../../player';
|
|
3
|
+
import { positionalEventFields } from './positional.z';
|
|
3
4
|
const freeBallTypeValues = [0, 1];
|
|
4
5
|
const freeBallFailureValues = [0];
|
|
5
6
|
const courtTargetValues = Array.from({ length: 13 }, (_, i) => i);
|
|
@@ -9,5 +10,6 @@ export const FreeBallInputSchema = z.object({
|
|
|
9
10
|
target: z.number().int().refine((v) => courtTargetValues.includes(v), { message: 'INVALID_COURT_TARGET' }),
|
|
10
11
|
activeTraits: z.array(z.enum(Object.values(TraitEnum))).default([]),
|
|
11
12
|
failure: z.number().int().refine((v) => freeBallFailureValues.includes(v), { message: 'INVALID_FREE_BALL_FAILURE' }),
|
|
12
|
-
type: z.number().int().refine((v) => freeBallTypeValues.includes(v), { message: 'INVALID_FREE_BALL_TYPE' })
|
|
13
|
+
type: z.number().int().refine((v) => freeBallTypeValues.includes(v), { message: 'INVALID_FREE_BALL_TYPE' }),
|
|
14
|
+
...positionalEventFields
|
|
13
15
|
});
|