volleyballsimtypes 0.0.504 → 0.0.506
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 +3 -2
- package/dist/cjs/src/api/tactics-player-refs.js +2 -2
- package/dist/cjs/src/data/init-models.js +4 -0
- package/dist/cjs/src/data/models/base-position-preset.d.ts +22 -0
- package/dist/cjs/src/data/models/base-position-preset.js +54 -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/tactics.d.ts +5 -3
- package/dist/cjs/src/data/models/tactics.js +4 -0
- package/dist/cjs/src/data/transformers/rally-event-injury.test.js +1 -1
- package/dist/cjs/src/data/transformers/rally-event-positional.test.js +10 -2
- package/dist/cjs/src/data/transformers/rally-event.js +3 -1
- package/dist/cjs/src/data/transformers/rally.test.js +2 -2
- package/dist/cjs/src/data/transformers/tactics.js +4 -0
- package/dist/cjs/src/data/transformers/tactics.test.js +34 -10
- package/dist/cjs/src/service/competition/competition-rewards.d.ts +20 -0
- package/dist/cjs/src/service/competition/competition-rewards.js +47 -0
- package/dist/cjs/src/service/competition/competition-rewards.test.d.ts +1 -0
- package/dist/cjs/src/service/competition/competition-rewards.test.js +32 -0
- package/dist/cjs/src/service/competition/index.d.ts +2 -0
- package/dist/cjs/src/service/competition/index.js +2 -0
- package/dist/cjs/src/service/competition/league-rewards.d.ts +21 -0
- package/dist/cjs/src/service/competition/league-rewards.js +58 -0
- package/dist/cjs/src/service/competition/league-rewards.test.d.ts +1 -0
- package/dist/cjs/src/service/competition/league-rewards.test.js +50 -0
- package/dist/cjs/src/service/event/schemas/spike.z.js +3 -1
- package/dist/cjs/src/service/event/spike.d.ts +2 -4
- package/dist/cjs/src/service/event/spike.js +5 -3
- package/dist/cjs/src/service/team/base-config.d.ts +10 -0
- package/dist/cjs/src/service/team/base-config.js +14 -0
- package/dist/cjs/src/service/team/blocking-preference.d.ts +3 -1
- package/dist/cjs/src/service/team/blocking-preference.js +2 -1
- package/dist/cjs/src/service/team/default-base-config.d.ts +4 -0
- package/dist/cjs/src/service/team/default-base-config.js +128 -0
- package/dist/cjs/src/service/team/index.d.ts +2 -0
- package/dist/cjs/src/service/team/index.js +2 -0
- package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +17 -2
- package/dist/cjs/src/service/team/schemas/tactics.z.js +24 -4
- package/dist/cjs/src/service/team/schemas/tactics.z.test.js +29 -16
- package/dist/cjs/src/service/team/schemas/team.z.d.ts +17 -2
- package/dist/cjs/src/service/team/tactics.d.ts +3 -0
- package/dist/cjs/src/service/team/tactics.js +2 -1
- package/dist/cjs/src/service/team/team-generator.js +6 -1
- package/dist/esm/src/api/index.d.ts +3 -2
- package/dist/esm/src/api/tactics-player-refs.js +2 -2
- package/dist/esm/src/data/init-models.js +5 -1
- package/dist/esm/src/data/models/base-position-preset.d.ts +22 -0
- package/dist/esm/src/data/models/base-position-preset.js +50 -0
- 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/tactics.d.ts +5 -3
- package/dist/esm/src/data/models/tactics.js +4 -0
- package/dist/esm/src/data/transformers/rally-event-injury.test.js +1 -1
- package/dist/esm/src/data/transformers/rally-event-positional.test.js +10 -2
- package/dist/esm/src/data/transformers/rally-event.js +3 -1
- package/dist/esm/src/data/transformers/rally.test.js +2 -2
- package/dist/esm/src/data/transformers/tactics.js +4 -0
- package/dist/esm/src/data/transformers/tactics.test.js +34 -10
- package/dist/esm/src/service/competition/competition-rewards.d.ts +20 -0
- package/dist/esm/src/service/competition/competition-rewards.js +43 -0
- package/dist/esm/src/service/competition/competition-rewards.test.d.ts +1 -0
- package/dist/esm/src/service/competition/competition-rewards.test.js +30 -0
- package/dist/esm/src/service/competition/index.d.ts +2 -0
- package/dist/esm/src/service/competition/index.js +2 -0
- package/dist/esm/src/service/competition/league-rewards.d.ts +21 -0
- package/dist/esm/src/service/competition/league-rewards.js +53 -0
- package/dist/esm/src/service/competition/league-rewards.test.d.ts +1 -0
- package/dist/esm/src/service/competition/league-rewards.test.js +48 -0
- package/dist/esm/src/service/event/schemas/spike.z.js +3 -1
- package/dist/esm/src/service/event/spike.d.ts +2 -4
- package/dist/esm/src/service/event/spike.js +5 -3
- package/dist/esm/src/service/team/base-config.d.ts +10 -0
- package/dist/esm/src/service/team/base-config.js +11 -0
- package/dist/esm/src/service/team/blocking-preference.d.ts +3 -1
- package/dist/esm/src/service/team/blocking-preference.js +1 -0
- package/dist/esm/src/service/team/default-base-config.d.ts +4 -0
- package/dist/esm/src/service/team/default-base-config.js +124 -0
- package/dist/esm/src/service/team/index.d.ts +2 -0
- package/dist/esm/src/service/team/index.js +2 -0
- package/dist/esm/src/service/team/schemas/tactics.z.d.ts +17 -2
- package/dist/esm/src/service/team/schemas/tactics.z.js +24 -4
- package/dist/esm/src/service/team/schemas/tactics.z.test.js +29 -16
- package/dist/esm/src/service/team/schemas/team.z.d.ts +17 -2
- package/dist/esm/src/service/team/tactics.d.ts +3 -0
- package/dist/esm/src/service/team/tactics.js +2 -1
- package/dist/esm/src/service/team/team-generator.js +6 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
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, 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, BlockingAssignment, CourtPosition, ConditionLogic, EnergyBand, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand } from '../service';
|
|
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, BlockingAssignment, DefenceFormation, 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[];
|
|
@@ -102,7 +102,7 @@ export interface ApiOffensivePreference {
|
|
|
102
102
|
}
|
|
103
103
|
export interface ApiBlockingPreference {
|
|
104
104
|
rotation: number;
|
|
105
|
-
targets: Record<string, BlockingAssignment
|
|
105
|
+
targets: Record<DefenceFormation, Record<string, BlockingAssignment>>;
|
|
106
106
|
}
|
|
107
107
|
export interface ApiSystemSet {
|
|
108
108
|
rotationSystem: RotationSystemEnum;
|
|
@@ -140,6 +140,7 @@ export interface Tactics {
|
|
|
140
140
|
systemSets?: ApiSystemSet[];
|
|
141
141
|
offensivePreferenceSets?: ApiOffensivePreference[][];
|
|
142
142
|
serveAttackShares?: Record<string, number>;
|
|
143
|
+
basePositions?: BaseConfig;
|
|
143
144
|
blockingPreferences: ApiBlockingPreference[];
|
|
144
145
|
blockingPreferenceSets?: ApiBlockingPreference[][];
|
|
145
146
|
replaceKnockedImmediately?: Record<string, boolean>;
|
|
@@ -26,8 +26,8 @@ export function collectTacticsPlayerIds(config) {
|
|
|
26
26
|
...((config.offensivePreferences ?? []).flatMap(p => Object.keys(p.weights))),
|
|
27
27
|
...((config.systemSets ?? []).flatMap(s => s.designatedSetters)),
|
|
28
28
|
...((config.offensivePreferenceSets ?? []).flat().flatMap(p => Object.keys(p.weights))),
|
|
29
|
-
...((config.blockingPreferences ?? []).flatMap(p => Object.
|
|
30
|
-
...((config.blockingPreferenceSets ?? []).flat().flatMap(p => Object.
|
|
29
|
+
...((config.blockingPreferences ?? []).flatMap(p => Object.values(p.targets).flatMap(m => Object.keys(m ?? {})))),
|
|
30
|
+
...((config.blockingPreferenceSets ?? []).flat().flatMap(p => Object.values(p.targets).flatMap(m => Object.keys(m ?? {})))),
|
|
31
31
|
// Injury replacements reference reserves (roster players outside the lineup); roster membership is the only
|
|
32
32
|
// player check possible here (the schema enforces the bench/reserve role rules).
|
|
33
33
|
...((config.injuryReplacements ?? []).flatMap(ir => [ir.starterId, ir.replacementId, ir.benchBackfillId])),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthIdentityModel, AuthSessionModel, AuthUserModel, BoxScoreModel, CoachModel, ScoutModel, PhysicianModel, PhysiotherapistModel, BoxScoreTotalsModel, CompetitionChampionModel, CurrencyTransactionModel, GachaPityModel, GachaPullHistoryModel, WishlistModel, UserSettingsModel, UserDeviceTokenModel, CompetitionMatchModel, CompetitionMVPModel, CompetitionModel, CompetitionStandingsMatchModel, CompetitionStandingsModel, CompetitionTeamsModel, CountryModel, DivisionModel, DivisionSeasonModel, IterationModel, LeagueModel, MatchModel, MatchRatingModel, MatchResultModel, MatchSetModel, PerformanceStatsModel, PlayerModel, PlayerImprovementLogModel, PlayerDeclineLogModel, PlayerTeamModel, RetiredPlayerModel, PromotionMatchModel, FriendlyModel, RegionQualifierModel, NationalCountryModel, RallyModel, TacticsModel, LineupPresetModel, MatchPresetModel, EventModel, EventCompetitionModel, EventSquadModel, EventPackModel, EventPackPlayerModel, EventResultModel, EventBotTeamModel, TeamModel, TeamReplacementModel, UserCurrencyModel, UserPlayerProgressModel, UserPullGrantModel, UserTeamModel, VPERModel, NotificationModel, AppConfigModel, LegacyTeamFlagModel } from './models';
|
|
1
|
+
import { AuthIdentityModel, AuthSessionModel, AuthUserModel, BoxScoreModel, CoachModel, ScoutModel, PhysicianModel, PhysiotherapistModel, BoxScoreTotalsModel, CompetitionChampionModel, CurrencyTransactionModel, GachaPityModel, GachaPullHistoryModel, WishlistModel, UserSettingsModel, UserDeviceTokenModel, BasePositionPresetModel, CompetitionMatchModel, CompetitionMVPModel, CompetitionModel, CompetitionStandingsMatchModel, CompetitionStandingsModel, CompetitionTeamsModel, CountryModel, DivisionModel, DivisionSeasonModel, IterationModel, LeagueModel, MatchModel, MatchRatingModel, MatchResultModel, MatchSetModel, PerformanceStatsModel, PlayerModel, PlayerImprovementLogModel, PlayerDeclineLogModel, PlayerTeamModel, RetiredPlayerModel, PromotionMatchModel, FriendlyModel, RegionQualifierModel, NationalCountryModel, RallyModel, TacticsModel, LineupPresetModel, MatchPresetModel, EventModel, EventCompetitionModel, EventSquadModel, EventPackModel, EventPackPlayerModel, EventResultModel, EventBotTeamModel, TeamModel, TeamReplacementModel, UserCurrencyModel, UserPlayerProgressModel, UserPullGrantModel, UserTeamModel, VPERModel, NotificationModel, AppConfigModel, LegacyTeamFlagModel } from './models';
|
|
2
2
|
export function initModels(sequelize) {
|
|
3
3
|
const Coach = CoachModel.initModel(sequelize);
|
|
4
4
|
const Scout = ScoutModel.initModel(sequelize);
|
|
@@ -24,6 +24,7 @@ export function initModels(sequelize) {
|
|
|
24
24
|
const Wishlist = WishlistModel.initModel(sequelize);
|
|
25
25
|
const UserSettings = UserSettingsModel.initModel(sequelize);
|
|
26
26
|
const UserDeviceToken = UserDeviceTokenModel.initModel(sequelize);
|
|
27
|
+
const BasePositionPreset = BasePositionPresetModel.initModel(sequelize);
|
|
27
28
|
const Notification = NotificationModel.initModel(sequelize);
|
|
28
29
|
const AuthIdentity = AuthIdentityModel.initModel(sequelize);
|
|
29
30
|
const AuthSession = AuthSessionModel.initModel(sequelize);
|
|
@@ -72,8 +73,10 @@ export function initModels(sequelize) {
|
|
|
72
73
|
AuthUser.hasOne(Wishlist, { as: 'Wishlist', foreignKey: 'user_id' });
|
|
73
74
|
AuthUser.hasOne(UserSettings, { as: 'UserSettings', foreignKey: 'user_id' });
|
|
74
75
|
AuthUser.hasMany(UserDeviceToken, { as: 'UserDeviceTokens', foreignKey: 'user_id' });
|
|
76
|
+
AuthUser.hasMany(BasePositionPreset, { as: 'BasePositionPresets', foreignKey: 'user_id' });
|
|
75
77
|
AuthUser.hasMany(Notification, { as: 'Notifications', foreignKey: 'user_id' });
|
|
76
78
|
UserDeviceToken.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
79
|
+
BasePositionPreset.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
77
80
|
Notification.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
78
81
|
AuthUser.hasMany(GachaPullHistory, { as: 'GachaPullHistory', foreignKey: 'user_id' });
|
|
79
82
|
AuthUser.hasMany(UserPullGrant, { as: 'UserPullGrants', foreignKey: 'user_id' });
|
|
@@ -351,6 +354,7 @@ export function initModels(sequelize) {
|
|
|
351
354
|
Wishlist,
|
|
352
355
|
UserSettings,
|
|
353
356
|
UserDeviceToken,
|
|
357
|
+
BasePositionPreset,
|
|
354
358
|
Notification,
|
|
355
359
|
Coach,
|
|
356
360
|
Scout,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as Sequelize from 'sequelize';
|
|
2
|
+
import { Model, Optional } from 'sequelize';
|
|
3
|
+
import { BaseConfig } from '../../service';
|
|
4
|
+
export interface BasePositionPresetAttributes {
|
|
5
|
+
preset_id: string;
|
|
6
|
+
user_id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
config: BaseConfig;
|
|
9
|
+
created_at?: Date;
|
|
10
|
+
}
|
|
11
|
+
export type BasePositionPresetPk = 'preset_id';
|
|
12
|
+
export type BasePositionPresetId = BasePositionPresetModel[BasePositionPresetPk];
|
|
13
|
+
export type BasePositionPresetOptionalAttributes = 'preset_id' | 'created_at';
|
|
14
|
+
export type BasePositionPresetCreationAttributes = Optional<BasePositionPresetAttributes, BasePositionPresetOptionalAttributes>;
|
|
15
|
+
export declare class BasePositionPresetModel extends Model<BasePositionPresetAttributes, BasePositionPresetCreationAttributes> implements BasePositionPresetAttributes {
|
|
16
|
+
preset_id: string;
|
|
17
|
+
user_id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
config: BaseConfig;
|
|
20
|
+
created_at?: Date;
|
|
21
|
+
static initModel(sequelize: Sequelize.Sequelize): typeof BasePositionPresetModel;
|
|
22
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DataTypes, Model } from 'sequelize';
|
|
2
|
+
export class BasePositionPresetModel extends Model {
|
|
3
|
+
static initModel(sequelize) {
|
|
4
|
+
return BasePositionPresetModel.init({
|
|
5
|
+
preset_id: {
|
|
6
|
+
type: DataTypes.UUID,
|
|
7
|
+
allowNull: false,
|
|
8
|
+
primaryKey: true,
|
|
9
|
+
defaultValue: DataTypes.UUIDV4
|
|
10
|
+
},
|
|
11
|
+
user_id: {
|
|
12
|
+
type: DataTypes.UUID,
|
|
13
|
+
allowNull: false,
|
|
14
|
+
references: {
|
|
15
|
+
model: 'AuthUser',
|
|
16
|
+
key: 'user_id'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
name: {
|
|
20
|
+
type: DataTypes.STRING,
|
|
21
|
+
allowNull: false
|
|
22
|
+
},
|
|
23
|
+
config: {
|
|
24
|
+
type: DataTypes.JSONB,
|
|
25
|
+
allowNull: false
|
|
26
|
+
},
|
|
27
|
+
created_at: {
|
|
28
|
+
type: DataTypes.DATE,
|
|
29
|
+
allowNull: false,
|
|
30
|
+
defaultValue: DataTypes.NOW
|
|
31
|
+
}
|
|
32
|
+
}, {
|
|
33
|
+
sequelize,
|
|
34
|
+
tableName: 'BasePositionPreset',
|
|
35
|
+
schema: 'public',
|
|
36
|
+
timestamps: false,
|
|
37
|
+
indexes: [
|
|
38
|
+
{
|
|
39
|
+
name: 'BasePositionPreset_pk',
|
|
40
|
+
unique: true,
|
|
41
|
+
fields: [{ name: 'preset_id' }]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'BasePositionPreset_user_id_idx',
|
|
45
|
+
fields: [{ name: 'user_id' }]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -38,6 +38,7 @@ export * from './team';
|
|
|
38
38
|
export * from './team-replacement';
|
|
39
39
|
export * from './tactics';
|
|
40
40
|
export * from './lineup-preset';
|
|
41
|
+
export * from './base-position-preset';
|
|
41
42
|
export * from './event';
|
|
42
43
|
export * from './event-competition';
|
|
43
44
|
export * from './event-bot-team';
|
|
@@ -38,6 +38,7 @@ export * from './team';
|
|
|
38
38
|
export * from './team-replacement';
|
|
39
39
|
export * from './tactics';
|
|
40
40
|
export * from './lineup-preset';
|
|
41
|
+
export * from './base-position-preset';
|
|
41
42
|
export * from './event';
|
|
42
43
|
export * from './event-competition';
|
|
43
44
|
export * from './event-bot-team';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Sequelize from 'sequelize';
|
|
2
2
|
import { Model, Optional } from 'sequelize';
|
|
3
3
|
import { PlayerId, TeamId, TeamModel } from '.';
|
|
4
|
-
import { BlockingAssignment, ConditionLogic, CourtPosition, EnergyBand, PinchCondition, RotationSystemEnum, SubBackMode, SubBand, SubMode } from '../../service';
|
|
4
|
+
import { BaseConfig, BlockingAssignment, ConditionLogic, CourtPosition, DefenceFormation, EnergyBand, PinchCondition, RotationSystemEnum, SubBackMode, SubBand, SubMode } from '../../service';
|
|
5
5
|
export interface TacticsLineupAttributes {
|
|
6
6
|
[CourtPosition.LIBERO_ZONE]?: PlayerId;
|
|
7
7
|
[CourtPosition.LEFT_BACK]: PlayerId;
|
|
@@ -36,7 +36,7 @@ export interface OffensivePreferenceAttributes {
|
|
|
36
36
|
}
|
|
37
37
|
export interface BlockingPreferenceAttributes {
|
|
38
38
|
rotation: number;
|
|
39
|
-
targets: Record<PlayerId, BlockingAssignment
|
|
39
|
+
targets: Record<DefenceFormation, Record<PlayerId, BlockingAssignment>>;
|
|
40
40
|
}
|
|
41
41
|
export interface SystemSetAttributes {
|
|
42
42
|
rotationSystem: RotationSystemEnum;
|
|
@@ -82,10 +82,11 @@ export interface TacticsAttributes {
|
|
|
82
82
|
libero_injury?: LiberoInjuryAttributes;
|
|
83
83
|
blocking_preferences: BlockingPreferenceAttributes[];
|
|
84
84
|
blocking_preference_sets?: BlockingPreferenceAttributes[][];
|
|
85
|
+
base_positions?: BaseConfig;
|
|
85
86
|
}
|
|
86
87
|
export type TacticsPk = 'team_id';
|
|
87
88
|
export type TacticsId = TacticsModel[TacticsPk];
|
|
88
|
-
export type TacticsOptionalAttributes = 'lineup' | 'libero_replacements' | 'receive_rotation_offset' | 'designated_subs' | 'substitution_band' | 'second_libero' | 'libero_sub' | 'libero_replacement_sets' | 'rotation_system' | 'designated_setters' | 'offensive_preferences' | 'system_sets' | 'offensive_preference_sets' | 'serve_attack_shares' | 'replace_knocked_immediately' | 'injury_replacements' | 'libero_injury' | 'blocking_preferences' | 'blocking_preference_sets';
|
|
89
|
+
export type TacticsOptionalAttributes = 'lineup' | 'libero_replacements' | 'receive_rotation_offset' | 'designated_subs' | 'substitution_band' | 'second_libero' | 'libero_sub' | 'libero_replacement_sets' | 'rotation_system' | 'designated_setters' | 'offensive_preferences' | 'system_sets' | 'offensive_preference_sets' | 'serve_attack_shares' | 'replace_knocked_immediately' | 'injury_replacements' | 'libero_injury' | 'blocking_preferences' | 'blocking_preference_sets' | 'base_positions';
|
|
89
90
|
export type TacticsCreationAttributes = Optional<TacticsAttributes, TacticsOptionalAttributes>;
|
|
90
91
|
export declare class TacticsModel extends Model<TacticsAttributes, TacticsCreationAttributes> implements TacticsAttributes {
|
|
91
92
|
team_id: string;
|
|
@@ -108,6 +109,7 @@ export declare class TacticsModel extends Model<TacticsAttributes, TacticsCreati
|
|
|
108
109
|
libero_injury?: LiberoInjuryAttributes;
|
|
109
110
|
blocking_preferences: BlockingPreferenceAttributes[];
|
|
110
111
|
blocking_preference_sets?: BlockingPreferenceAttributes[][];
|
|
112
|
+
base_positions?: BaseConfig;
|
|
111
113
|
team: TeamModel;
|
|
112
114
|
getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
|
|
113
115
|
setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
|
|
@@ -39,7 +39,7 @@ describe('rally-event compact round trip — incident marker on the causing even
|
|
|
39
39
|
score: 62.5,
|
|
40
40
|
target: 4,
|
|
41
41
|
failure: SpikeFailureEnum.NO_FAILURE,
|
|
42
|
-
type: SpikeTypeEnum.
|
|
42
|
+
type: SpikeTypeEnum.SPIKE,
|
|
43
43
|
...(incident != null ? { incident } : {})
|
|
44
44
|
});
|
|
45
45
|
}
|
|
@@ -12,7 +12,7 @@ describe('rally-event compact round trip -- positional fields (coords / speed /
|
|
|
12
12
|
score: 60,
|
|
13
13
|
target: 4,
|
|
14
14
|
failure: SpikeFailureEnum.NO_FAILURE,
|
|
15
|
-
type: SpikeTypeEnum.
|
|
15
|
+
type: SpikeTypeEnum.SPIKE,
|
|
16
16
|
contact: { x: 1.234, y: -0.5 },
|
|
17
17
|
landing: { x: -2.5, y: 6.789 },
|
|
18
18
|
speed: 92.456
|
|
@@ -41,7 +41,7 @@ describe('rally-event compact round trip -- positional fields (coords / speed /
|
|
|
41
41
|
expect(decoded.digQuality).toBe(DigQualityEnum.MEDIOCRE);
|
|
42
42
|
});
|
|
43
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.
|
|
44
|
+
const spike = Spike.create({ playerId, score: 60, target: 4, failure: SpikeFailureEnum.NO_FAILURE, type: SpikeTypeEnum.SPIKE });
|
|
45
45
|
const compact = transformToCompact(spike, playerIndex);
|
|
46
46
|
expect('c' in compact).toBe(false);
|
|
47
47
|
expect('v' in compact).toBe(false);
|
|
@@ -51,4 +51,12 @@ describe('rally-event compact round trip -- positional fields (coords / speed /
|
|
|
51
51
|
expect(decoded.speed).toBeUndefined();
|
|
52
52
|
expect(decoded.digQuality).toBeUndefined();
|
|
53
53
|
});
|
|
54
|
+
it('collapses legacy LINE_SHOT(1)/REVERSE_CROSS(2) to SPIKE(0) on decode (owner 2026-08-03)', () => {
|
|
55
|
+
// A zone-engine match persisted before the collapse carries type 1 or 2 in its jsonb; decode heals them to
|
|
56
|
+
// SPIKE so the match still opens. TIP(3) and up are untouched.
|
|
57
|
+
const base = transformToCompact(Spike.create({ playerId, score: 60, target: 4, failure: SpikeFailureEnum.NO_FAILURE, type: SpikeTypeEnum.SPIKE }), playerIndex);
|
|
58
|
+
expect(transformToSpike({ ...base, t: 1 }, roster).type).toBe(SpikeTypeEnum.SPIKE);
|
|
59
|
+
expect(transformToSpike({ ...base, t: 2 }, roster).type).toBe(SpikeTypeEnum.SPIKE);
|
|
60
|
+
expect(transformToSpike({ ...base, t: 3 }, roster).type).toBe(SpikeTypeEnum.TIP);
|
|
61
|
+
});
|
|
54
62
|
});
|
|
@@ -127,7 +127,9 @@ export function transformToSet(event, roster) {
|
|
|
127
127
|
export function transformToSpike(event, roster) {
|
|
128
128
|
return Spike.create({
|
|
129
129
|
failure: event.f,
|
|
130
|
-
|
|
130
|
+
// Legacy LINE_SHOT(1)/REVERSE_CROSS(2) collapse to SPIKE(0) (owner 2026-08-03): direction is redundant with
|
|
131
|
+
// the stored contact+landing coords. Maps old zone-engine matches so they keep decoding; new events emit 0/3-6.
|
|
132
|
+
type: event.t === 1 || event.t === 2 ? 0 : event.t,
|
|
131
133
|
playerId: roster[event.p],
|
|
132
134
|
target: event.a,
|
|
133
135
|
score: event.s,
|
|
@@ -17,7 +17,7 @@ describe('rally compact round trip — every event type', () => {
|
|
|
17
17
|
Serve.create({ playerId: pA, target: 6, score: 55, failure: ServeFailureEnum.NO_FAILURE, type: ServeTypeEnum.JUMP_TOPSPIN }),
|
|
18
18
|
Reception.create({ playerId: pB, target: 5, score: 50, failure: ReceptionFailureEnum.NO_FAILURE, type: ReceptionTypeEnum.DIG }),
|
|
19
19
|
VolleySet.create({ playerId: pC, target: 3, score: 48, failure: SetFailureEnum.NO_FAILURE, type: SetTypeEnum.OVERHAND, tempo: SetTempoEnum.FIRST }),
|
|
20
|
-
Spike.create({ playerId: pA, target: 4, score: 62.5, failure: SpikeFailureEnum.NO_FAILURE, type: SpikeTypeEnum.
|
|
20
|
+
Spike.create({ playerId: pA, target: 4, score: 62.5, failure: SpikeFailureEnum.NO_FAILURE, type: SpikeTypeEnum.SPIKE }),
|
|
21
21
|
Block.create({ playerId: pB, blockers: [pB, pC], target: 8, score: 48, failure: BlockFailureEnum.NO_FAILURE, type: BlockTypeEnum.DOUBLE }),
|
|
22
22
|
FreeBall.create({ playerId: pC, target: 6, score: 45, failure: FreeBallFailureEnum.NO_FAILURE, type: FreeBallTypeEnum.OVER })
|
|
23
23
|
];
|
|
@@ -47,7 +47,7 @@ describe('rally compact round trip — every event type', () => {
|
|
|
47
47
|
expect(rec.type).toBe(ReceptionTypeEnum.DIG);
|
|
48
48
|
expect(set.type).toBe(SetTypeEnum.OVERHAND);
|
|
49
49
|
expect(set.tempo).toBe(SetTempoEnum.FIRST);
|
|
50
|
-
expect(spike.type).toBe(SpikeTypeEnum.
|
|
50
|
+
expect(spike.type).toBe(SpikeTypeEnum.SPIKE);
|
|
51
51
|
expect(spike.score).toBe(62.5);
|
|
52
52
|
expect(block.type).toBe(BlockTypeEnum.DOUBLE);
|
|
53
53
|
expect(block.blockers).toEqual([pB, pC]);
|
|
@@ -80,6 +80,7 @@ function transformToAttributes(tactics, teamId) {
|
|
|
80
80
|
tempos: p.tempos
|
|
81
81
|
}))),
|
|
82
82
|
serve_attack_shares: tactics.serveAttackShares ?? {},
|
|
83
|
+
base_positions: tactics.basePositions,
|
|
83
84
|
blocking_preferences: tactics.blockingPreferences.map(p => ({ rotation: p.rotation, targets: p.targets })),
|
|
84
85
|
blocking_preference_sets: tactics.blockingPreferenceSets?.map(prefs => prefs.map(p => ({ rotation: p.rotation, targets: p.targets }))),
|
|
85
86
|
replace_knocked_immediately: tactics.replaceKnockedImmediately,
|
|
@@ -201,6 +202,7 @@ function transformToObject(model, roster) {
|
|
|
201
202
|
}
|
|
202
203
|
return healed;
|
|
203
204
|
})(),
|
|
205
|
+
basePositions: model.base_positions ?? undefined,
|
|
204
206
|
blockingPreferences: (model.blocking_preferences ?? []).map(p => ({ rotation: p.rotation, targets: p.targets })),
|
|
205
207
|
blockingPreferenceSets: model.blocking_preference_sets?.map(prefs => prefs.map(p => ({ rotation: p.rotation, targets: p.targets }))),
|
|
206
208
|
// LENIENT: keep only boolean entries keyed by a court starter or, when a second libero exists, the starting
|
|
@@ -361,6 +363,7 @@ function tacticsColumnsToApiDto(model) {
|
|
|
361
363
|
}
|
|
362
364
|
return Object.keys(healed).length > 0 ? healed : undefined;
|
|
363
365
|
})(),
|
|
366
|
+
basePositions: model.base_positions ?? undefined,
|
|
364
367
|
blockingPreferences: model.blocking_preferences,
|
|
365
368
|
blockingPreferenceSets: model.blocking_preference_sets,
|
|
366
369
|
// Same stale-data policy as the heals below: only boolean entries keyed by a court starter or, when a second
|
|
@@ -469,6 +472,7 @@ function apiTacticsConfigToAttributes(config, teamId) {
|
|
|
469
472
|
system_sets: config.systemSets,
|
|
470
473
|
offensive_preference_sets: config.offensivePreferenceSets,
|
|
471
474
|
serve_attack_shares: config.serveAttackShares ?? {},
|
|
475
|
+
base_positions: config.basePositions,
|
|
472
476
|
blocking_preferences: config.blockingPreferences ?? [],
|
|
473
477
|
blocking_preference_sets: config.blockingPreferenceSets,
|
|
474
478
|
replace_knocked_immediately: config.replaceKnockedImmediately,
|
|
@@ -225,6 +225,30 @@ describe('per-set tactics — service <-> row round-trip', () => {
|
|
|
225
225
|
const back = transformToTactics(attrs, roster);
|
|
226
226
|
expect(back.serveAttackShares).toEqual({});
|
|
227
227
|
});
|
|
228
|
+
it('round-trips base positions (coords + reception participation)', () => {
|
|
229
|
+
const config = {
|
|
230
|
+
coords: { RECEIVE: { 1: { 5: { d: 5.5, r: -3 }, 6: { d: 6.5, r: 0 } } }, DEFENCE: { 3: { 2: { d: 0.9, r: 3 } } } },
|
|
231
|
+
receive: { 1: { 5: true, 2: false }, 4: { 6: true } }
|
|
232
|
+
};
|
|
233
|
+
const withBases = Tactics.create({
|
|
234
|
+
lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
|
|
235
|
+
liberoReplacements: [],
|
|
236
|
+
basePositions: config
|
|
237
|
+
});
|
|
238
|
+
const attrs = transformFromTactics(withBases, 'team-1');
|
|
239
|
+
expect(attrs.base_positions).toEqual(config);
|
|
240
|
+
const back = transformToTactics(attrs, roster);
|
|
241
|
+
expect(back.basePositions).toEqual(config);
|
|
242
|
+
});
|
|
243
|
+
it('leaves basePositions undefined when the column is absent', () => {
|
|
244
|
+
const attrs = transformFromTactics(Tactics.create({
|
|
245
|
+
lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
|
|
246
|
+
liberoReplacements: []
|
|
247
|
+
}), 'team-1');
|
|
248
|
+
expect(attrs.base_positions).toBeUndefined();
|
|
249
|
+
const back = transformToTactics(attrs, roster);
|
|
250
|
+
expect(back.basePositions).toBeUndefined();
|
|
251
|
+
});
|
|
228
252
|
it('round-trips a per-starter tempos map on offensive preferences', () => {
|
|
229
253
|
const withTempos = Tactics.create({
|
|
230
254
|
lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
|
|
@@ -238,18 +262,18 @@ describe('per-set tactics — service <-> row round-trip', () => {
|
|
|
238
262
|
const back = transformToTactics(attrs, roster);
|
|
239
263
|
expect(back.offensivePreferences[0].tempos).toEqual({ [p2.id]: 'FAST', [p3.id]: 'HIGH' });
|
|
240
264
|
});
|
|
241
|
-
it('round-trips blocking preferences as id-keyed function maps', () => {
|
|
265
|
+
it('round-trips blocking preferences as per-defence-side id-keyed function maps', () => {
|
|
242
266
|
const withBlocking = Tactics.create({
|
|
243
267
|
lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
|
|
244
268
|
liberoReplacements: [],
|
|
245
|
-
blockingPreferences: [{ rotation: 1, targets: { [p4.id]: RoleEnum.OUTSIDE_HITTER, [p3.id]: RoleEnum.MIDDLE_BLOCKER } }]
|
|
269
|
+
blockingPreferences: [{ rotation: 1, targets: { DEF_L: { [p4.id]: RoleEnum.OUTSIDE_HITTER }, DEF_R: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } } }]
|
|
246
270
|
});
|
|
247
271
|
const attrs = transformFromTactics(withBlocking, 'team-1');
|
|
248
272
|
expect(attrs.blocking_preferences).toEqual([
|
|
249
|
-
{ rotation: 1, targets: { [p4.id]: RoleEnum.OUTSIDE_HITTER, [p3.id]: RoleEnum.MIDDLE_BLOCKER } }
|
|
273
|
+
{ rotation: 1, targets: { DEF_L: { [p4.id]: RoleEnum.OUTSIDE_HITTER }, DEF_M: {}, DEF_R: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } } }
|
|
250
274
|
]);
|
|
251
275
|
const back = transformToTactics(attrs, roster);
|
|
252
|
-
expect(back.blockingPreferences[0].targets).toEqual({ [p4.id]: RoleEnum.OUTSIDE_HITTER, [p3.id]: RoleEnum.MIDDLE_BLOCKER });
|
|
276
|
+
expect(back.blockingPreferences[0].targets).toEqual({ DEF_L: { [p4.id]: RoleEnum.OUTSIDE_HITTER }, DEF_M: {}, DEF_R: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } });
|
|
253
277
|
});
|
|
254
278
|
// NO_BLOCK rides in the same targets map as a commit assignment, so the transformers copy it wholesale with no
|
|
255
279
|
// mapping code. This pins that: the sentinel must survive the row round-trip verbatim, mixed with a commit, and
|
|
@@ -258,17 +282,17 @@ describe('per-set tactics — service <-> row round-trip', () => {
|
|
|
258
282
|
const withNoBlock = Tactics.create({
|
|
259
283
|
lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
|
|
260
284
|
liberoReplacements: [],
|
|
261
|
-
blockingPreferences: [{ rotation: 1, targets: { [p4.id]: NO_BLOCK, [p3.id]: RoleEnum.MIDDLE_BLOCKER } }],
|
|
262
|
-
blockingPreferenceSets: [[{ rotation: 2, targets: { [p2.id]: NO_BLOCK } }]]
|
|
285
|
+
blockingPreferences: [{ rotation: 1, targets: { DEF_M: { [p4.id]: NO_BLOCK, [p3.id]: RoleEnum.MIDDLE_BLOCKER } } }],
|
|
286
|
+
blockingPreferenceSets: [[{ rotation: 2, targets: { DEF_L: { [p2.id]: NO_BLOCK } } }]]
|
|
263
287
|
});
|
|
264
288
|
const attrs = transformFromTactics(withNoBlock, 'team-1');
|
|
265
289
|
expect(attrs.blocking_preferences).toEqual([
|
|
266
|
-
{ rotation: 1, targets: { [p4.id]: NO_BLOCK, [p3.id]: RoleEnum.MIDDLE_BLOCKER } }
|
|
290
|
+
{ rotation: 1, targets: { DEF_L: {}, DEF_M: { [p4.id]: NO_BLOCK, [p3.id]: RoleEnum.MIDDLE_BLOCKER }, DEF_R: {} } }
|
|
267
291
|
]);
|
|
268
|
-
expect(attrs.blocking_preference_sets).toEqual([[{ rotation: 2, targets: { [p2.id]: NO_BLOCK } }]]);
|
|
292
|
+
expect(attrs.blocking_preference_sets).toEqual([[{ rotation: 2, targets: { DEF_L: { [p2.id]: NO_BLOCK }, DEF_M: {}, DEF_R: {} } }]]);
|
|
269
293
|
const back = transformToTactics(attrs, roster);
|
|
270
|
-
expect(back.blockingPreferences[0].targets).toEqual({ [p4.id]: NO_BLOCK, [p3.id]: RoleEnum.MIDDLE_BLOCKER });
|
|
271
|
-
expect(back.blockingPreferenceSets?.[0][0].targets).toEqual({ [p2.id]: NO_BLOCK });
|
|
294
|
+
expect(back.blockingPreferences[0].targets).toEqual({ DEF_L: {}, DEF_M: { [p4.id]: NO_BLOCK, [p3.id]: RoleEnum.MIDDLE_BLOCKER }, DEF_R: {} });
|
|
295
|
+
expect(back.blockingPreferenceSets?.[0][0].targets).toEqual({ DEF_L: { [p2.id]: NO_BLOCK }, DEF_M: {}, DEF_R: {} });
|
|
272
296
|
});
|
|
273
297
|
});
|
|
274
298
|
describe('libero injuries — service <-> row round-trip + lenient heal', () => {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type CompetitionRewardKind = 'REGIONAL' | 'NATIONAL' | 'WCC';
|
|
2
|
+
export interface CompetitionRewardRow {
|
|
3
|
+
/** Best final position that still earns this row: the "Top N" band. */
|
|
4
|
+
readonly maxPosition: number;
|
|
5
|
+
/** Coins awarded by this row alone (the increment, not the running total). */
|
|
6
|
+
readonly coins: number;
|
|
7
|
+
/** Recruit-reward pulls awarded by this row alone (the competition's own pull type). */
|
|
8
|
+
readonly recruits: number;
|
|
9
|
+
}
|
|
10
|
+
/** Per-competition bands, widest band first (the order the docs list them, with a growing cumulative total). */
|
|
11
|
+
export declare const COMPETITION_REWARDS: Readonly<Record<CompetitionRewardKind, readonly CompetitionRewardRow[]>>;
|
|
12
|
+
export interface CompetitionPayout {
|
|
13
|
+
readonly coins: number;
|
|
14
|
+
readonly recruits: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Everything a finishing position earns in the given competition: the sum of every band that still includes the
|
|
18
|
+
* position (cumulative). Positions past the lowest band earn nothing. `position` is the 1-based final placement.
|
|
19
|
+
*/
|
|
20
|
+
export declare function competitionPayoutFor(kind: CompetitionRewardKind, position: number): CompetitionPayout;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// What the knockout competitions (Regional Cup, National Cup, WCC) pay out by final placement. Owner-defined
|
|
2
|
+
// 2026-08-07. Like the event and league rewards, these are CUMULATIVE: a finish earns its band and every band
|
|
3
|
+
// below it. Each band gives coins and/or "recruit rewards" (the competition's own pull grant: a Regional
|
|
4
|
+
// recruit is a QUALIFIER pull, a National recruit a NATIONAL pull, a WCC recruit a TOURNAMENT pull). Recruit
|
|
5
|
+
// rewards stack, so a champion collects one per qualifying band.
|
|
6
|
+
//
|
|
7
|
+
// One source of truth for two consumers: the simulator grants from it and the UI competition docs render it.
|
|
8
|
+
/** Per-competition bands, widest band first (the order the docs list them, with a growing cumulative total). */
|
|
9
|
+
export const COMPETITION_REWARDS = {
|
|
10
|
+
REGIONAL: [
|
|
11
|
+
{ maxPosition: 4, coins: 1000, recruits: 0 },
|
|
12
|
+
{ maxPosition: 3, coins: 0, recruits: 1 },
|
|
13
|
+
{ maxPosition: 2, coins: 1000, recruits: 0 },
|
|
14
|
+
{ maxPosition: 1, coins: 500, recruits: 1 }
|
|
15
|
+
],
|
|
16
|
+
NATIONAL: [
|
|
17
|
+
{ maxPosition: 8, coins: 1000, recruits: 0 },
|
|
18
|
+
{ maxPosition: 4, coins: 500, recruits: 1 },
|
|
19
|
+
{ maxPosition: 2, coins: 500, recruits: 0 },
|
|
20
|
+
{ maxPosition: 1, coins: 0, recruits: 1 }
|
|
21
|
+
],
|
|
22
|
+
WCC: [
|
|
23
|
+
{ maxPosition: 4, coins: 1000, recruits: 0 },
|
|
24
|
+
{ maxPosition: 3, coins: 0, recruits: 1 },
|
|
25
|
+
{ maxPosition: 2, coins: 500, recruits: 1 },
|
|
26
|
+
{ maxPosition: 1, coins: 500, recruits: 1 }
|
|
27
|
+
]
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Everything a finishing position earns in the given competition: the sum of every band that still includes the
|
|
31
|
+
* position (cumulative). Positions past the lowest band earn nothing. `position` is the 1-based final placement.
|
|
32
|
+
*/
|
|
33
|
+
export function competitionPayoutFor(kind, position) {
|
|
34
|
+
let coins = 0;
|
|
35
|
+
let recruits = 0;
|
|
36
|
+
for (const row of COMPETITION_REWARDS[kind]) {
|
|
37
|
+
if (position <= row.maxPosition) {
|
|
38
|
+
coins += row.coins;
|
|
39
|
+
recruits += row.recruits;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { coins, recruits };
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { competitionPayoutFor, COMPETITION_REWARDS } from './competition-rewards';
|
|
3
|
+
// The cumulative per-position totals the owner specified (2026-08-07): { coins, recruits } a finish earns,
|
|
4
|
+
// summing its band and every band below. Positions past the lowest band earn nothing.
|
|
5
|
+
describe('competitionPayoutFor', () => {
|
|
6
|
+
const CASES = {
|
|
7
|
+
// position: [coins, recruits]
|
|
8
|
+
REGIONAL: { 1: [2500, 2], 2: [2000, 1], 3: [1000, 1], 4: [1000, 0], 5: [0, 0] },
|
|
9
|
+
NATIONAL: { 1: [2000, 2], 2: [2000, 1], 3: [1500, 1], 4: [1500, 1], 5: [1000, 0], 8: [1000, 0], 9: [0, 0] },
|
|
10
|
+
WCC: { 1: [2000, 3], 2: [1500, 2], 3: [1000, 1], 4: [1000, 0], 5: [0, 0] }
|
|
11
|
+
};
|
|
12
|
+
for (const kind of Object.keys(CASES)) {
|
|
13
|
+
it(`matches the owner totals for ${kind}`, () => {
|
|
14
|
+
for (const [pos, [coins, recruits]] of Object.entries(CASES[kind])) {
|
|
15
|
+
expect(competitionPayoutFor(kind, Number(pos))).toEqual({ coins, recruits });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
it('champion collects one recruit per qualifying band', () => {
|
|
20
|
+
expect(competitionPayoutFor('REGIONAL', 1).recruits).toBe(2);
|
|
21
|
+
expect(competitionPayoutFor('NATIONAL', 1).recruits).toBe(2);
|
|
22
|
+
expect(competitionPayoutFor('WCC', 1).recruits).toBe(3);
|
|
23
|
+
});
|
|
24
|
+
it('champion coin total equals the sum of the band table', () => {
|
|
25
|
+
const sum = (k) => COMPETITION_REWARDS[k].reduce((s, r) => s + r.coins, 0);
|
|
26
|
+
expect(competitionPayoutFor('REGIONAL', 1).coins).toBe(sum('REGIONAL'));
|
|
27
|
+
expect(competitionPayoutFor('NATIONAL', 1).coins).toBe(sum('NATIONAL'));
|
|
28
|
+
expect(competitionPayoutFor('WCC', 1).coins).toBe(sum('WCC'));
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -3,6 +3,8 @@ export * from './season';
|
|
|
3
3
|
export * from './event-schedule';
|
|
4
4
|
export * from './event-draft';
|
|
5
5
|
export * from './event-rewards';
|
|
6
|
+
export * from './league-rewards';
|
|
7
|
+
export * from './competition-rewards';
|
|
6
8
|
export * from './league';
|
|
7
9
|
export * from './division';
|
|
8
10
|
export * from './iteration';
|
|
@@ -3,6 +3,8 @@ export * from './season';
|
|
|
3
3
|
export * from './event-schedule';
|
|
4
4
|
export * from './event-draft';
|
|
5
5
|
export * from './event-rewards';
|
|
6
|
+
export * from './league-rewards';
|
|
7
|
+
export * from './competition-rewards';
|
|
6
8
|
export * from './league';
|
|
7
9
|
export * from './division';
|
|
8
10
|
export * from './iteration';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface LeagueRewardRow {
|
|
2
|
+
/** Best final position that still earns this row: the "Top N" band. */
|
|
3
|
+
readonly maxPosition: number;
|
|
4
|
+
/** Coins awarded by this row alone (the increment, not the running total). */
|
|
5
|
+
readonly coins: number;
|
|
6
|
+
}
|
|
7
|
+
/** Division tiers that get their own reward table. Tier 3 covers "division 3 and lower". */
|
|
8
|
+
export type LeagueRewardTier = 1 | 2 | 3;
|
|
9
|
+
/**
|
|
10
|
+
* Per-tier bands, widest band first (the order the standings doc lists them, with a growing cumulative total).
|
|
11
|
+
* A team earns every row whose band still includes its finishing position, which is what "cumulative" means.
|
|
12
|
+
*/
|
|
13
|
+
export declare const LEAGUE_REWARDS: Readonly<Record<LeagueRewardTier, readonly LeagueRewardRow[]>>;
|
|
14
|
+
/** Clamp any division tier to a reward table: tier 1, tier 2, and tier 3-and-lower share the tier-3 table. */
|
|
15
|
+
export declare function leagueRewardTier(tier: number): LeagueRewardTier;
|
|
16
|
+
/**
|
|
17
|
+
* Coins a finishing position earns in a division of the given tier: the sum of every band that still includes
|
|
18
|
+
* the position (cumulative). Positions past the lowest band earn 0. `position` is the 1-based final standings
|
|
19
|
+
* place.
|
|
20
|
+
*/
|
|
21
|
+
export declare function leaguePayoutFor(tier: number, position: number): number;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// What a league season pays out at the end, by division tier and final standings position. Owner-defined
|
|
2
|
+
// 2026-08-07. Like the event rewards, these are CUMULATIVE: a team collects its own band and every band below
|
|
3
|
+
// it, so first place collects them all. Positions past the lowest band earn nothing.
|
|
4
|
+
//
|
|
5
|
+
// One source of truth for two consumers: the simulator grants from it (assignSeasonPayoutIfMissing) and the UI
|
|
6
|
+
// standings doc renders it.
|
|
7
|
+
/**
|
|
8
|
+
* Per-tier bands, widest band first (the order the standings doc lists them, with a growing cumulative total).
|
|
9
|
+
* A team earns every row whose band still includes its finishing position, which is what "cumulative" means.
|
|
10
|
+
*/
|
|
11
|
+
export const LEAGUE_REWARDS = {
|
|
12
|
+
1: [
|
|
13
|
+
{ maxPosition: 12, coins: 1000 },
|
|
14
|
+
{ maxPosition: 8, coins: 500 },
|
|
15
|
+
{ maxPosition: 4, coins: 500 },
|
|
16
|
+
{ maxPosition: 3, coins: 250 },
|
|
17
|
+
{ maxPosition: 2, coins: 250 },
|
|
18
|
+
{ maxPosition: 1, coins: 500 }
|
|
19
|
+
],
|
|
20
|
+
2: [
|
|
21
|
+
{ maxPosition: 8, coins: 500 },
|
|
22
|
+
{ maxPosition: 4, coins: 500 },
|
|
23
|
+
{ maxPosition: 2, coins: 250 },
|
|
24
|
+
{ maxPosition: 1, coins: 250 }
|
|
25
|
+
],
|
|
26
|
+
3: [
|
|
27
|
+
{ maxPosition: 4, coins: 500 },
|
|
28
|
+
{ maxPosition: 2, coins: 250 },
|
|
29
|
+
{ maxPosition: 1, coins: 250 }
|
|
30
|
+
]
|
|
31
|
+
};
|
|
32
|
+
/** Clamp any division tier to a reward table: tier 1, tier 2, and tier 3-and-lower share the tier-3 table. */
|
|
33
|
+
export function leagueRewardTier(tier) {
|
|
34
|
+
const t = Math.trunc(tier);
|
|
35
|
+
if (t <= 1)
|
|
36
|
+
return 1;
|
|
37
|
+
if (t === 2)
|
|
38
|
+
return 2;
|
|
39
|
+
return 3;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Coins a finishing position earns in a division of the given tier: the sum of every band that still includes
|
|
43
|
+
* the position (cumulative). Positions past the lowest band earn 0. `position` is the 1-based final standings
|
|
44
|
+
* place.
|
|
45
|
+
*/
|
|
46
|
+
export function leaguePayoutFor(tier, position) {
|
|
47
|
+
let coins = 0;
|
|
48
|
+
for (const row of LEAGUE_REWARDS[leagueRewardTier(tier)]) {
|
|
49
|
+
if (position <= row.maxPosition)
|
|
50
|
+
coins += row.coins;
|
|
51
|
+
}
|
|
52
|
+
return coins;
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|