volleyballsimtypes 0.0.360 → 0.0.363
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 +2 -1
- package/dist/cjs/src/data/init-models.js +5 -0
- package/dist/cjs/src/data/models/index.d.ts +1 -0
- package/dist/cjs/src/data/models/index.js +1 -0
- package/dist/cjs/src/data/models/retired-player.d.ts +27 -0
- package/dist/cjs/src/data/models/retired-player.js +50 -0
- package/dist/cjs/src/service/player/decline.test.d.ts +1 -0
- package/dist/cjs/src/service/player/decline.test.js +102 -0
- package/dist/esm/src/api/index.d.ts +2 -1
- package/dist/esm/src/data/init-models.js +6 -1
- package/dist/esm/src/data/models/index.d.ts +1 -0
- package/dist/esm/src/data/models/index.js +1 -0
- package/dist/esm/src/data/models/retired-player.d.ts +27 -0
- package/dist/esm/src/data/models/retired-player.js +46 -0
- package/dist/esm/src/service/player/decline.test.d.ts +1 -0
- package/dist/esm/src/service/player/decline.test.js +100 -0
- package/package.json +2 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoxScore, DataProps, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, Country as _Country, CourtPosition } from '../service';
|
|
1
|
+
import { BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, Country as _Country, CourtPosition } from '../service';
|
|
2
2
|
export type Rally = DataProps<_Rally> & {
|
|
3
3
|
homePlayerPosition: PlayerPosition[];
|
|
4
4
|
awayPlayerPosition: PlayerPosition[];
|
|
@@ -33,6 +33,7 @@ export type Player = DataProps<_Player> & {
|
|
|
33
33
|
teamId: string;
|
|
34
34
|
jerseyNumber: number;
|
|
35
35
|
locked: boolean;
|
|
36
|
+
declineStatus: DeclineStatus;
|
|
36
37
|
};
|
|
37
38
|
export interface StartingLineup {
|
|
38
39
|
[CourtPosition.LEFT_FRONT]: string;
|
|
@@ -40,6 +40,7 @@ function initModels(sequelize) {
|
|
|
40
40
|
const Player = models_1.PlayerModel.initModel(sequelize);
|
|
41
41
|
const PlayerImprovementLog = models_1.PlayerImprovementLogModel.initModel(sequelize);
|
|
42
42
|
const PlayerTeam = models_1.PlayerTeamModel.initModel(sequelize);
|
|
43
|
+
const RetiredPlayer = models_1.RetiredPlayerModel.initModel(sequelize);
|
|
43
44
|
const Rally = models_1.RallyModel.initModel(sequelize);
|
|
44
45
|
const PromotionMatch = models_1.PromotionMatchModel.initModel(sequelize);
|
|
45
46
|
const RegionQualifier = models_1.RegionQualifierModel.initModel(sequelize);
|
|
@@ -168,6 +169,9 @@ function initModels(sequelize) {
|
|
|
168
169
|
Player.hasOne(BoxScoreTotals, { as: 'BoxScoreTotals', foreignKey: 'player_id' });
|
|
169
170
|
PlayerTeam.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
|
|
170
171
|
PlayerTeam.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
172
|
+
RetiredPlayer.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
|
|
173
|
+
RetiredPlayer.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
174
|
+
Player.hasOne(RetiredPlayer, { as: 'RetiredPlayer', foreignKey: 'player_id' });
|
|
171
175
|
Rally.belongsTo(MatchSet, { as: 'matchSet', foreignKey: 'match_set_id' });
|
|
172
176
|
Rally.belongsTo(Team, { as: 'ServingTeam', foreignKey: 'serving_team' });
|
|
173
177
|
PromotionMatch.belongsTo(Iteration, { as: 'iterationData', foreignKey: 'iteration' });
|
|
@@ -242,6 +246,7 @@ function initModels(sequelize) {
|
|
|
242
246
|
PerformanceStats,
|
|
243
247
|
Player,
|
|
244
248
|
PlayerTeam,
|
|
249
|
+
RetiredPlayer,
|
|
245
250
|
PlayerImprovementLog,
|
|
246
251
|
PromotionMatch,
|
|
247
252
|
RegionQualifier,
|
|
@@ -23,6 +23,7 @@ export * from './performance-stats';
|
|
|
23
23
|
export * from './player';
|
|
24
24
|
export * from './player-improvement-log';
|
|
25
25
|
export * from './player-team';
|
|
26
|
+
export * from './retired-player';
|
|
26
27
|
export * from './rally';
|
|
27
28
|
export * from './promotion-match';
|
|
28
29
|
export * from './region-qualifier';
|
|
@@ -39,6 +39,7 @@ __exportStar(require("./performance-stats"), exports);
|
|
|
39
39
|
__exportStar(require("./player"), exports);
|
|
40
40
|
__exportStar(require("./player-improvement-log"), exports);
|
|
41
41
|
__exportStar(require("./player-team"), exports);
|
|
42
|
+
__exportStar(require("./retired-player"), exports);
|
|
42
43
|
__exportStar(require("./rally"), exports);
|
|
43
44
|
__exportStar(require("./promotion-match"), exports);
|
|
44
45
|
__exportStar(require("./region-qualifier"), exports);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as Sequelize from 'sequelize';
|
|
2
|
+
import { Model } from 'sequelize';
|
|
3
|
+
import { PlayerId, PlayerModel, TeamId, TeamModel } from '.';
|
|
4
|
+
export interface RetiredPlayerAttributes {
|
|
5
|
+
player_id: string;
|
|
6
|
+
user_id: string;
|
|
7
|
+
team_id: string;
|
|
8
|
+
retired_iteration: number;
|
|
9
|
+
}
|
|
10
|
+
export type RetiredPlayerPk = 'player_id';
|
|
11
|
+
export type RetiredPlayerId = RetiredPlayerModel[RetiredPlayerPk];
|
|
12
|
+
export type RetiredPlayerCreationAttributes = RetiredPlayerAttributes;
|
|
13
|
+
export declare class RetiredPlayerModel extends Model<RetiredPlayerAttributes, RetiredPlayerCreationAttributes> implements RetiredPlayerAttributes {
|
|
14
|
+
player_id: string;
|
|
15
|
+
user_id: string;
|
|
16
|
+
team_id: string;
|
|
17
|
+
retired_iteration: number;
|
|
18
|
+
player: PlayerModel;
|
|
19
|
+
getPlayer: Sequelize.BelongsToGetAssociationMixin<PlayerModel>;
|
|
20
|
+
setPlayer: Sequelize.BelongsToSetAssociationMixin<PlayerModel, PlayerId>;
|
|
21
|
+
createPlayer: Sequelize.BelongsToCreateAssociationMixin<PlayerModel>;
|
|
22
|
+
team: TeamModel;
|
|
23
|
+
getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
|
|
24
|
+
setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
|
|
25
|
+
createTeam: Sequelize.BelongsToCreateAssociationMixin<TeamModel>;
|
|
26
|
+
static initModel(sequelize: Sequelize.Sequelize): typeof RetiredPlayerModel;
|
|
27
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RetiredPlayerModel = void 0;
|
|
4
|
+
const sequelize_1 = require("sequelize");
|
|
5
|
+
class RetiredPlayerModel extends sequelize_1.Model {
|
|
6
|
+
static initModel(sequelize) {
|
|
7
|
+
return RetiredPlayerModel.init({
|
|
8
|
+
player_id: {
|
|
9
|
+
type: sequelize_1.DataTypes.UUID,
|
|
10
|
+
allowNull: false,
|
|
11
|
+
primaryKey: true,
|
|
12
|
+
references: {
|
|
13
|
+
model: 'Player',
|
|
14
|
+
key: 'player_id'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
user_id: {
|
|
18
|
+
type: sequelize_1.DataTypes.UUID,
|
|
19
|
+
allowNull: false,
|
|
20
|
+
references: {
|
|
21
|
+
model: 'AuthUser',
|
|
22
|
+
key: 'user_id'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
team_id: {
|
|
26
|
+
type: sequelize_1.DataTypes.UUID,
|
|
27
|
+
allowNull: false,
|
|
28
|
+
references: {
|
|
29
|
+
model: 'Team',
|
|
30
|
+
key: 'team_id'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
retired_iteration: {
|
|
34
|
+
type: sequelize_1.DataTypes.INTEGER,
|
|
35
|
+
allowNull: false
|
|
36
|
+
}
|
|
37
|
+
}, {
|
|
38
|
+
sequelize,
|
|
39
|
+
tableName: 'RetiredPlayer',
|
|
40
|
+
schema: 'public',
|
|
41
|
+
timestamps: false,
|
|
42
|
+
indexes: [{
|
|
43
|
+
name: 'RetiredPlayer_pk',
|
|
44
|
+
unique: true,
|
|
45
|
+
fields: [{ name: 'player_id' }]
|
|
46
|
+
}]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.RetiredPlayerModel = RetiredPlayerModel;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globals_1 = require("@jest/globals");
|
|
4
|
+
const decline_1 = require("./decline");
|
|
5
|
+
const performance_stats_keys_1 = require("./performance-stats-keys");
|
|
6
|
+
// ─── getDeclineProfile ─────────────────────────────────────────────────────────
|
|
7
|
+
(0, globals_1.describe)('getDeclineProfile()', () => {
|
|
8
|
+
globals_1.it.each([
|
|
9
|
+
[decline_1.DeclineProfileEnum.PRODIGY, 27, 13.0, 1.0, 16.0],
|
|
10
|
+
[decline_1.DeclineProfileEnum.STANDARD, 29, 9.0, 1.75, 16.0],
|
|
11
|
+
[decline_1.DeclineProfileEnum.VETERAN, 32, 10.0, 1.5, 16.0],
|
|
12
|
+
[decline_1.DeclineProfileEnum.IRONMAN, 34, 10.0, 1.5, 16.0]
|
|
13
|
+
])('%s → startAge %i', (profile, startAge, initTicks, accel, peakTicks) => {
|
|
14
|
+
const entry = (0, decline_1.getDeclineProfile)(profile);
|
|
15
|
+
(0, globals_1.expect)(entry.startAge).toBe(startAge);
|
|
16
|
+
(0, globals_1.expect)(entry.initTicks).toBeCloseTo(initTicks, 5);
|
|
17
|
+
(0, globals_1.expect)(entry.accel).toBeCloseTo(accel, 5);
|
|
18
|
+
(0, globals_1.expect)(entry.peakTicks).toBeCloseTo(peakTicks, 5);
|
|
19
|
+
});
|
|
20
|
+
(0, globals_1.it)('throws UNKNOWN_DECLINE_PROFILE for an unrecognised profile', () => {
|
|
21
|
+
(0, globals_1.expect)(() => (0, decline_1.getDeclineProfile)('LEGEND')).toThrow('UNKNOWN_DECLINE_PROFILE');
|
|
22
|
+
});
|
|
23
|
+
(0, globals_1.it)('declineProfiles lists all four enum values', () => {
|
|
24
|
+
(0, globals_1.expect)([...decline_1.declineProfiles].sort()).toEqual([...Object.values(decline_1.DeclineProfileEnum)].sort());
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
// ─── stat pools ────────────────────────────────────────────────────────────────
|
|
28
|
+
(0, globals_1.describe)('decline stat pools', () => {
|
|
29
|
+
(0, globals_1.it)('SKILL_POOL and ENERGY_POOL are disjoint', () => {
|
|
30
|
+
const overlap = decline_1.SKILL_POOL.filter(k => decline_1.ENERGY_POOL.includes(k));
|
|
31
|
+
(0, globals_1.expect)(overlap).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
(0, globals_1.it)('together they cover every performance stat key exactly once', () => {
|
|
34
|
+
const union = [...decline_1.SKILL_POOL, ...decline_1.ENERGY_POOL].sort();
|
|
35
|
+
(0, globals_1.expect)(union).toEqual([...performance_stats_keys_1.performanceStatKeys].sort());
|
|
36
|
+
});
|
|
37
|
+
(0, globals_1.it)('ENERGY_POOL holds the reaction/energy stats (incl. crit-chance stats)', () => {
|
|
38
|
+
(0, globals_1.expect)([...decline_1.ENERGY_POOL].sort()).toEqual(['awareness', 'commit', 'focus', 'read', 'reflex', 'stamina'].sort());
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
// ─── computeDeclineTick ────────────────────────────────────────────────────────
|
|
42
|
+
(0, globals_1.describe)('computeDeclineTick()', () => {
|
|
43
|
+
(0, globals_1.it)('returns no decline before the profile start age', () => {
|
|
44
|
+
for (const profile of decline_1.declineProfiles) {
|
|
45
|
+
const { startAge } = (0, decline_1.getDeclineProfile)(profile);
|
|
46
|
+
const tick = (0, decline_1.computeDeclineTick)(startAge - 1, profile);
|
|
47
|
+
(0, globals_1.expect)(tick.ticks).toBe(0);
|
|
48
|
+
(0, globals_1.expect)(tick.atPeak).toBe(false);
|
|
49
|
+
(0, globals_1.expect)(tick.status).toBe('ACTIVE');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
(0, globals_1.it)('at the start age, tick rate equals round(initTicks) and status is DECLINING', () => {
|
|
53
|
+
for (const profile of decline_1.declineProfiles) {
|
|
54
|
+
const { startAge, initTicks } = (0, decline_1.getDeclineProfile)(profile);
|
|
55
|
+
const tick = (0, decline_1.computeDeclineTick)(startAge, profile);
|
|
56
|
+
(0, globals_1.expect)(tick.ticks).toBe(Math.round(initTicks));
|
|
57
|
+
(0, globals_1.expect)(tick.atPeak).toBe(false);
|
|
58
|
+
(0, globals_1.expect)(tick.status).toBe('DECLINING');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
(0, globals_1.it)('tick count is non-decreasing as age increases', () => {
|
|
62
|
+
for (const profile of decline_1.declineProfiles) {
|
|
63
|
+
const { startAge } = (0, decline_1.getDeclineProfile)(profile);
|
|
64
|
+
let prev = -1;
|
|
65
|
+
for (let age = startAge; age <= startAge + 30; age++) {
|
|
66
|
+
const { ticks } = (0, decline_1.computeDeclineTick)(age, profile);
|
|
67
|
+
(0, globals_1.expect)(ticks).toBeGreaterThanOrEqual(prev);
|
|
68
|
+
prev = ticks;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
(0, globals_1.it)('never exceeds peakTicks, and reaching it flips atPeak / PEAK', () => {
|
|
73
|
+
for (const profile of decline_1.declineProfiles) {
|
|
74
|
+
const { startAge, peakTicks } = (0, decline_1.getDeclineProfile)(profile);
|
|
75
|
+
for (let age = startAge; age <= startAge + 40; age++) {
|
|
76
|
+
const tick = (0, decline_1.computeDeclineTick)(age, profile);
|
|
77
|
+
(0, globals_1.expect)(tick.ticks).toBeLessThanOrEqual(Math.round(peakTicks));
|
|
78
|
+
if (tick.atPeak) {
|
|
79
|
+
(0, globals_1.expect)(tick.ticks).toBe(Math.round(peakTicks));
|
|
80
|
+
(0, globals_1.expect)(tick.status).toBe('PEAK');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
(0, globals_1.it)('peak triggers at the analytically expected age for each profile', () => {
|
|
86
|
+
// age at peak = startAge + ceil((peakTicks - initTicks) / accel)
|
|
87
|
+
for (const profile of decline_1.declineProfiles) {
|
|
88
|
+
const { startAge, initTicks, accel, peakTicks } = (0, decline_1.getDeclineProfile)(profile);
|
|
89
|
+
const peakAge = startAge + Math.ceil((peakTicks - initTicks) / accel);
|
|
90
|
+
(0, globals_1.expect)((0, decline_1.computeDeclineTick)(peakAge - 1, profile).atPeak).toBe(false);
|
|
91
|
+
(0, globals_1.expect)((0, decline_1.computeDeclineTick)(peakAge, profile).atPeak).toBe(true);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
(0, globals_1.it)('Standard profile matches the validated model at key ages', () => {
|
|
95
|
+
// initTicks 9, accel 1.75, peakTicks 16, startAge 29
|
|
96
|
+
(0, globals_1.expect)((0, decline_1.computeDeclineTick)(28, decline_1.DeclineProfileEnum.STANDARD)).toEqual({ ticks: 0, atPeak: false, status: 'ACTIVE' });
|
|
97
|
+
(0, globals_1.expect)((0, decline_1.computeDeclineTick)(29, decline_1.DeclineProfileEnum.STANDARD).ticks).toBe(9);
|
|
98
|
+
(0, globals_1.expect)((0, decline_1.computeDeclineTick)(30, decline_1.DeclineProfileEnum.STANDARD).ticks).toBe(Math.round(9 + 1.75)); // 11
|
|
99
|
+
// 9 + 4*1.75 = 16 → peak at age 33
|
|
100
|
+
(0, globals_1.expect)((0, decline_1.computeDeclineTick)(33, decline_1.DeclineProfileEnum.STANDARD).atPeak).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoxScore, DataProps, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, Country as _Country, CourtPosition } from '../service';
|
|
1
|
+
import { BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, Country as _Country, CourtPosition } from '../service';
|
|
2
2
|
export type Rally = DataProps<_Rally> & {
|
|
3
3
|
homePlayerPosition: PlayerPosition[];
|
|
4
4
|
awayPlayerPosition: PlayerPosition[];
|
|
@@ -33,6 +33,7 @@ export type Player = DataProps<_Player> & {
|
|
|
33
33
|
teamId: string;
|
|
34
34
|
jerseyNumber: number;
|
|
35
35
|
locked: boolean;
|
|
36
|
+
declineStatus: DeclineStatus;
|
|
36
37
|
};
|
|
37
38
|
export interface StartingLineup {
|
|
38
39
|
[CourtPosition.LEFT_FRONT]: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthIdentityModel, AuthSessionModel, AuthUserModel, BoxScoreModel, CoachModel, BoxScoreTotalsModel, CompetitionChampionModel, CurrencyTransactionModel, GachaPityModel, GachaPullHistoryModel, WishlistModel, UserSettingsModel, CompetitionMatchModel, CompetitionMVPModel, CompetitionModel, CompetitionStandingsMatchModel, CompetitionStandingsModel, CompetitionTeamsModel, CountryModel, DivisionModel, DivisionSeasonModel, IterationModel, LeagueModel, MatchModel, MatchRatingModel, MatchResultModel, MatchSetModel, PerformanceStatsModel, PlayerModel, PlayerImprovementLogModel, PlayerTeamModel, PromotionMatchModel, RegionQualifierModel, RallyModel, TacticsModel, TeamModel, TeamReplacementModel, UserCurrencyModel, UserPlayerProgressModel, UserPullGrantModel, UserTeamModel, VPERModel, NotificationModel } from './models';
|
|
1
|
+
import { AuthIdentityModel, AuthSessionModel, AuthUserModel, BoxScoreModel, CoachModel, BoxScoreTotalsModel, CompetitionChampionModel, CurrencyTransactionModel, GachaPityModel, GachaPullHistoryModel, WishlistModel, UserSettingsModel, CompetitionMatchModel, CompetitionMVPModel, CompetitionModel, CompetitionStandingsMatchModel, CompetitionStandingsModel, CompetitionTeamsModel, CountryModel, DivisionModel, DivisionSeasonModel, IterationModel, LeagueModel, MatchModel, MatchRatingModel, MatchResultModel, MatchSetModel, PerformanceStatsModel, PlayerModel, PlayerImprovementLogModel, PlayerTeamModel, RetiredPlayerModel, PromotionMatchModel, RegionQualifierModel, RallyModel, TacticsModel, TeamModel, TeamReplacementModel, UserCurrencyModel, UserPlayerProgressModel, UserPullGrantModel, UserTeamModel, VPERModel, NotificationModel } from './models';
|
|
2
2
|
export function initModels(sequelize) {
|
|
3
3
|
const Coach = CoachModel.initModel(sequelize);
|
|
4
4
|
const CurrencyTransaction = CurrencyTransactionModel.initModel(sequelize);
|
|
@@ -37,6 +37,7 @@ export function initModels(sequelize) {
|
|
|
37
37
|
const Player = PlayerModel.initModel(sequelize);
|
|
38
38
|
const PlayerImprovementLog = PlayerImprovementLogModel.initModel(sequelize);
|
|
39
39
|
const PlayerTeam = PlayerTeamModel.initModel(sequelize);
|
|
40
|
+
const RetiredPlayer = RetiredPlayerModel.initModel(sequelize);
|
|
40
41
|
const Rally = RallyModel.initModel(sequelize);
|
|
41
42
|
const PromotionMatch = PromotionMatchModel.initModel(sequelize);
|
|
42
43
|
const RegionQualifier = RegionQualifierModel.initModel(sequelize);
|
|
@@ -165,6 +166,9 @@ export function initModels(sequelize) {
|
|
|
165
166
|
Player.hasOne(BoxScoreTotals, { as: 'BoxScoreTotals', foreignKey: 'player_id' });
|
|
166
167
|
PlayerTeam.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
|
|
167
168
|
PlayerTeam.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
169
|
+
RetiredPlayer.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
|
|
170
|
+
RetiredPlayer.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
171
|
+
Player.hasOne(RetiredPlayer, { as: 'RetiredPlayer', foreignKey: 'player_id' });
|
|
168
172
|
Rally.belongsTo(MatchSet, { as: 'matchSet', foreignKey: 'match_set_id' });
|
|
169
173
|
Rally.belongsTo(Team, { as: 'ServingTeam', foreignKey: 'serving_team' });
|
|
170
174
|
PromotionMatch.belongsTo(Iteration, { as: 'iterationData', foreignKey: 'iteration' });
|
|
@@ -239,6 +243,7 @@ export function initModels(sequelize) {
|
|
|
239
243
|
PerformanceStats,
|
|
240
244
|
Player,
|
|
241
245
|
PlayerTeam,
|
|
246
|
+
RetiredPlayer,
|
|
242
247
|
PlayerImprovementLog,
|
|
243
248
|
PromotionMatch,
|
|
244
249
|
RegionQualifier,
|
|
@@ -23,6 +23,7 @@ export * from './performance-stats';
|
|
|
23
23
|
export * from './player';
|
|
24
24
|
export * from './player-improvement-log';
|
|
25
25
|
export * from './player-team';
|
|
26
|
+
export * from './retired-player';
|
|
26
27
|
export * from './rally';
|
|
27
28
|
export * from './promotion-match';
|
|
28
29
|
export * from './region-qualifier';
|
|
@@ -23,6 +23,7 @@ export * from './performance-stats';
|
|
|
23
23
|
export * from './player';
|
|
24
24
|
export * from './player-improvement-log';
|
|
25
25
|
export * from './player-team';
|
|
26
|
+
export * from './retired-player';
|
|
26
27
|
export * from './rally';
|
|
27
28
|
export * from './promotion-match';
|
|
28
29
|
export * from './region-qualifier';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as Sequelize from 'sequelize';
|
|
2
|
+
import { Model } from 'sequelize';
|
|
3
|
+
import { PlayerId, PlayerModel, TeamId, TeamModel } from '.';
|
|
4
|
+
export interface RetiredPlayerAttributes {
|
|
5
|
+
player_id: string;
|
|
6
|
+
user_id: string;
|
|
7
|
+
team_id: string;
|
|
8
|
+
retired_iteration: number;
|
|
9
|
+
}
|
|
10
|
+
export type RetiredPlayerPk = 'player_id';
|
|
11
|
+
export type RetiredPlayerId = RetiredPlayerModel[RetiredPlayerPk];
|
|
12
|
+
export type RetiredPlayerCreationAttributes = RetiredPlayerAttributes;
|
|
13
|
+
export declare class RetiredPlayerModel extends Model<RetiredPlayerAttributes, RetiredPlayerCreationAttributes> implements RetiredPlayerAttributes {
|
|
14
|
+
player_id: string;
|
|
15
|
+
user_id: string;
|
|
16
|
+
team_id: string;
|
|
17
|
+
retired_iteration: number;
|
|
18
|
+
player: PlayerModel;
|
|
19
|
+
getPlayer: Sequelize.BelongsToGetAssociationMixin<PlayerModel>;
|
|
20
|
+
setPlayer: Sequelize.BelongsToSetAssociationMixin<PlayerModel, PlayerId>;
|
|
21
|
+
createPlayer: Sequelize.BelongsToCreateAssociationMixin<PlayerModel>;
|
|
22
|
+
team: TeamModel;
|
|
23
|
+
getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
|
|
24
|
+
setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
|
|
25
|
+
createTeam: Sequelize.BelongsToCreateAssociationMixin<TeamModel>;
|
|
26
|
+
static initModel(sequelize: Sequelize.Sequelize): typeof RetiredPlayerModel;
|
|
27
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { DataTypes, Model } from 'sequelize';
|
|
2
|
+
export class RetiredPlayerModel extends Model {
|
|
3
|
+
static initModel(sequelize) {
|
|
4
|
+
return RetiredPlayerModel.init({
|
|
5
|
+
player_id: {
|
|
6
|
+
type: DataTypes.UUID,
|
|
7
|
+
allowNull: false,
|
|
8
|
+
primaryKey: true,
|
|
9
|
+
references: {
|
|
10
|
+
model: 'Player',
|
|
11
|
+
key: 'player_id'
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
user_id: {
|
|
15
|
+
type: DataTypes.UUID,
|
|
16
|
+
allowNull: false,
|
|
17
|
+
references: {
|
|
18
|
+
model: 'AuthUser',
|
|
19
|
+
key: 'user_id'
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
team_id: {
|
|
23
|
+
type: DataTypes.UUID,
|
|
24
|
+
allowNull: false,
|
|
25
|
+
references: {
|
|
26
|
+
model: 'Team',
|
|
27
|
+
key: 'team_id'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
retired_iteration: {
|
|
31
|
+
type: DataTypes.INTEGER,
|
|
32
|
+
allowNull: false
|
|
33
|
+
}
|
|
34
|
+
}, {
|
|
35
|
+
sequelize,
|
|
36
|
+
tableName: 'RetiredPlayer',
|
|
37
|
+
schema: 'public',
|
|
38
|
+
timestamps: false,
|
|
39
|
+
indexes: [{
|
|
40
|
+
name: 'RetiredPlayer_pk',
|
|
41
|
+
unique: true,
|
|
42
|
+
fields: [{ name: 'player_id' }]
|
|
43
|
+
}]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { DeclineProfileEnum, declineProfiles, getDeclineProfile, computeDeclineTick, SKILL_POOL, ENERGY_POOL } from './decline';
|
|
3
|
+
import { performanceStatKeys } from './performance-stats-keys';
|
|
4
|
+
// ─── getDeclineProfile ─────────────────────────────────────────────────────────
|
|
5
|
+
describe('getDeclineProfile()', () => {
|
|
6
|
+
it.each([
|
|
7
|
+
[DeclineProfileEnum.PRODIGY, 27, 13.0, 1.0, 16.0],
|
|
8
|
+
[DeclineProfileEnum.STANDARD, 29, 9.0, 1.75, 16.0],
|
|
9
|
+
[DeclineProfileEnum.VETERAN, 32, 10.0, 1.5, 16.0],
|
|
10
|
+
[DeclineProfileEnum.IRONMAN, 34, 10.0, 1.5, 16.0]
|
|
11
|
+
])('%s → startAge %i', (profile, startAge, initTicks, accel, peakTicks) => {
|
|
12
|
+
const entry = getDeclineProfile(profile);
|
|
13
|
+
expect(entry.startAge).toBe(startAge);
|
|
14
|
+
expect(entry.initTicks).toBeCloseTo(initTicks, 5);
|
|
15
|
+
expect(entry.accel).toBeCloseTo(accel, 5);
|
|
16
|
+
expect(entry.peakTicks).toBeCloseTo(peakTicks, 5);
|
|
17
|
+
});
|
|
18
|
+
it('throws UNKNOWN_DECLINE_PROFILE for an unrecognised profile', () => {
|
|
19
|
+
expect(() => getDeclineProfile('LEGEND')).toThrow('UNKNOWN_DECLINE_PROFILE');
|
|
20
|
+
});
|
|
21
|
+
it('declineProfiles lists all four enum values', () => {
|
|
22
|
+
expect([...declineProfiles].sort()).toEqual([...Object.values(DeclineProfileEnum)].sort());
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
// ─── stat pools ────────────────────────────────────────────────────────────────
|
|
26
|
+
describe('decline stat pools', () => {
|
|
27
|
+
it('SKILL_POOL and ENERGY_POOL are disjoint', () => {
|
|
28
|
+
const overlap = SKILL_POOL.filter(k => ENERGY_POOL.includes(k));
|
|
29
|
+
expect(overlap).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
it('together they cover every performance stat key exactly once', () => {
|
|
32
|
+
const union = [...SKILL_POOL, ...ENERGY_POOL].sort();
|
|
33
|
+
expect(union).toEqual([...performanceStatKeys].sort());
|
|
34
|
+
});
|
|
35
|
+
it('ENERGY_POOL holds the reaction/energy stats (incl. crit-chance stats)', () => {
|
|
36
|
+
expect([...ENERGY_POOL].sort()).toEqual(['awareness', 'commit', 'focus', 'read', 'reflex', 'stamina'].sort());
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
// ─── computeDeclineTick ────────────────────────────────────────────────────────
|
|
40
|
+
describe('computeDeclineTick()', () => {
|
|
41
|
+
it('returns no decline before the profile start age', () => {
|
|
42
|
+
for (const profile of declineProfiles) {
|
|
43
|
+
const { startAge } = getDeclineProfile(profile);
|
|
44
|
+
const tick = computeDeclineTick(startAge - 1, profile);
|
|
45
|
+
expect(tick.ticks).toBe(0);
|
|
46
|
+
expect(tick.atPeak).toBe(false);
|
|
47
|
+
expect(tick.status).toBe('ACTIVE');
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
it('at the start age, tick rate equals round(initTicks) and status is DECLINING', () => {
|
|
51
|
+
for (const profile of declineProfiles) {
|
|
52
|
+
const { startAge, initTicks } = getDeclineProfile(profile);
|
|
53
|
+
const tick = computeDeclineTick(startAge, profile);
|
|
54
|
+
expect(tick.ticks).toBe(Math.round(initTicks));
|
|
55
|
+
expect(tick.atPeak).toBe(false);
|
|
56
|
+
expect(tick.status).toBe('DECLINING');
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
it('tick count is non-decreasing as age increases', () => {
|
|
60
|
+
for (const profile of declineProfiles) {
|
|
61
|
+
const { startAge } = getDeclineProfile(profile);
|
|
62
|
+
let prev = -1;
|
|
63
|
+
for (let age = startAge; age <= startAge + 30; age++) {
|
|
64
|
+
const { ticks } = computeDeclineTick(age, profile);
|
|
65
|
+
expect(ticks).toBeGreaterThanOrEqual(prev);
|
|
66
|
+
prev = ticks;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
it('never exceeds peakTicks, and reaching it flips atPeak / PEAK', () => {
|
|
71
|
+
for (const profile of declineProfiles) {
|
|
72
|
+
const { startAge, peakTicks } = getDeclineProfile(profile);
|
|
73
|
+
for (let age = startAge; age <= startAge + 40; age++) {
|
|
74
|
+
const tick = computeDeclineTick(age, profile);
|
|
75
|
+
expect(tick.ticks).toBeLessThanOrEqual(Math.round(peakTicks));
|
|
76
|
+
if (tick.atPeak) {
|
|
77
|
+
expect(tick.ticks).toBe(Math.round(peakTicks));
|
|
78
|
+
expect(tick.status).toBe('PEAK');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
it('peak triggers at the analytically expected age for each profile', () => {
|
|
84
|
+
// age at peak = startAge + ceil((peakTicks - initTicks) / accel)
|
|
85
|
+
for (const profile of declineProfiles) {
|
|
86
|
+
const { startAge, initTicks, accel, peakTicks } = getDeclineProfile(profile);
|
|
87
|
+
const peakAge = startAge + Math.ceil((peakTicks - initTicks) / accel);
|
|
88
|
+
expect(computeDeclineTick(peakAge - 1, profile).atPeak).toBe(false);
|
|
89
|
+
expect(computeDeclineTick(peakAge, profile).atPeak).toBe(true);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
it('Standard profile matches the validated model at key ages', () => {
|
|
93
|
+
// initTicks 9, accel 1.75, peakTicks 16, startAge 29
|
|
94
|
+
expect(computeDeclineTick(28, DeclineProfileEnum.STANDARD)).toEqual({ ticks: 0, atPeak: false, status: 'ACTIVE' });
|
|
95
|
+
expect(computeDeclineTick(29, DeclineProfileEnum.STANDARD).ticks).toBe(9);
|
|
96
|
+
expect(computeDeclineTick(30, DeclineProfileEnum.STANDARD).ticks).toBe(Math.round(9 + 1.75)); // 11
|
|
97
|
+
// 9 + 4*1.75 = 16 → peak at age 33
|
|
98
|
+
expect(computeDeclineTick(33, DeclineProfileEnum.STANDARD).atPeak).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "volleyballsimtypes",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.363",
|
|
4
4
|
"description": "vbsim types",
|
|
5
5
|
"main": "./dist/cjs/src/index.js",
|
|
6
6
|
"module": "./dist/esm/src/index.js",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"test-types": "npm run validate && npx jest --coverage --passWithNoTests",
|
|
68
68
|
"build": "npm run test-types && yarn clean && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json",
|
|
69
69
|
"sync:consumers": "powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/sync-consumers.ps1",
|
|
70
|
+
"release": "powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/release.ps1",
|
|
70
71
|
"export:schema": "powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/export-schema.ps1",
|
|
71
72
|
"preversion": "npm run clean && npm run build && git commit -a --amend --no-edit",
|
|
72
73
|
"postversion": "git push --follow-tags && npm login && npm publish"
|