volleyballsimtypes 0.0.519 → 0.0.520
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/api/index.d.ts +14 -1
- package/dist/cjs/src/data/init-models.js +14 -0
- package/dist/cjs/src/data/models/auth-identity.d.ts +1 -1
- package/dist/cjs/src/data/models/auth-identity.js +1 -1
- package/dist/cjs/src/data/models/index.d.ts +3 -0
- package/dist/cjs/src/data/models/index.js +3 -0
- package/dist/cjs/src/data/models/jersey-badge.d.ts +32 -0
- package/dist/cjs/src/data/models/jersey-badge.js +60 -0
- package/dist/cjs/src/data/models/premium-grant.d.ts +39 -0
- package/dist/cjs/src/data/models/premium-grant.js +82 -0
- package/dist/cjs/src/data/models/team-jersey.d.ts +20 -0
- package/dist/cjs/src/data/models/team-jersey.js +38 -0
- package/dist/cjs/src/data/models/team.d.ts +3 -1
- package/dist/cjs/src/data/transformers/team.js +4 -1
- package/dist/cjs/src/service/index.d.ts +1 -0
- package/dist/cjs/src/service/index.js +1 -0
- package/dist/cjs/src/service/player/improvement.test.js +5 -5
- package/dist/cjs/src/service/player/stats.js +30 -16
- package/dist/cjs/src/service/player/stats.test.js +6 -6
- package/dist/cjs/src/service/premium/index.d.ts +3 -0
- package/dist/cjs/src/service/premium/index.js +19 -0
- package/dist/cjs/src/service/premium/patreon-tier-map.d.ts +5 -0
- package/dist/cjs/src/service/premium/patreon-tier-map.js +22 -0
- package/dist/cjs/src/service/premium/patreon-tier-map.test.d.ts +1 -0
- package/dist/cjs/src/service/premium/patreon-tier-map.test.js +48 -0
- package/dist/cjs/src/service/premium/premium-grant.d.ts +17 -0
- package/dist/cjs/src/service/premium/premium-grant.js +15 -0
- package/dist/cjs/src/service/premium/premium-tier.d.ts +10 -0
- package/dist/cjs/src/service/premium/premium-tier.js +24 -0
- package/dist/cjs/src/service/premium/premium.test.d.ts +1 -0
- package/dist/cjs/src/service/premium/premium.test.js +93 -0
- package/dist/cjs/src/service/team/index.d.ts +1 -0
- package/dist/cjs/src/service/team/index.js +1 -0
- package/dist/cjs/src/service/team/jersey.d.ts +74 -0
- package/dist/cjs/src/service/team/jersey.js +33 -0
- package/dist/cjs/src/service/team/schemas/team.z.d.ts +69 -0
- package/dist/cjs/src/service/team/schemas/team.z.js +3 -1
- package/dist/cjs/src/service/team/team.d.ts +2 -0
- package/dist/cjs/src/service/team/team.js +2 -1
- package/dist/cjs/src/stat-config/improvement-floors.json +204 -204
- package/dist/esm/src/api/index.d.ts +14 -1
- package/dist/esm/src/data/init-models.js +15 -1
- package/dist/esm/src/data/models/auth-identity.d.ts +1 -1
- package/dist/esm/src/data/models/auth-identity.js +1 -1
- package/dist/esm/src/data/models/index.d.ts +3 -0
- package/dist/esm/src/data/models/index.js +3 -0
- package/dist/esm/src/data/models/jersey-badge.d.ts +32 -0
- package/dist/esm/src/data/models/jersey-badge.js +56 -0
- package/dist/esm/src/data/models/premium-grant.d.ts +39 -0
- package/dist/esm/src/data/models/premium-grant.js +78 -0
- package/dist/esm/src/data/models/team-jersey.d.ts +20 -0
- package/dist/esm/src/data/models/team-jersey.js +34 -0
- package/dist/esm/src/data/models/team.d.ts +3 -1
- package/dist/esm/src/data/transformers/team.js +4 -1
- package/dist/esm/src/service/index.d.ts +1 -0
- package/dist/esm/src/service/index.js +1 -0
- package/dist/esm/src/service/player/improvement.test.js +5 -5
- package/dist/esm/src/service/player/stats.js +30 -16
- package/dist/esm/src/service/player/stats.test.js +6 -6
- package/dist/esm/src/service/premium/index.d.ts +3 -0
- package/dist/esm/src/service/premium/index.js +3 -0
- package/dist/esm/src/service/premium/patreon-tier-map.d.ts +5 -0
- package/dist/esm/src/service/premium/patreon-tier-map.js +18 -0
- package/dist/esm/src/service/premium/patreon-tier-map.test.d.ts +1 -0
- package/dist/esm/src/service/premium/patreon-tier-map.test.js +46 -0
- package/dist/esm/src/service/premium/premium-grant.d.ts +17 -0
- package/dist/esm/src/service/premium/premium-grant.js +11 -0
- package/dist/esm/src/service/premium/premium-tier.d.ts +10 -0
- package/dist/esm/src/service/premium/premium-tier.js +19 -0
- package/dist/esm/src/service/premium/premium.test.d.ts +1 -0
- package/dist/esm/src/service/premium/premium.test.js +91 -0
- package/dist/esm/src/service/team/index.d.ts +1 -0
- package/dist/esm/src/service/team/index.js +1 -0
- package/dist/esm/src/service/team/jersey.d.ts +74 -0
- package/dist/esm/src/service/team/jersey.js +30 -0
- package/dist/esm/src/service/team/schemas/team.z.d.ts +69 -0
- package/dist/esm/src/service/team/schemas/team.z.js +3 -1
- package/dist/esm/src/service/team/team.d.ts +2 -0
- package/dist/esm/src/service/team/team.js +2 -1
- package/dist/esm/src/stat-config/improvement-floors.json +204 -204
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseConfig, BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, PointBreakdown, RallyMetrics, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, DefenceFormation, LineupFunction, CourtPosition, ConditionLogic, EnergyBand, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand } from '../service';
|
|
1
|
+
import { BaseConfig, BoxScore, DataProps, DeclineStatus, JerseyConfig, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, PointBreakdown, RallyMetrics, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, DefenceFormation, LineupFunction, CourtPosition, ConditionLogic, EnergyBand, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand } from '../service';
|
|
2
2
|
export * from './tactics-player-refs';
|
|
3
3
|
export type Rally = DataProps<_Rally> & {
|
|
4
4
|
homePlayerPosition: PlayerPosition[];
|
|
@@ -60,6 +60,17 @@ export interface ApiPlayerInjuryStatus {
|
|
|
60
60
|
severity: number;
|
|
61
61
|
injuredUntil: string;
|
|
62
62
|
}
|
|
63
|
+
export type ChampionRegion = 'azureich' | 'borealas' | 'varune' | 'tirdeas';
|
|
64
|
+
export type ChampionBadgeSpec = {
|
|
65
|
+
type: 'league';
|
|
66
|
+
} | {
|
|
67
|
+
type: 'nationals';
|
|
68
|
+
} | {
|
|
69
|
+
type: 'regionals';
|
|
70
|
+
region: ChampionRegion;
|
|
71
|
+
} | {
|
|
72
|
+
type: 'wcc';
|
|
73
|
+
};
|
|
63
74
|
export type Player = Omit<DataProps<_Player>, 'stats' | 'birthAge' | 'birthIteration' | 'declineProfile' | 'nickname' | 'injuryProneProfile' | 'injury'> & {
|
|
64
75
|
-readonly [K in 'birthAge' | 'birthIteration']?: DataProps<_Player>[K];
|
|
65
76
|
} & {
|
|
@@ -70,6 +81,8 @@ export type Player = Omit<DataProps<_Player>, 'stats' | 'birthAge' | 'birthItera
|
|
|
70
81
|
declineStatus?: DeclineStatus;
|
|
71
82
|
injuryStatus?: ApiPlayerInjuryStatus;
|
|
72
83
|
nickname?: string;
|
|
84
|
+
jerseyConfig?: JerseyConfig;
|
|
85
|
+
championBadge?: ChampionBadgeSpec;
|
|
73
86
|
};
|
|
74
87
|
export interface StartingLineup {
|
|
75
88
|
[CourtPosition.LEFT_FRONT]: string;
|
|
@@ -9,6 +9,7 @@ function initModels(sequelize) {
|
|
|
9
9
|
const Physiotherapist = models_1.PhysiotherapistModel.initModel(sequelize);
|
|
10
10
|
const CurrencyTransaction = models_1.CurrencyTransactionModel.initModel(sequelize);
|
|
11
11
|
const Tactics = models_1.TacticsModel.initModel(sequelize);
|
|
12
|
+
const TeamJersey = models_1.TeamJerseyModel.initModel(sequelize);
|
|
12
13
|
const LineupPreset = models_1.LineupPresetModel.initModel(sequelize);
|
|
13
14
|
const MatchPreset = models_1.MatchPresetModel.initModel(sequelize);
|
|
14
15
|
const Event = models_1.EventModel.initModel(sequelize);
|
|
@@ -24,6 +25,7 @@ function initModels(sequelize) {
|
|
|
24
25
|
const GachaPity = models_1.GachaPityModel.initModel(sequelize);
|
|
25
26
|
const GachaPullHistory = models_1.GachaPullHistoryModel.initModel(sequelize);
|
|
26
27
|
const UserPullGrant = models_1.UserPullGrantModel.initModel(sequelize);
|
|
28
|
+
const PremiumGrant = models_1.PremiumGrantModel.initModel(sequelize);
|
|
27
29
|
const Wishlist = models_1.WishlistModel.initModel(sequelize);
|
|
28
30
|
const UserSettings = models_1.UserSettingsModel.initModel(sequelize);
|
|
29
31
|
const UserDeviceToken = models_1.UserDeviceTokenModel.initModel(sequelize);
|
|
@@ -39,6 +41,7 @@ function initModels(sequelize) {
|
|
|
39
41
|
const CompetitionStandingsMatch = models_1.CompetitionStandingsMatchModel.initModel(sequelize);
|
|
40
42
|
const CompetitionTeams = models_1.CompetitionTeamsModel.initModel(sequelize);
|
|
41
43
|
const CompetitionChampion = models_1.CompetitionChampionModel.initModel(sequelize);
|
|
44
|
+
const JerseyBadge = models_1.JerseyBadgeModel.initModel(sequelize);
|
|
42
45
|
const CompetitionMatch = models_1.CompetitionMatchModel.initModel(sequelize);
|
|
43
46
|
const Iteration = models_1.IterationModel.initModel(sequelize);
|
|
44
47
|
const League = models_1.LeagueModel.initModel(sequelize);
|
|
@@ -83,6 +86,7 @@ function initModels(sequelize) {
|
|
|
83
86
|
Notification.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
84
87
|
AuthUser.hasMany(GachaPullHistory, { as: 'GachaPullHistory', foreignKey: 'user_id' });
|
|
85
88
|
AuthUser.hasMany(UserPullGrant, { as: 'UserPullGrants', foreignKey: 'user_id' });
|
|
89
|
+
AuthUser.hasMany(PremiumGrant, { as: 'PremiumGrants', foreignKey: 'user_id' });
|
|
86
90
|
AuthIdentity.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
87
91
|
AuthSession.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
88
92
|
UserTeam.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
@@ -93,6 +97,8 @@ function initModels(sequelize) {
|
|
|
93
97
|
GachaPullHistory.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
94
98
|
GachaPullHistory.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
|
|
95
99
|
UserPullGrant.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
100
|
+
PremiumGrant.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
101
|
+
PremiumGrant.belongsTo(AuthUser, { as: 'grantedByUser', foreignKey: 'granted_by' });
|
|
96
102
|
Wishlist.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
97
103
|
UserSettings.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
98
104
|
Competition.belongsTo(Iteration, { as: 'Iteration', foreignKey: 'iteration' });
|
|
@@ -110,6 +116,8 @@ function initModels(sequelize) {
|
|
|
110
116
|
});
|
|
111
117
|
CompetitionChampion.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
|
|
112
118
|
CompetitionChampion.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
119
|
+
JerseyBadge.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
|
|
120
|
+
JerseyBadge.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
113
121
|
CompetitionMVP.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
|
|
114
122
|
CompetitionMVP.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
|
|
115
123
|
CompetitionMatch.belongsTo(Competition, { as: 'Competition', foreignKey: 'competition_id' });
|
|
@@ -228,6 +236,10 @@ function initModels(sequelize) {
|
|
|
228
236
|
Team.hasOne(Physiotherapist, { as: 'Physiotherapist', foreignKey: 'team_id' });
|
|
229
237
|
Physiotherapist.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
230
238
|
Team.hasOne(Tactics, { as: 'tactics', foreignKey: 'team_id' });
|
|
239
|
+
// Saved premium kit. Like lineup presets, deliberately NOT in Team.defaultScope: only the management API (editor)
|
|
240
|
+
// and the effective-jersey resolver include it, so the simulator's team loads are unchanged.
|
|
241
|
+
Team.hasOne(TeamJersey, { as: 'TeamJersey', foreignKey: 'team_id' });
|
|
242
|
+
TeamJersey.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
231
243
|
// Lineup presets are loaded only by the management API; deliberately NOT in Team.defaultScope so the
|
|
232
244
|
// simulator's team loads are unchanged (it reads the active config from the mirrored Tactics row).
|
|
233
245
|
Team.hasMany(LineupPreset, { as: 'lineupPresets', foreignKey: 'team_id' });
|
|
@@ -309,6 +321,7 @@ function initModels(sequelize) {
|
|
|
309
321
|
AuthUser,
|
|
310
322
|
Competition,
|
|
311
323
|
CompetitionChampion,
|
|
324
|
+
JerseyBadge,
|
|
312
325
|
CompetitionMatch,
|
|
313
326
|
CompetitionMVP,
|
|
314
327
|
CompetitionStandings,
|
|
@@ -354,6 +367,7 @@ function initModels(sequelize) {
|
|
|
354
367
|
GachaPity,
|
|
355
368
|
GachaPullHistory,
|
|
356
369
|
UserPullGrant,
|
|
370
|
+
PremiumGrant,
|
|
357
371
|
Wishlist,
|
|
358
372
|
UserSettings,
|
|
359
373
|
UserDeviceToken,
|
|
@@ -14,7 +14,7 @@ export interface AuthIdentityAttributes {
|
|
|
14
14
|
}
|
|
15
15
|
export type AuthIdentityPk = 'identity_id';
|
|
16
16
|
export type AuthIdentityId = AuthIdentityModel[AuthIdentityPk];
|
|
17
|
-
export type AuthProvider = 'EMAIL' | 'GOOGLE' | 'FACEBOOK' | 'APPLE';
|
|
17
|
+
export type AuthProvider = 'EMAIL' | 'GOOGLE' | 'FACEBOOK' | 'APPLE' | 'PATREON';
|
|
18
18
|
export type AuthIdentityOptionalAttributes = 'email' | 'password_hash' | 'last_used_at' | 'created_at' | 'updated_at';
|
|
19
19
|
export type AuthIdentityCreationAttributes = Optional<AuthIdentityAttributes, AuthIdentityOptionalAttributes>;
|
|
20
20
|
export declare class AuthIdentityModel extends Model<AuthIdentityAttributes, AuthIdentityCreationAttributes> implements AuthIdentityAttributes {
|
|
@@ -19,7 +19,7 @@ class AuthIdentityModel extends sequelize_1.Model {
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
provider: {
|
|
22
|
-
type: sequelize_1.DataTypes.ENUM('EMAIL', 'GOOGLE', 'FACEBOOK', 'APPLE'),
|
|
22
|
+
type: sequelize_1.DataTypes.ENUM('EMAIL', 'GOOGLE', 'FACEBOOK', 'APPLE', 'PATREON'),
|
|
23
23
|
allowNull: false
|
|
24
24
|
},
|
|
25
25
|
provider_user_id: {
|
|
@@ -7,6 +7,7 @@ export * from './competition';
|
|
|
7
7
|
export * from './competition-mvp';
|
|
8
8
|
export * from './competition-champion';
|
|
9
9
|
export * from './competition-match';
|
|
10
|
+
export * from './jersey-badge';
|
|
10
11
|
export * from './competition-standings';
|
|
11
12
|
export * from './competition-standings-match';
|
|
12
13
|
export * from './competition-teams';
|
|
@@ -37,8 +38,10 @@ export * from './physiotherapist';
|
|
|
37
38
|
export * from './team';
|
|
38
39
|
export * from './team-replacement';
|
|
39
40
|
export * from './tactics';
|
|
41
|
+
export * from './team-jersey';
|
|
40
42
|
export * from './lineup-preset';
|
|
41
43
|
export * from './base-position-preset';
|
|
44
|
+
export * from './premium-grant';
|
|
42
45
|
export * from './event';
|
|
43
46
|
export * from './event-competition';
|
|
44
47
|
export * from './event-bot-team';
|
|
@@ -23,6 +23,7 @@ __exportStar(require("./competition"), exports);
|
|
|
23
23
|
__exportStar(require("./competition-mvp"), exports);
|
|
24
24
|
__exportStar(require("./competition-champion"), exports);
|
|
25
25
|
__exportStar(require("./competition-match"), exports);
|
|
26
|
+
__exportStar(require("./jersey-badge"), exports);
|
|
26
27
|
__exportStar(require("./competition-standings"), exports);
|
|
27
28
|
__exportStar(require("./competition-standings-match"), exports);
|
|
28
29
|
__exportStar(require("./competition-teams"), exports);
|
|
@@ -53,8 +54,10 @@ __exportStar(require("./physiotherapist"), exports);
|
|
|
53
54
|
__exportStar(require("./team"), exports);
|
|
54
55
|
__exportStar(require("./team-replacement"), exports);
|
|
55
56
|
__exportStar(require("./tactics"), exports);
|
|
57
|
+
__exportStar(require("./team-jersey"), exports);
|
|
56
58
|
__exportStar(require("./lineup-preset"), exports);
|
|
57
59
|
__exportStar(require("./base-position-preset"), exports);
|
|
60
|
+
__exportStar(require("./premium-grant"), exports);
|
|
58
61
|
__exportStar(require("./event"), exports);
|
|
59
62
|
__exportStar(require("./event-competition"), exports);
|
|
60
63
|
__exportStar(require("./event-bot-team"), exports);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as Sequelize from 'sequelize';
|
|
2
|
+
import { Model } from 'sequelize';
|
|
3
|
+
import { CompetitionId, CompetitionModel, TeamId, TeamModel } from '.';
|
|
4
|
+
export type JerseyBadgeKind = 'LEAGUE' | 'NATIONALS' | 'REGIONALS' | 'WCC';
|
|
5
|
+
export interface JerseyBadgeAttributes {
|
|
6
|
+
competition_id: string;
|
|
7
|
+
team_id: string;
|
|
8
|
+
badge: JerseyBadgeKind;
|
|
9
|
+
region: string | null;
|
|
10
|
+
start_time: Date;
|
|
11
|
+
end_time: Date;
|
|
12
|
+
}
|
|
13
|
+
export type JerseyBadgePk = 'competition_id';
|
|
14
|
+
export type JerseyBadgeId = JerseyBadgeModel[JerseyBadgePk];
|
|
15
|
+
export type JerseyBadgeCreationAttributes = JerseyBadgeAttributes;
|
|
16
|
+
export declare class JerseyBadgeModel extends Model<JerseyBadgeAttributes, JerseyBadgeCreationAttributes> implements JerseyBadgeAttributes {
|
|
17
|
+
competition_id: string;
|
|
18
|
+
team_id: string;
|
|
19
|
+
badge: JerseyBadgeKind;
|
|
20
|
+
region: string | null;
|
|
21
|
+
start_time: Date;
|
|
22
|
+
end_time: Date;
|
|
23
|
+
competition: CompetitionModel;
|
|
24
|
+
getCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
|
|
25
|
+
setCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
|
|
26
|
+
createCompetition: Sequelize.BelongsToCreateAssociationMixin<CompetitionModel>;
|
|
27
|
+
team: TeamModel;
|
|
28
|
+
getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
|
|
29
|
+
setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
|
|
30
|
+
createTeam: Sequelize.BelongsToCreateAssociationMixin<TeamModel>;
|
|
31
|
+
static initModel(sequelize: Sequelize.Sequelize): typeof JerseyBadgeModel;
|
|
32
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JerseyBadgeModel = void 0;
|
|
4
|
+
const sequelize_1 = require("sequelize");
|
|
5
|
+
class JerseyBadgeModel extends sequelize_1.Model {
|
|
6
|
+
static initModel(sequelize) {
|
|
7
|
+
return JerseyBadgeModel.init({
|
|
8
|
+
competition_id: {
|
|
9
|
+
type: sequelize_1.DataTypes.UUID,
|
|
10
|
+
allowNull: false,
|
|
11
|
+
primaryKey: true,
|
|
12
|
+
references: {
|
|
13
|
+
model: 'Competition',
|
|
14
|
+
key: 'competition_id'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
team_id: {
|
|
18
|
+
type: sequelize_1.DataTypes.UUID,
|
|
19
|
+
allowNull: false,
|
|
20
|
+
references: {
|
|
21
|
+
model: 'Team',
|
|
22
|
+
key: 'team_id'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
badge: {
|
|
26
|
+
type: sequelize_1.DataTypes.ENUM('LEAGUE', 'NATIONALS', 'REGIONALS', 'WCC'),
|
|
27
|
+
allowNull: false
|
|
28
|
+
},
|
|
29
|
+
region: {
|
|
30
|
+
type: sequelize_1.DataTypes.STRING(3),
|
|
31
|
+
allowNull: true
|
|
32
|
+
},
|
|
33
|
+
start_time: {
|
|
34
|
+
type: sequelize_1.DataTypes.DATE,
|
|
35
|
+
allowNull: false
|
|
36
|
+
},
|
|
37
|
+
end_time: {
|
|
38
|
+
type: sequelize_1.DataTypes.DATE,
|
|
39
|
+
allowNull: false
|
|
40
|
+
}
|
|
41
|
+
}, {
|
|
42
|
+
sequelize,
|
|
43
|
+
tableName: 'JerseyBadge',
|
|
44
|
+
schema: 'public',
|
|
45
|
+
timestamps: false,
|
|
46
|
+
indexes: [
|
|
47
|
+
{
|
|
48
|
+
name: 'jerseybadge_pk',
|
|
49
|
+
unique: true,
|
|
50
|
+
fields: [{ name: 'competition_id' }]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'JerseyBadge_team_active_idx',
|
|
54
|
+
fields: [{ name: 'team_id' }, { name: 'start_time' }, { name: 'end_time' }]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.JerseyBadgeModel = JerseyBadgeModel;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as Sequelize from 'sequelize';
|
|
2
|
+
import { Model, Optional } from 'sequelize';
|
|
3
|
+
import { PremiumGrantSource } from '../../service';
|
|
4
|
+
import { AuthUserId, AuthUserModel } from '.';
|
|
5
|
+
export interface PremiumGrantAttributes {
|
|
6
|
+
grant_id: string;
|
|
7
|
+
user_id: string;
|
|
8
|
+
tier: number;
|
|
9
|
+
source: PremiumGrantSource;
|
|
10
|
+
granted_by?: string | null;
|
|
11
|
+
granted_at: Date;
|
|
12
|
+
expires_at?: Date | null;
|
|
13
|
+
revoked_at?: Date | null;
|
|
14
|
+
patreon_member_id?: string | null;
|
|
15
|
+
note?: string | null;
|
|
16
|
+
}
|
|
17
|
+
export type PremiumGrantPk = 'grant_id';
|
|
18
|
+
export type PremiumGrantId = PremiumGrantModel[PremiumGrantPk];
|
|
19
|
+
export type PremiumGrantOptionalAttributes = 'grant_id' | 'granted_by' | 'granted_at' | 'expires_at' | 'revoked_at' | 'patreon_member_id' | 'note';
|
|
20
|
+
export type PremiumGrantCreationAttributes = Optional<PremiumGrantAttributes, PremiumGrantOptionalAttributes>;
|
|
21
|
+
export declare class PremiumGrantModel extends Model<PremiumGrantAttributes, PremiumGrantCreationAttributes> implements PremiumGrantAttributes {
|
|
22
|
+
grant_id: string;
|
|
23
|
+
user_id: string;
|
|
24
|
+
tier: number;
|
|
25
|
+
source: PremiumGrantSource;
|
|
26
|
+
granted_by?: string | null;
|
|
27
|
+
granted_at: Date;
|
|
28
|
+
expires_at?: Date | null;
|
|
29
|
+
revoked_at?: Date | null;
|
|
30
|
+
patreon_member_id?: string | null;
|
|
31
|
+
note?: string | null;
|
|
32
|
+
user: AuthUserModel;
|
|
33
|
+
getUser: Sequelize.BelongsToGetAssociationMixin<AuthUserModel>;
|
|
34
|
+
setUser: Sequelize.BelongsToSetAssociationMixin<AuthUserModel, AuthUserId>;
|
|
35
|
+
createUser: Sequelize.BelongsToCreateAssociationMixin<AuthUserModel>;
|
|
36
|
+
grantedByUser?: AuthUserModel | null;
|
|
37
|
+
getGrantedByUser: Sequelize.BelongsToGetAssociationMixin<AuthUserModel>;
|
|
38
|
+
static initModel(sequelize: Sequelize.Sequelize): typeof PremiumGrantModel;
|
|
39
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PremiumGrantModel = void 0;
|
|
4
|
+
const sequelize_1 = require("sequelize");
|
|
5
|
+
class PremiumGrantModel extends sequelize_1.Model {
|
|
6
|
+
static initModel(sequelize) {
|
|
7
|
+
return PremiumGrantModel.init({
|
|
8
|
+
grant_id: {
|
|
9
|
+
type: sequelize_1.DataTypes.UUID,
|
|
10
|
+
allowNull: false,
|
|
11
|
+
primaryKey: true,
|
|
12
|
+
defaultValue: sequelize_1.DataTypes.UUIDV4
|
|
13
|
+
},
|
|
14
|
+
user_id: {
|
|
15
|
+
type: sequelize_1.DataTypes.UUID,
|
|
16
|
+
allowNull: false,
|
|
17
|
+
references: {
|
|
18
|
+
model: 'AuthUser',
|
|
19
|
+
key: 'user_id'
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
tier: {
|
|
23
|
+
type: sequelize_1.DataTypes.INTEGER,
|
|
24
|
+
allowNull: false
|
|
25
|
+
},
|
|
26
|
+
source: {
|
|
27
|
+
type: sequelize_1.DataTypes.ENUM('ADMIN', 'PATREON'),
|
|
28
|
+
allowNull: false
|
|
29
|
+
},
|
|
30
|
+
granted_by: {
|
|
31
|
+
type: sequelize_1.DataTypes.UUID,
|
|
32
|
+
allowNull: true,
|
|
33
|
+
references: {
|
|
34
|
+
model: 'AuthUser',
|
|
35
|
+
key: 'user_id'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
granted_at: {
|
|
39
|
+
type: sequelize_1.DataTypes.DATE,
|
|
40
|
+
allowNull: false,
|
|
41
|
+
defaultValue: sequelize_1.DataTypes.NOW
|
|
42
|
+
},
|
|
43
|
+
expires_at: {
|
|
44
|
+
type: sequelize_1.DataTypes.DATE,
|
|
45
|
+
allowNull: true
|
|
46
|
+
},
|
|
47
|
+
revoked_at: {
|
|
48
|
+
type: sequelize_1.DataTypes.DATE,
|
|
49
|
+
allowNull: true
|
|
50
|
+
},
|
|
51
|
+
patreon_member_id: {
|
|
52
|
+
type: sequelize_1.DataTypes.STRING,
|
|
53
|
+
allowNull: true
|
|
54
|
+
},
|
|
55
|
+
note: {
|
|
56
|
+
type: sequelize_1.DataTypes.TEXT,
|
|
57
|
+
allowNull: true
|
|
58
|
+
}
|
|
59
|
+
}, {
|
|
60
|
+
sequelize,
|
|
61
|
+
tableName: 'PremiumGrant',
|
|
62
|
+
schema: 'public',
|
|
63
|
+
timestamps: false,
|
|
64
|
+
indexes: [
|
|
65
|
+
{
|
|
66
|
+
name: 'PremiumGrant_pk',
|
|
67
|
+
unique: true,
|
|
68
|
+
fields: [{ name: 'grant_id' }]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'PremiumGrant_user_id_idx',
|
|
72
|
+
fields: [{ name: 'user_id' }]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'PremiumGrant_patreon_member_id_idx',
|
|
76
|
+
fields: [{ name: 'patreon_member_id' }]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.PremiumGrantModel = PremiumGrantModel;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as Sequelize from 'sequelize';
|
|
2
|
+
import { Model } from 'sequelize';
|
|
3
|
+
import { TeamId, TeamModel } from '.';
|
|
4
|
+
import { JerseyConfig } from '../../service';
|
|
5
|
+
export interface TeamJerseyAttributes {
|
|
6
|
+
team_id: string;
|
|
7
|
+
config: JerseyConfig;
|
|
8
|
+
}
|
|
9
|
+
export type TeamJerseyPk = 'team_id';
|
|
10
|
+
export type TeamJerseyId = TeamJerseyModel[TeamJerseyPk];
|
|
11
|
+
export type TeamJerseyCreationAttributes = TeamJerseyAttributes;
|
|
12
|
+
export declare class TeamJerseyModel extends Model<TeamJerseyAttributes, TeamJerseyCreationAttributes> implements TeamJerseyAttributes {
|
|
13
|
+
team_id: string;
|
|
14
|
+
config: JerseyConfig;
|
|
15
|
+
team: TeamModel;
|
|
16
|
+
getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
|
|
17
|
+
setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
|
|
18
|
+
createTeam: Sequelize.BelongsToCreateAssociationMixin<TeamModel>;
|
|
19
|
+
static initModel(sequelize: Sequelize.Sequelize): typeof TeamJerseyModel;
|
|
20
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TeamJerseyModel = void 0;
|
|
4
|
+
const sequelize_1 = require("sequelize");
|
|
5
|
+
class TeamJerseyModel extends sequelize_1.Model {
|
|
6
|
+
static initModel(sequelize) {
|
|
7
|
+
return TeamJerseyModel.init({
|
|
8
|
+
team_id: {
|
|
9
|
+
type: sequelize_1.DataTypes.UUID,
|
|
10
|
+
allowNull: false,
|
|
11
|
+
primaryKey: true,
|
|
12
|
+
references: {
|
|
13
|
+
model: 'Team',
|
|
14
|
+
key: 'team_id'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
config: {
|
|
18
|
+
type: sequelize_1.DataTypes.JSONB,
|
|
19
|
+
allowNull: false
|
|
20
|
+
}
|
|
21
|
+
}, {
|
|
22
|
+
sequelize,
|
|
23
|
+
tableName: 'TeamJersey',
|
|
24
|
+
schema: 'public',
|
|
25
|
+
timestamps: false,
|
|
26
|
+
indexes: [
|
|
27
|
+
{
|
|
28
|
+
name: 'TeamJersey_pk',
|
|
29
|
+
unique: true,
|
|
30
|
+
fields: [
|
|
31
|
+
{ name: 'team_id' }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.TeamJerseyModel = TeamJerseyModel;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Sequelize from 'sequelize';
|
|
2
2
|
import { Model, Optional } from 'sequelize';
|
|
3
|
-
import { CompetitionChampionId, CompetitionChampionModel, CompetitionId, CompetitionModel, CompetitionTeamsId, CompetitionTeamsModel, CountryId, CountryModel, DivisionAttributes, DivisionId, DivisionModel, LegacyTeamFlagModel, MatchId, MatchModel, MatchRatingId, MatchRatingModel, PlayerAttributes, PlayerId, PlayerModel, PlayerTeamId, PlayerTeamModel, RallyId, RallyModel, TacticsAttributes, TacticsId, TacticsModel } from '.';
|
|
3
|
+
import { CompetitionChampionId, CompetitionChampionModel, CompetitionId, CompetitionModel, CompetitionTeamsId, CompetitionTeamsModel, CountryId, CountryModel, DivisionAttributes, DivisionId, DivisionModel, LegacyTeamFlagModel, MatchId, MatchModel, MatchRatingId, MatchRatingModel, PlayerAttributes, PlayerId, PlayerModel, PlayerTeamId, PlayerTeamModel, RallyId, RallyModel, TacticsAttributes, TacticsId, TacticsModel, TeamJerseyModel } from '.';
|
|
4
4
|
export interface TeamAttributes {
|
|
5
5
|
team_id: string;
|
|
6
6
|
name: string;
|
|
@@ -29,6 +29,8 @@ export declare class TeamModel extends Model<TeamAttributes, TeamCreationAttribu
|
|
|
29
29
|
getTactics: Sequelize.HasOneGetAssociationMixin<TacticsModel>;
|
|
30
30
|
setTactics: Sequelize.HasOneSetAssociationMixin<TacticsModel, TacticsId>;
|
|
31
31
|
createTactics: Sequelize.HasOneCreateAssociationMixin<TacticsModel>;
|
|
32
|
+
TeamJersey?: TeamJerseyModel;
|
|
33
|
+
getTeamJersey: Sequelize.HasOneGetAssociationMixin<TeamJerseyModel>;
|
|
32
34
|
country: CountryModel;
|
|
33
35
|
getCountry: Sequelize.BelongsToGetAssociationMixin<CountryModel>;
|
|
34
36
|
setCountry: Sequelize.BelongsToSetAssociationMixin<CountryModel, CountryId>;
|
|
@@ -50,6 +50,9 @@ function transformToObject(model, currentIteration, tacticsOverride) {
|
|
|
50
50
|
rating: model.rating,
|
|
51
51
|
tactics,
|
|
52
52
|
roster,
|
|
53
|
-
divisionId: model.division_id
|
|
53
|
+
divisionId: model.division_id,
|
|
54
|
+
// Present only when the caller explicitly included the TeamJersey association (editor / effective-jersey
|
|
55
|
+
// resolver); undefined on ordinary team loads, which heals to the id-derived default in the UI.
|
|
56
|
+
jerseyConfig: model.TeamJersey?.config
|
|
54
57
|
});
|
|
55
58
|
}
|
|
@@ -21,4 +21,5 @@ __exportStar(require("./event"), exports);
|
|
|
21
21
|
__exportStar(require("./player"), exports);
|
|
22
22
|
__exportStar(require("./competition"), exports);
|
|
23
23
|
__exportStar(require("./notification"), exports);
|
|
24
|
+
__exportStar(require("./premium"), exports);
|
|
24
25
|
__exportStar(require("./utils"), exports);
|
|
@@ -194,8 +194,8 @@ function allStats(value) {
|
|
|
194
194
|
const cap = (0, improvement_1.getStatCap)(rarity_1.RarityEnum.COMMON); // 59
|
|
195
195
|
(0, globals_1.expect)((0, improvement_1.selectImprovableStat)(rarity_1.RarityEnum.COMMON, stats_1.StatsEnum.ATTACK, allStats(cap))).toBeNull();
|
|
196
196
|
});
|
|
197
|
-
(0, globals_1.it)('returns a stat from the SERVE focus pool (serve, awareness, attack)', () => {
|
|
198
|
-
const focusPool = new Set(['serve', 'awareness', 'attack']);
|
|
197
|
+
(0, globals_1.it)('returns a stat from the SERVE focus pool (serve, awareness, focus, attack)', () => {
|
|
198
|
+
const focusPool = new Set(['serve', 'awareness', 'focus', 'attack']);
|
|
199
199
|
const stats = allStats(1);
|
|
200
200
|
// Run many times to confirm we never escape the focus pool
|
|
201
201
|
for (let i = 0; i < 100; i++) {
|
|
@@ -215,13 +215,13 @@ function allStats(value) {
|
|
|
215
215
|
});
|
|
216
216
|
(0, globals_1.it)('falls back to any improvable stat when all focus-pool stats are capped', () => {
|
|
217
217
|
const cap = (0, improvement_1.getStatCap)(rarity_1.RarityEnum.COMMON);
|
|
218
|
-
// Cap the SERVE focus stats: serve, awareness, attack
|
|
219
|
-
const stats = { ...allStats(1), serve: cap, awareness: cap, attack: cap };
|
|
218
|
+
// Cap the SERVE focus stats: serve, awareness, focus, attack
|
|
219
|
+
const stats = { ...allStats(1), serve: cap, awareness: cap, focus: cap, attack: cap };
|
|
220
220
|
for (let i = 0; i < 50; i++) {
|
|
221
221
|
const result = (0, improvement_1.selectImprovableStat)(rarity_1.RarityEnum.COMMON, stats_1.StatsEnum.SERVE, stats);
|
|
222
222
|
(0, globals_1.expect)(result).not.toBeNull();
|
|
223
223
|
// Must never return one of the capped focus stats
|
|
224
|
-
(0, globals_1.expect)(['serve', 'awareness', 'attack']).not.toContain(result);
|
|
224
|
+
(0, globals_1.expect)(['serve', 'awareness', 'focus', 'attack']).not.toContain(result);
|
|
225
225
|
}
|
|
226
226
|
});
|
|
227
227
|
(0, globals_1.it)('returns a string (DB column name) not a PerformanceStatsKey', () => {
|
|
@@ -22,12 +22,14 @@ function getMultipliers(statName, performanceStats) {
|
|
|
22
22
|
switch (statName) {
|
|
23
23
|
case StatsEnum.ATTACK:
|
|
24
24
|
return {
|
|
25
|
-
spike: 0.
|
|
25
|
+
spike: 0.5,
|
|
26
26
|
backAttack: 0.1,
|
|
27
27
|
power: performanceStats == null ? 0.125 : performanceStats.power >= performanceStats.quick ? 0.2 : 0.05,
|
|
28
28
|
quick: performanceStats == null ? 0.125 : performanceStats.quick > performanceStats.power ? 0.2 : 0.05,
|
|
29
29
|
awareness: 0.05,
|
|
30
|
-
attack: 0.05
|
|
30
|
+
attack: 0.05,
|
|
31
|
+
// focus places the spike inside its aim cone (attack-shot.focusLanding): placement precision, kill vs dug.
|
|
32
|
+
focus: 0.05
|
|
31
33
|
};
|
|
32
34
|
case StatsEnum.SET:
|
|
33
35
|
return {
|
|
@@ -36,33 +38,45 @@ function getMultipliers(statName, performanceStats) {
|
|
|
36
38
|
// rating AND in the SET training pool. Without it a setter on SET focus can never raise `quick` (it has
|
|
37
39
|
// weight 0 in every role too), and their whole tempo game would be fixed forever by a generation roll.
|
|
38
40
|
quick: 0.07,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
// set TECHNIQUE: overhand sets good passes, bump sets scramble/negative passes (both +/-15% on set score).
|
|
42
|
+
overhand: 0.08,
|
|
43
|
+
bump: 0.07,
|
|
44
|
+
// attack is the setter's crit-style upside on the set (setAttackMod in the engine, owner 2026-08-17), the
|
|
45
|
+
// same modifier the spike + serve use. awareness dropped: the engine reads it only for the setter DUMP.
|
|
41
46
|
attack: 0.05
|
|
42
47
|
};
|
|
43
48
|
case StatsEnum.RECEIVE:
|
|
49
|
+
// reflex carries the most (owner 2026-08-19): reception success is gated by REFLEX (the reach + lunge-save on
|
|
50
|
+
// any serve that lands out of position), not by the `reception` control stat. A low-reflex passer is aced
|
|
51
|
+
// regardless of reception -- match 3aebe480 had a Mythic (reception 88, reflex 36) shank 79% of receptions,
|
|
52
|
+
// and the league >=40%-ace group averages reflex 24-32 across every rarity. `reception` is the pass CONTROL
|
|
53
|
+
// once the ball is reached; bump/overhand are technique; defense is the pass variance; focus avoids the seam ace.
|
|
44
54
|
return {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
bump: 0.
|
|
48
|
-
|
|
49
|
-
defense: 0.
|
|
50
|
-
|
|
55
|
+
reflex: 0.45,
|
|
56
|
+
reception: 0.3,
|
|
57
|
+
bump: 0.1,
|
|
58
|
+
overhand: 0.05,
|
|
59
|
+
defense: 0.05,
|
|
60
|
+
focus: 0.05
|
|
51
61
|
};
|
|
52
62
|
case StatsEnum.SERVE:
|
|
53
63
|
return {
|
|
54
|
-
serve: 0.
|
|
55
|
-
awareness
|
|
56
|
-
|
|
64
|
+
serve: 0.75,
|
|
65
|
+
// awareness (target selection) + focus (aim-cone placement) are important serve drivers (owner 2026-08-17).
|
|
66
|
+
awareness: 0.1,
|
|
67
|
+
focus: 0.1,
|
|
68
|
+
attack: 0.05
|
|
57
69
|
};
|
|
58
70
|
case StatsEnum.BLOCK:
|
|
59
71
|
return {
|
|
60
|
-
block: 0.
|
|
72
|
+
block: 0.6,
|
|
61
73
|
read: performanceStats == null ? 0.125 : performanceStats.read >= performanceStats.commit ? 0.2 : 0.05,
|
|
62
74
|
commit: performanceStats == null ? 0.125 : performanceStats.commit > performanceStats.read ? 0.2 : 0.05,
|
|
63
|
-
|
|
75
|
+
// reflex sets block REACH (help blockers forming doubles/triples; dominant for surprise/dump blocks): raised
|
|
76
|
+
// from 0.02 to match its impact.
|
|
77
|
+
reflex: 0.08,
|
|
64
78
|
defense: 0.04,
|
|
65
|
-
|
|
79
|
+
focus: 0.03
|
|
66
80
|
};
|
|
67
81
|
case StatsEnum.STAMINA:
|
|
68
82
|
return { stamina: 1 };
|
|
@@ -64,10 +64,10 @@ function makeStats(base = 50, overrides = {}) {
|
|
|
64
64
|
const total = Object.values(m).reduce((s, v) => s + (v ?? 0), 0);
|
|
65
65
|
(0, globals_1.expect)(total).toBeCloseTo(1, 10);
|
|
66
66
|
});
|
|
67
|
-
(0, globals_1.it)('
|
|
67
|
+
(0, globals_1.it)('reflex has the highest weight (reflex gates the reach + lunge-save)', () => {
|
|
68
68
|
const m = (0, stats_1.getMultipliers)(stats_1.StatsEnum.RECEIVE);
|
|
69
69
|
const max = Math.max(...Object.values(m).map(v => v ?? 0));
|
|
70
|
-
(0, globals_1.expect)(m.
|
|
70
|
+
(0, globals_1.expect)(m.reflex).toBe(max);
|
|
71
71
|
});
|
|
72
72
|
});
|
|
73
73
|
(0, globals_1.describe)('SERVE', () => {
|
|
@@ -147,11 +147,11 @@ function makeStats(base = 50, overrides = {}) {
|
|
|
147
147
|
const strong = (0, stats_1.calculateStatScore)(makeStats(80), stats_1.StatsEnum.SERVE);
|
|
148
148
|
(0, globals_1.expect)(strong).toBeGreaterThan(weak);
|
|
149
149
|
});
|
|
150
|
-
(0, globals_1.it)('SERVE: serve=99 dominates, contributing 0.
|
|
151
|
-
// All other serve-relevant stats at 1: awareness=1, attack=1
|
|
152
|
-
// score ≈ 99*0.
|
|
150
|
+
(0, globals_1.it)('SERVE: serve=99 dominates, contributing 0.75 of the total', () => {
|
|
151
|
+
// All other serve-relevant stats at 1: awareness=1, focus=1, attack=1
|
|
152
|
+
// score ≈ 99*0.75 + 1*0.1 + 1*0.1 + 1*0.05 = 74.25 + 0.25 = 74.5 → 74
|
|
153
153
|
const stats = makeStats(1, { serve: 99 });
|
|
154
|
-
(0, globals_1.expect)((0, stats_1.calculateStatScore)(stats, stats_1.StatsEnum.SERVE)).toBe(
|
|
154
|
+
(0, globals_1.expect)((0, stats_1.calculateStatScore)(stats, stats_1.StatsEnum.SERVE)).toBe(74);
|
|
155
155
|
});
|
|
156
156
|
});
|
|
157
157
|
// ─── invariants (every stat, every branch) ────────────────────────────────────
|