volleyballsimtypes 0.0.358 → 0.0.360

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cjs/src/data/models/player.d.ts +5 -1
  2. package/dist/cjs/src/data/models/player.js +10 -0
  3. package/dist/cjs/src/data/transformers/player.js +5 -1
  4. package/dist/cjs/src/service/player/decline.d.ts +43 -0
  5. package/dist/cjs/src/service/player/decline.js +55 -0
  6. package/dist/cjs/src/service/player/index.d.ts +1 -0
  7. package/dist/cjs/src/service/player/index.js +1 -0
  8. package/dist/cjs/src/service/player/player-generator.js +12 -9
  9. package/dist/cjs/src/service/player/player.d.ts +3 -0
  10. package/dist/cjs/src/service/player/player.js +3 -1
  11. package/dist/cjs/src/service/player/schemas/player.z.d.ts +8 -0
  12. package/dist/cjs/src/service/player/schemas/player.z.js +3 -0
  13. package/dist/cjs/src/service/test-helpers.js +4 -1
  14. package/dist/cjs/src/stat-config/decline-profiles.json +6 -0
  15. package/dist/cjs/src/stat-config/index.d.ts +2 -1
  16. package/dist/cjs/src/stat-config/index.js +3 -1
  17. package/dist/esm/src/data/models/player.d.ts +5 -1
  18. package/dist/esm/src/data/models/player.js +11 -1
  19. package/dist/esm/src/data/transformers/player.js +5 -1
  20. package/dist/esm/src/service/player/decline.d.ts +43 -0
  21. package/dist/esm/src/service/player/decline.js +50 -0
  22. package/dist/esm/src/service/player/index.d.ts +1 -0
  23. package/dist/esm/src/service/player/index.js +1 -0
  24. package/dist/esm/src/service/player/player-generator.js +12 -9
  25. package/dist/esm/src/service/player/player.d.ts +3 -0
  26. package/dist/esm/src/service/player/player.js +3 -1
  27. package/dist/esm/src/service/player/schemas/player.z.d.ts +8 -0
  28. package/dist/esm/src/service/player/schemas/player.z.js +3 -0
  29. package/dist/esm/src/service/test-helpers.js +4 -1
  30. package/dist/esm/src/stat-config/decline-profiles.json +6 -0
  31. package/dist/esm/src/stat-config/index.d.ts +2 -1
  32. package/dist/esm/src/stat-config/index.js +2 -1
  33. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model } from 'sequelize';
3
3
  import { BoxScoreAttributes, BoxScoreId, BoxScoreModel, BoxScoreTotalsId, BoxScoreTotalsModel, CompetitionMVPId, CompetitionMVPModel, CountryId, CountryModel, MatchSetId, MatchSetModel, PerformanceStatsAttributes, PerformanceStatsId, PerformanceStatsModel, PlayerTeamId, PlayerTeamModel, TeamId, TeamModel, VPERId, VPERModel } from '.';
4
- import { Rarity, Role, Trait } from '../../service';
4
+ import { DeclineProfile, Rarity, Role, Trait } from '../../service';
5
5
  export interface PlayerAttributes {
6
6
  player_id: string;
7
7
  roles: Role[];
@@ -10,6 +10,8 @@ export interface PlayerAttributes {
10
10
  first_name: string;
11
11
  last_name: string;
12
12
  country_id: string;
13
+ age: number;
14
+ decline_profile: DeclineProfile;
13
15
  PerformanceStat?: PerformanceStatsAttributes;
14
16
  BoxScores?: BoxScoreAttributes[];
15
17
  }
@@ -24,6 +26,8 @@ export declare class PlayerModel extends Model<PlayerAttributes, PlayerCreationA
24
26
  first_name: string;
25
27
  last_name: string;
26
28
  country_id: string;
29
+ age: number;
30
+ decline_profile: DeclineProfile;
27
31
  country: CountryModel;
28
32
  getCountry: Sequelize.BelongsToGetAssociationMixin<CountryModel>;
29
33
  setCountry: Sequelize.BelongsToSetAssociationMixin<CountryModel, CountryId>;
@@ -39,6 +39,16 @@ class PlayerModel extends sequelize_1.Model {
39
39
  model: 'Country',
40
40
  key: 'country_id'
41
41
  }
42
+ },
43
+ age: {
44
+ type: sequelize_1.DataTypes.INTEGER,
45
+ allowNull: false,
46
+ defaultValue: 17
47
+ },
48
+ decline_profile: {
49
+ type: sequelize_1.DataTypes.ENUM(...Object.values(service_1.DeclineProfileEnum)),
50
+ allowNull: false,
51
+ defaultValue: service_1.DeclineProfileEnum.STANDARD
42
52
  }
43
53
  }, {
44
54
  sequelize,
@@ -20,7 +20,9 @@ function transformToAttributes(player) {
20
20
  roles: player.roles,
21
21
  traits: player.traits,
22
22
  PerformanceStat: (0, _1.transformFromPerformanceStats)(player.stats, player.id),
23
- rarity: player.rarity
23
+ rarity: player.rarity,
24
+ age: player.age,
25
+ decline_profile: player.declineProfile
24
26
  };
25
27
  }
26
28
  function transformToBoxScoreTotals(model) {
@@ -79,6 +81,8 @@ function transformToObject(model) {
79
81
  traits: model.traits,
80
82
  stats: (0, _1.transformToPerformanceStats)(model.PerformanceStat),
81
83
  rarity: model.rarity,
84
+ age: model.age,
85
+ declineProfile: model.decline_profile,
82
86
  boxScores: (model.BoxScores ?? []).map(_1.transformToBoxScore),
83
87
  boxScoreTotals
84
88
  });
@@ -0,0 +1,43 @@
1
+ import { PerformanceStatsKey } from './performance-stats-keys';
2
+ export declare enum DeclineProfileEnum {
3
+ PRODIGY = "PRODIGY",
4
+ STANDARD = "STANDARD",
5
+ VETERAN = "VETERAN",
6
+ IRONMAN = "IRONMAN"
7
+ }
8
+ export type DeclineProfile = DeclineProfileEnum.PRODIGY | DeclineProfileEnum.STANDARD | DeclineProfileEnum.VETERAN | DeclineProfileEnum.IRONMAN;
9
+ export declare const declineProfiles: DeclineProfileEnum[];
10
+ interface DeclineProfileEntry {
11
+ profile: string;
12
+ startAge: number;
13
+ initTicks: number;
14
+ accel: number;
15
+ peakTicks: number;
16
+ }
17
+ /**
18
+ * Stats that decline first (technique / skill). One random stat from this pool is
19
+ * removed per tick during the pre-peak decline phase.
20
+ */
21
+ export declare const SKILL_POOL: PerformanceStatsKey[];
22
+ /**
23
+ * Energy / reaction stats (incl. crit-chance stats). These only start declining
24
+ * once the player reaches peak decline, alongside the skill pool.
25
+ */
26
+ export declare const ENERGY_POOL: PerformanceStatsKey[];
27
+ export declare function getDeclineProfile(profile: DeclineProfile): DeclineProfileEntry;
28
+ export type DeclineStatus = 'ACTIVE' | 'DECLINING' | 'PEAK';
29
+ export interface DeclineTick {
30
+ /** Number of decline ticks to apply this iteration. */
31
+ ticks: number;
32
+ /** True once tick rate hits the profile ceiling — both pools decline and training is blocked. */
33
+ atPeak: boolean;
34
+ /** Coarse status for display / gating. */
35
+ status: DeclineStatus;
36
+ }
37
+ /**
38
+ * Decline is fully derivable from age + profile — no running state is persisted.
39
+ * tickRate = min(peakTicks, initTicks + (age - startAge) * accel)
40
+ * Pre-peak: `ticks` random SKILL stats lost. At peak: `ticks` from BOTH pools + training blocked.
41
+ */
42
+ export declare function computeDeclineTick(age: number, profile: DeclineProfile): DeclineTick;
43
+ export {};
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ENERGY_POOL = exports.SKILL_POOL = exports.declineProfiles = exports.DeclineProfileEnum = void 0;
4
+ exports.getDeclineProfile = getDeclineProfile;
5
+ exports.computeDeclineTick = computeDeclineTick;
6
+ const stat_config_1 = require("../../stat-config");
7
+ var DeclineProfileEnum;
8
+ (function (DeclineProfileEnum) {
9
+ DeclineProfileEnum["PRODIGY"] = "PRODIGY";
10
+ DeclineProfileEnum["STANDARD"] = "STANDARD";
11
+ DeclineProfileEnum["VETERAN"] = "VETERAN";
12
+ DeclineProfileEnum["IRONMAN"] = "IRONMAN";
13
+ })(DeclineProfileEnum || (exports.DeclineProfileEnum = DeclineProfileEnum = {}));
14
+ exports.declineProfiles = Object.values(DeclineProfileEnum);
15
+ const profileEntries = stat_config_1.declineProfilesJSON;
16
+ /**
17
+ * Stats that decline first (technique / skill). One random stat from this pool is
18
+ * removed per tick during the pre-peak decline phase.
19
+ */
20
+ exports.SKILL_POOL = [
21
+ 'setting', 'serve', 'spike', 'quick', 'power', 'backAttack',
22
+ 'reception', 'overhand', 'bump', 'block', 'attack', 'defense'
23
+ ];
24
+ /**
25
+ * Energy / reaction stats (incl. crit-chance stats). These only start declining
26
+ * once the player reaches peak decline, alongside the skill pool.
27
+ */
28
+ exports.ENERGY_POOL = [
29
+ 'stamina', 'reflex', 'read', 'commit', 'awareness', 'focus'
30
+ ];
31
+ function getDeclineProfile(profile) {
32
+ const entry = profileEntries.find(e => e.profile === profile);
33
+ if (entry == null)
34
+ throw new Error(`UNKNOWN_DECLINE_PROFILE: ${profile}`);
35
+ return entry;
36
+ }
37
+ /**
38
+ * Decline is fully derivable from age + profile — no running state is persisted.
39
+ * tickRate = min(peakTicks, initTicks + (age - startAge) * accel)
40
+ * Pre-peak: `ticks` random SKILL stats lost. At peak: `ticks` from BOTH pools + training blocked.
41
+ */
42
+ function computeDeclineTick(age, profile) {
43
+ const { startAge, initTicks, accel, peakTicks } = getDeclineProfile(profile);
44
+ if (age < startAge) {
45
+ return { ticks: 0, atPeak: false, status: 'ACTIVE' };
46
+ }
47
+ const rawRate = initTicks + (age - startAge) * accel;
48
+ const tickRate = Math.min(peakTicks, rawRate);
49
+ const atPeak = rawRate >= peakTicks;
50
+ return {
51
+ ticks: Math.round(tickRate),
52
+ atPeak,
53
+ status: atPeak ? 'PEAK' : 'DECLINING'
54
+ };
55
+ }
@@ -1,5 +1,6 @@
1
1
  export * from './player';
2
2
  export * from './improvement';
3
+ export * from './decline';
3
4
  export * from './player-generator';
4
5
  export * from './performance-stats';
5
6
  export * from './trait';
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./player"), exports);
18
18
  __exportStar(require("./improvement"), exports);
19
+ __exportStar(require("./decline"), exports);
19
20
  __exportStar(require("./player-generator"), exports);
20
21
  __exportStar(require("./performance-stats"), exports);
21
22
  __exportStar(require("./trait"), exports);
@@ -13,6 +13,7 @@ const stat_config_1 = require("../../stat-config");
13
13
  const utils_1 = require("../utils");
14
14
  const player_1 = require("./player");
15
15
  const trait_1 = require("./trait");
16
+ const decline_1 = require("./decline");
16
17
  const LEGENDARY_PITY_STEP = 0.004;
17
18
  const MYTHIC_PITY_STEP = 0.00001;
18
19
  const SPECIAL_PITY_STEP = 0.000005;
@@ -115,12 +116,12 @@ class PlayerGenerator {
115
116
  const BLOCK = (0, stats_1.getMultipliers)(stats_1.StatsEnum.BLOCK);
116
117
  const SERVE = (0, stats_1.getMultipliers)(stats_1.StatsEnum.SERVE);
117
118
  const STAMINA = (0, stats_1.getMultipliers)(stats_1.StatsEnum.STAMINA);
118
- function buildPerformanceFromRole(weights, maxValue) {
119
+ function buildPerformanceFromRole(weights, minValue, maxValue) {
119
120
  return performance_stats_1.PerformanceStats.create(Object.fromEntries(performance_stats_1.performanceStatKeys.map((key) => {
120
121
  if (weights[key] > 0)
121
- return [key, maxValue];
122
+ return [key, (0, node_crypto_1.randomInt)(minValue, maxValue + 1)];
122
123
  else
123
- return [key, Math.round((0, node_crypto_1.randomInt)(rarity_1.RarityRanges.COMMON[0], maxValue - 5))];
124
+ return [key, (0, node_crypto_1.randomInt)(rarity_1.RarityRanges.COMMON[0], Math.max(minValue - 1, rarity_1.RarityRanges.COMMON[0] + 1))];
124
125
  })));
125
126
  }
126
127
  switch (rarity) {
@@ -128,7 +129,7 @@ class PlayerGenerator {
128
129
  if (role != null) {
129
130
  const weights = stat_config_1.rolesJSON.find(entry => entry.role === role)?.weight;
130
131
  if (weights != null) {
131
- return buildPerformanceFromRole(weights, rarity_1.RarityRanges.COMMON[1]);
132
+ return buildPerformanceFromRole(weights, rarity_1.RarityRanges.COMMON[0], rarity_1.RarityRanges.COMMON[1]);
132
133
  }
133
134
  }
134
135
  return performance_stats_1.PerformanceStats.create(Object.fromEntries(performance_stats_1.performanceStatKeys.map((key) => [key, Math.round((0, node_crypto_1.randomInt)(rarity_1.RarityRanges.COMMON[0], rarity_1.RarityRanges.COMMON[1]))])));
@@ -137,7 +138,7 @@ class PlayerGenerator {
137
138
  if (role != null) {
138
139
  const weights = stat_config_1.rolesJSON.find(entry => entry.role === role)?.weight;
139
140
  if (weights != null) {
140
- return buildPerformanceFromRole(weights, rarity_1.RarityRanges.RARE[1]);
141
+ return buildPerformanceFromRole(weights, rarity_1.RarityRanges.RARE[0], rarity_1.RarityRanges.RARE[1]);
141
142
  }
142
143
  }
143
144
  const stats = (0, utils_1.shuffle)([ATTACK, SET, SERVE, RECEIVE, BLOCK]);
@@ -155,7 +156,7 @@ class PlayerGenerator {
155
156
  if (role != null) {
156
157
  const weights = stat_config_1.rolesJSON.find(entry => entry.role === role)?.weight;
157
158
  if (weights != null) {
158
- return buildPerformanceFromRole(weights, rarity_1.RarityRanges.LEGENDARY[1]);
159
+ return buildPerformanceFromRole(weights, rarity_1.RarityRanges.LEGENDARY[0], rarity_1.RarityRanges.LEGENDARY[1]);
159
160
  }
160
161
  }
161
162
  const s1 = (0, utils_1.shuffle)([ATTACK, SET, RECEIVE, BLOCK]);
@@ -174,7 +175,7 @@ class PlayerGenerator {
174
175
  if (role != null) {
175
176
  const weights = stat_config_1.rolesJSON.find(entry => entry.role === role)?.weight;
176
177
  if (weights != null) {
177
- return buildPerformanceFromRole(weights, rarity_1.RarityRanges.MYTHIC[1]);
178
+ return buildPerformanceFromRole(weights, rarity_1.RarityRanges.MYTHIC[0], rarity_1.RarityRanges.MYTHIC[1]);
178
179
  }
179
180
  }
180
181
  const stats = (0, utils_1.shuffle)([ATTACK, SET, RECEIVE, BLOCK]);
@@ -192,7 +193,7 @@ class PlayerGenerator {
192
193
  if (role != null) {
193
194
  const weights = stat_config_1.rolesJSON.find(entry => entry.role === role)?.weight;
194
195
  if (weights != null) {
195
- return buildPerformanceFromRole(weights, rarity_1.RarityRanges.SPECIAL[1]);
196
+ return buildPerformanceFromRole(weights, rarity_1.RarityRanges.SPECIAL[0], rarity_1.RarityRanges.SPECIAL[1]);
196
197
  }
197
198
  }
198
199
  const stats = (0, utils_1.shuffle)([ATTACK, SERVE, SET, RECEIVE, BLOCK]);
@@ -218,7 +219,9 @@ class PlayerGenerator {
218
219
  const stats = PlayerGenerator.generatePerformance(rarity, role);
219
220
  const roles = (0, role_1.assignRoles)(stats, rarity);
220
221
  const traits = (0, trait_1.assignTraits)(roles, rarity, maxTraits);
221
- return player_1.Player.create({ id: (0, uuid_1.v4)(), name, country, stats, roles, traits, rarity });
222
+ const age = (0, node_crypto_1.randomInt)(15, 21);
223
+ const declineProfile = decline_1.declineProfiles[(0, node_crypto_1.randomInt)(0, decline_1.declineProfiles.length)];
224
+ return player_1.Player.create({ id: (0, uuid_1.v4)(), name, country, stats, roles, traits, rarity, age, declineProfile });
222
225
  }
223
226
  static generatePlayers(count, countries) {
224
227
  const players = [];
@@ -5,6 +5,7 @@ import { Rarity } from './rarity';
5
5
  import { Stats } from './stats';
6
6
  import { Role } from './role';
7
7
  import { BoxScore, BoxScoreTotals } from '../match';
8
+ import { DeclineProfile } from './decline';
8
9
  export declare class Stat {
9
10
  readonly name: Stats;
10
11
  readonly value: number;
@@ -30,6 +31,8 @@ export declare class Player {
30
31
  readonly traits: Trait[];
31
32
  readonly generalStats: Stat[];
32
33
  readonly rarity: Rarity;
34
+ readonly age: number;
35
+ readonly declineProfile: DeclineProfile;
33
36
  readonly boxScores?: BoxScore[];
34
37
  readonly boxScoreTotals?: BoxScoreTotals;
35
38
  static create(input: unknown): Player;
@@ -54,7 +54,7 @@ class Player {
54
54
  stats: performance_stats_1.PerformanceStats.create(result.data.stats)
55
55
  });
56
56
  }
57
- constructor({ id, name, country, stats, roles, traits, rarity, boxScores, boxScoreTotals }) {
57
+ constructor({ id, name, country, stats, roles, traits, rarity, age, declineProfile, boxScores, boxScoreTotals }) {
58
58
  this.id = id;
59
59
  this.name = name;
60
60
  this.country = country;
@@ -62,6 +62,8 @@ class Player {
62
62
  this.roles = roles;
63
63
  this.traits = traits;
64
64
  this.rarity = rarity;
65
+ this.age = age;
66
+ this.declineProfile = declineProfile;
65
67
  this.boxScores = boxScores;
66
68
  this.boxScoreTotals = boxScoreTotals;
67
69
  this.generalStats = Object.values(stats_1.StatsEnum).map((stat) => ({
@@ -3,6 +3,7 @@ import { Country } from '../../country';
3
3
  import { RoleEnum } from '../role';
4
4
  import { TraitEnum } from '../trait';
5
5
  import { RarityEnum } from '../rarity';
6
+ import { DeclineProfileEnum } from '../decline';
6
7
  export declare const NameInputSchema: z.ZodObject<{
7
8
  first: z.ZodString;
8
9
  last: z.ZodString;
@@ -59,6 +60,13 @@ export declare const PlayerInputSchema: z.ZodObject<{
59
60
  MYTHIC: RarityEnum.MYTHIC;
60
61
  SPECIAL: RarityEnum.SPECIAL;
61
62
  }>;
63
+ age: z.ZodNumber;
64
+ declineProfile: z.ZodEnum<{
65
+ PRODIGY: DeclineProfileEnum.PRODIGY;
66
+ STANDARD: DeclineProfileEnum.STANDARD;
67
+ VETERAN: DeclineProfileEnum.VETERAN;
68
+ IRONMAN: DeclineProfileEnum.IRONMAN;
69
+ }>;
62
70
  boxScores: z.ZodOptional<z.ZodArray<z.ZodObject<{
63
71
  playerId: z.ZodUUID;
64
72
  matchSetId: z.ZodUUID;
@@ -6,6 +6,7 @@ const country_1 = require("../../country");
6
6
  const role_1 = require("../role");
7
7
  const trait_1 = require("../trait");
8
8
  const rarity_1 = require("../rarity");
9
+ const decline_1 = require("../decline");
9
10
  const box_score_z_1 = require("../../match/schemas/box-score.z");
10
11
  const performance_stats_z_1 = require("./performance-stats.z");
11
12
  exports.NameInputSchema = zod_1.z.object({
@@ -32,6 +33,8 @@ exports.PlayerInputSchema = zod_1.z.object({
32
33
  roles: zod_1.z.array(zod_1.z.enum(Object.values(role_1.RoleEnum))).min(1),
33
34
  traits: zod_1.z.array(zod_1.z.enum(Object.values(trait_1.TraitEnum))),
34
35
  rarity: zod_1.z.enum(Object.values(rarity_1.RarityEnum)),
36
+ age: zod_1.z.number().int().min(0),
37
+ declineProfile: zod_1.z.enum(Object.values(decline_1.DeclineProfileEnum)),
35
38
  boxScores: zod_1.z.array(box_score_z_1.BoxScoreInputSchema).optional(),
36
39
  boxScoreTotals: BoxScoreTotalsInputSchema.optional()
37
40
  });
@@ -21,6 +21,7 @@ const match_set_1 = require("./match/match-set");
21
21
  const common_1 = require("../data/common");
22
22
  const rarity_1 = require("./player/rarity");
23
23
  const role_1 = require("./player/role");
24
+ const decline_1 = require("./player/decline");
24
25
  const performance_stats_keys_1 = require("./player/performance-stats-keys");
25
26
  // ─── Country ─────────────────────────────────────────────────────────────────
26
27
  function makeCountry() {
@@ -43,7 +44,9 @@ function makePlayer(country, statValue = 50) {
43
44
  stats,
44
45
  roles: [role_1.RoleEnum.OUTSIDE_HITTER],
45
46
  traits: [],
46
- rarity: rarity_1.RarityEnum.COMMON
47
+ rarity: rarity_1.RarityEnum.COMMON,
48
+ age: 20,
49
+ declineProfile: decline_1.DeclineProfileEnum.STANDARD
47
50
  });
48
51
  }
49
52
  // ─── Team ─────────────────────────────────────────────────────────────────────
@@ -0,0 +1,6 @@
1
+ [
2
+ { "profile": "PRODIGY", "startAge": 27, "initTicks": 13.0, "accel": 1.00, "peakTicks": 16.0 },
3
+ { "profile": "STANDARD", "startAge": 29, "initTicks": 9.0, "accel": 1.75, "peakTicks": 16.0 },
4
+ { "profile": "VETERAN", "startAge": 32, "initTicks": 10.0, "accel": 1.50, "peakTicks": 16.0 },
5
+ { "profile": "IRONMAN", "startAge": 34, "initTicks": 10.0, "accel": 1.50, "peakTicks": 16.0 }
6
+ ]
@@ -1,4 +1,5 @@
1
1
  import rolesJSON from './roles.json';
2
2
  import traitsJSON from './traits.json';
3
3
  import statCapsJSON from './stat-caps.json';
4
- export { rolesJSON, traitsJSON, statCapsJSON };
4
+ import declineProfilesJSON from './decline-profiles.json';
5
+ export { rolesJSON, traitsJSON, statCapsJSON, declineProfilesJSON };
@@ -3,10 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.statCapsJSON = exports.traitsJSON = exports.rolesJSON = void 0;
6
+ exports.declineProfilesJSON = exports.statCapsJSON = exports.traitsJSON = exports.rolesJSON = void 0;
7
7
  const roles_json_1 = __importDefault(require("./roles.json"));
8
8
  exports.rolesJSON = roles_json_1.default;
9
9
  const traits_json_1 = __importDefault(require("./traits.json"));
10
10
  exports.traitsJSON = traits_json_1.default;
11
11
  const stat_caps_json_1 = __importDefault(require("./stat-caps.json"));
12
12
  exports.statCapsJSON = stat_caps_json_1.default;
13
+ const decline_profiles_json_1 = __importDefault(require("./decline-profiles.json"));
14
+ exports.declineProfilesJSON = decline_profiles_json_1.default;
@@ -1,7 +1,7 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model } from 'sequelize';
3
3
  import { BoxScoreAttributes, BoxScoreId, BoxScoreModel, BoxScoreTotalsId, BoxScoreTotalsModel, CompetitionMVPId, CompetitionMVPModel, CountryId, CountryModel, MatchSetId, MatchSetModel, PerformanceStatsAttributes, PerformanceStatsId, PerformanceStatsModel, PlayerTeamId, PlayerTeamModel, TeamId, TeamModel, VPERId, VPERModel } from '.';
4
- import { Rarity, Role, Trait } from '../../service';
4
+ import { DeclineProfile, Rarity, Role, Trait } from '../../service';
5
5
  export interface PlayerAttributes {
6
6
  player_id: string;
7
7
  roles: Role[];
@@ -10,6 +10,8 @@ export interface PlayerAttributes {
10
10
  first_name: string;
11
11
  last_name: string;
12
12
  country_id: string;
13
+ age: number;
14
+ decline_profile: DeclineProfile;
13
15
  PerformanceStat?: PerformanceStatsAttributes;
14
16
  BoxScores?: BoxScoreAttributes[];
15
17
  }
@@ -24,6 +26,8 @@ export declare class PlayerModel extends Model<PlayerAttributes, PlayerCreationA
24
26
  first_name: string;
25
27
  last_name: string;
26
28
  country_id: string;
29
+ age: number;
30
+ decline_profile: DeclineProfile;
27
31
  country: CountryModel;
28
32
  getCountry: Sequelize.BelongsToGetAssociationMixin<CountryModel>;
29
33
  setCountry: Sequelize.BelongsToSetAssociationMixin<CountryModel, CountryId>;
@@ -1,6 +1,6 @@
1
1
  import { DataTypes, Model } from 'sequelize';
2
2
  import { CountryModel, PerformanceStatsModel } from '.';
3
- import { RarityEnum, RoleEnum, TraitEnum } from '../../service';
3
+ import { DeclineProfileEnum, RarityEnum, RoleEnum, TraitEnum } from '../../service';
4
4
  export class PlayerModel extends Model {
5
5
  static initModel(sequelize) {
6
6
  return PlayerModel.init({
@@ -36,6 +36,16 @@ export class PlayerModel extends Model {
36
36
  model: 'Country',
37
37
  key: 'country_id'
38
38
  }
39
+ },
40
+ age: {
41
+ type: DataTypes.INTEGER,
42
+ allowNull: false,
43
+ defaultValue: 17
44
+ },
45
+ decline_profile: {
46
+ type: DataTypes.ENUM(...Object.values(DeclineProfileEnum)),
47
+ allowNull: false,
48
+ defaultValue: DeclineProfileEnum.STANDARD
39
49
  }
40
50
  }, {
41
51
  sequelize,
@@ -16,7 +16,9 @@ function transformToAttributes(player) {
16
16
  roles: player.roles,
17
17
  traits: player.traits,
18
18
  PerformanceStat: transformFromPerformanceStats(player.stats, player.id),
19
- rarity: player.rarity
19
+ rarity: player.rarity,
20
+ age: player.age,
21
+ decline_profile: player.declineProfile
20
22
  };
21
23
  }
22
24
  function transformToBoxScoreTotals(model) {
@@ -75,6 +77,8 @@ function transformToObject(model) {
75
77
  traits: model.traits,
76
78
  stats: transformToPerformanceStats(model.PerformanceStat),
77
79
  rarity: model.rarity,
80
+ age: model.age,
81
+ declineProfile: model.decline_profile,
78
82
  boxScores: (model.BoxScores ?? []).map(transformToBoxScore),
79
83
  boxScoreTotals
80
84
  });
@@ -0,0 +1,43 @@
1
+ import { PerformanceStatsKey } from './performance-stats-keys';
2
+ export declare enum DeclineProfileEnum {
3
+ PRODIGY = "PRODIGY",
4
+ STANDARD = "STANDARD",
5
+ VETERAN = "VETERAN",
6
+ IRONMAN = "IRONMAN"
7
+ }
8
+ export type DeclineProfile = DeclineProfileEnum.PRODIGY | DeclineProfileEnum.STANDARD | DeclineProfileEnum.VETERAN | DeclineProfileEnum.IRONMAN;
9
+ export declare const declineProfiles: DeclineProfileEnum[];
10
+ interface DeclineProfileEntry {
11
+ profile: string;
12
+ startAge: number;
13
+ initTicks: number;
14
+ accel: number;
15
+ peakTicks: number;
16
+ }
17
+ /**
18
+ * Stats that decline first (technique / skill). One random stat from this pool is
19
+ * removed per tick during the pre-peak decline phase.
20
+ */
21
+ export declare const SKILL_POOL: PerformanceStatsKey[];
22
+ /**
23
+ * Energy / reaction stats (incl. crit-chance stats). These only start declining
24
+ * once the player reaches peak decline, alongside the skill pool.
25
+ */
26
+ export declare const ENERGY_POOL: PerformanceStatsKey[];
27
+ export declare function getDeclineProfile(profile: DeclineProfile): DeclineProfileEntry;
28
+ export type DeclineStatus = 'ACTIVE' | 'DECLINING' | 'PEAK';
29
+ export interface DeclineTick {
30
+ /** Number of decline ticks to apply this iteration. */
31
+ ticks: number;
32
+ /** True once tick rate hits the profile ceiling — both pools decline and training is blocked. */
33
+ atPeak: boolean;
34
+ /** Coarse status for display / gating. */
35
+ status: DeclineStatus;
36
+ }
37
+ /**
38
+ * Decline is fully derivable from age + profile — no running state is persisted.
39
+ * tickRate = min(peakTicks, initTicks + (age - startAge) * accel)
40
+ * Pre-peak: `ticks` random SKILL stats lost. At peak: `ticks` from BOTH pools + training blocked.
41
+ */
42
+ export declare function computeDeclineTick(age: number, profile: DeclineProfile): DeclineTick;
43
+ export {};
@@ -0,0 +1,50 @@
1
+ import { declineProfilesJSON } from '../../stat-config';
2
+ export var DeclineProfileEnum;
3
+ (function (DeclineProfileEnum) {
4
+ DeclineProfileEnum["PRODIGY"] = "PRODIGY";
5
+ DeclineProfileEnum["STANDARD"] = "STANDARD";
6
+ DeclineProfileEnum["VETERAN"] = "VETERAN";
7
+ DeclineProfileEnum["IRONMAN"] = "IRONMAN";
8
+ })(DeclineProfileEnum || (DeclineProfileEnum = {}));
9
+ export const declineProfiles = Object.values(DeclineProfileEnum);
10
+ const profileEntries = declineProfilesJSON;
11
+ /**
12
+ * Stats that decline first (technique / skill). One random stat from this pool is
13
+ * removed per tick during the pre-peak decline phase.
14
+ */
15
+ export const SKILL_POOL = [
16
+ 'setting', 'serve', 'spike', 'quick', 'power', 'backAttack',
17
+ 'reception', 'overhand', 'bump', 'block', 'attack', 'defense'
18
+ ];
19
+ /**
20
+ * Energy / reaction stats (incl. crit-chance stats). These only start declining
21
+ * once the player reaches peak decline, alongside the skill pool.
22
+ */
23
+ export const ENERGY_POOL = [
24
+ 'stamina', 'reflex', 'read', 'commit', 'awareness', 'focus'
25
+ ];
26
+ export function getDeclineProfile(profile) {
27
+ const entry = profileEntries.find(e => e.profile === profile);
28
+ if (entry == null)
29
+ throw new Error(`UNKNOWN_DECLINE_PROFILE: ${profile}`);
30
+ return entry;
31
+ }
32
+ /**
33
+ * Decline is fully derivable from age + profile — no running state is persisted.
34
+ * tickRate = min(peakTicks, initTicks + (age - startAge) * accel)
35
+ * Pre-peak: `ticks` random SKILL stats lost. At peak: `ticks` from BOTH pools + training blocked.
36
+ */
37
+ export function computeDeclineTick(age, profile) {
38
+ const { startAge, initTicks, accel, peakTicks } = getDeclineProfile(profile);
39
+ if (age < startAge) {
40
+ return { ticks: 0, atPeak: false, status: 'ACTIVE' };
41
+ }
42
+ const rawRate = initTicks + (age - startAge) * accel;
43
+ const tickRate = Math.min(peakTicks, rawRate);
44
+ const atPeak = rawRate >= peakTicks;
45
+ return {
46
+ ticks: Math.round(tickRate),
47
+ atPeak,
48
+ status: atPeak ? 'PEAK' : 'DECLINING'
49
+ };
50
+ }
@@ -1,5 +1,6 @@
1
1
  export * from './player';
2
2
  export * from './improvement';
3
+ export * from './decline';
3
4
  export * from './player-generator';
4
5
  export * from './performance-stats';
5
6
  export * from './trait';
@@ -1,5 +1,6 @@
1
1
  export * from './player';
2
2
  export * from './improvement';
3
+ export * from './decline';
3
4
  export * from './player-generator';
4
5
  export * from './performance-stats';
5
6
  export * from './trait';
@@ -8,6 +8,7 @@ import { rolesJSON } from '../../stat-config';
8
8
  import { shuffle, generatePlayerName } from '../utils';
9
9
  import { Player } from './player';
10
10
  import { assignTraits } from './trait';
11
+ import { declineProfiles } from './decline';
11
12
  const LEGENDARY_PITY_STEP = 0.004;
12
13
  const MYTHIC_PITY_STEP = 0.00001;
13
14
  const SPECIAL_PITY_STEP = 0.000005;
@@ -110,12 +111,12 @@ export class PlayerGenerator {
110
111
  const BLOCK = getMultipliers(StatsEnum.BLOCK);
111
112
  const SERVE = getMultipliers(StatsEnum.SERVE);
112
113
  const STAMINA = getMultipliers(StatsEnum.STAMINA);
113
- function buildPerformanceFromRole(weights, maxValue) {
114
+ function buildPerformanceFromRole(weights, minValue, maxValue) {
114
115
  return PerformanceStats.create(Object.fromEntries(performanceStatKeys.map((key) => {
115
116
  if (weights[key] > 0)
116
- return [key, maxValue];
117
+ return [key, randomInt(minValue, maxValue + 1)];
117
118
  else
118
- return [key, Math.round(randomInt(RarityRanges.COMMON[0], maxValue - 5))];
119
+ return [key, randomInt(RarityRanges.COMMON[0], Math.max(minValue - 1, RarityRanges.COMMON[0] + 1))];
119
120
  })));
120
121
  }
121
122
  switch (rarity) {
@@ -123,7 +124,7 @@ export class PlayerGenerator {
123
124
  if (role != null) {
124
125
  const weights = rolesJSON.find(entry => entry.role === role)?.weight;
125
126
  if (weights != null) {
126
- return buildPerformanceFromRole(weights, RarityRanges.COMMON[1]);
127
+ return buildPerformanceFromRole(weights, RarityRanges.COMMON[0], RarityRanges.COMMON[1]);
127
128
  }
128
129
  }
129
130
  return PerformanceStats.create(Object.fromEntries(performanceStatKeys.map((key) => [key, Math.round(randomInt(RarityRanges.COMMON[0], RarityRanges.COMMON[1]))])));
@@ -132,7 +133,7 @@ export class PlayerGenerator {
132
133
  if (role != null) {
133
134
  const weights = rolesJSON.find(entry => entry.role === role)?.weight;
134
135
  if (weights != null) {
135
- return buildPerformanceFromRole(weights, RarityRanges.RARE[1]);
136
+ return buildPerformanceFromRole(weights, RarityRanges.RARE[0], RarityRanges.RARE[1]);
136
137
  }
137
138
  }
138
139
  const stats = shuffle([ATTACK, SET, SERVE, RECEIVE, BLOCK]);
@@ -150,7 +151,7 @@ export class PlayerGenerator {
150
151
  if (role != null) {
151
152
  const weights = rolesJSON.find(entry => entry.role === role)?.weight;
152
153
  if (weights != null) {
153
- return buildPerformanceFromRole(weights, RarityRanges.LEGENDARY[1]);
154
+ return buildPerformanceFromRole(weights, RarityRanges.LEGENDARY[0], RarityRanges.LEGENDARY[1]);
154
155
  }
155
156
  }
156
157
  const s1 = shuffle([ATTACK, SET, RECEIVE, BLOCK]);
@@ -169,7 +170,7 @@ export class PlayerGenerator {
169
170
  if (role != null) {
170
171
  const weights = rolesJSON.find(entry => entry.role === role)?.weight;
171
172
  if (weights != null) {
172
- return buildPerformanceFromRole(weights, RarityRanges.MYTHIC[1]);
173
+ return buildPerformanceFromRole(weights, RarityRanges.MYTHIC[0], RarityRanges.MYTHIC[1]);
173
174
  }
174
175
  }
175
176
  const stats = shuffle([ATTACK, SET, RECEIVE, BLOCK]);
@@ -187,7 +188,7 @@ export class PlayerGenerator {
187
188
  if (role != null) {
188
189
  const weights = rolesJSON.find(entry => entry.role === role)?.weight;
189
190
  if (weights != null) {
190
- return buildPerformanceFromRole(weights, RarityRanges.SPECIAL[1]);
191
+ return buildPerformanceFromRole(weights, RarityRanges.SPECIAL[0], RarityRanges.SPECIAL[1]);
191
192
  }
192
193
  }
193
194
  const stats = shuffle([ATTACK, SERVE, SET, RECEIVE, BLOCK]);
@@ -213,7 +214,9 @@ export class PlayerGenerator {
213
214
  const stats = PlayerGenerator.generatePerformance(rarity, role);
214
215
  const roles = assignRoles(stats, rarity);
215
216
  const traits = assignTraits(roles, rarity, maxTraits);
216
- return Player.create({ id: uuidv4(), name, country, stats, roles, traits, rarity });
217
+ const age = randomInt(15, 21);
218
+ const declineProfile = declineProfiles[randomInt(0, declineProfiles.length)];
219
+ return Player.create({ id: uuidv4(), name, country, stats, roles, traits, rarity, age, declineProfile });
217
220
  }
218
221
  static generatePlayers(count, countries) {
219
222
  const players = [];
@@ -5,6 +5,7 @@ import { Rarity } from './rarity';
5
5
  import { Stats } from './stats';
6
6
  import { Role } from './role';
7
7
  import { BoxScore, BoxScoreTotals } from '../match';
8
+ import { DeclineProfile } from './decline';
8
9
  export declare class Stat {
9
10
  readonly name: Stats;
10
11
  readonly value: number;
@@ -30,6 +31,8 @@ export declare class Player {
30
31
  readonly traits: Trait[];
31
32
  readonly generalStats: Stat[];
32
33
  readonly rarity: Rarity;
34
+ readonly age: number;
35
+ readonly declineProfile: DeclineProfile;
33
36
  readonly boxScores?: BoxScore[];
34
37
  readonly boxScoreTotals?: BoxScoreTotals;
35
38
  static create(input: unknown): Player;
@@ -49,7 +49,7 @@ export class Player {
49
49
  stats: PerformanceStats.create(result.data.stats)
50
50
  });
51
51
  }
52
- constructor({ id, name, country, stats, roles, traits, rarity, boxScores, boxScoreTotals }) {
52
+ constructor({ id, name, country, stats, roles, traits, rarity, age, declineProfile, boxScores, boxScoreTotals }) {
53
53
  this.id = id;
54
54
  this.name = name;
55
55
  this.country = country;
@@ -57,6 +57,8 @@ export class Player {
57
57
  this.roles = roles;
58
58
  this.traits = traits;
59
59
  this.rarity = rarity;
60
+ this.age = age;
61
+ this.declineProfile = declineProfile;
60
62
  this.boxScores = boxScores;
61
63
  this.boxScoreTotals = boxScoreTotals;
62
64
  this.generalStats = Object.values(StatsEnum).map((stat) => ({
@@ -3,6 +3,7 @@ import { Country } from '../../country';
3
3
  import { RoleEnum } from '../role';
4
4
  import { TraitEnum } from '../trait';
5
5
  import { RarityEnum } from '../rarity';
6
+ import { DeclineProfileEnum } from '../decline';
6
7
  export declare const NameInputSchema: z.ZodObject<{
7
8
  first: z.ZodString;
8
9
  last: z.ZodString;
@@ -59,6 +60,13 @@ export declare const PlayerInputSchema: z.ZodObject<{
59
60
  MYTHIC: RarityEnum.MYTHIC;
60
61
  SPECIAL: RarityEnum.SPECIAL;
61
62
  }>;
63
+ age: z.ZodNumber;
64
+ declineProfile: z.ZodEnum<{
65
+ PRODIGY: DeclineProfileEnum.PRODIGY;
66
+ STANDARD: DeclineProfileEnum.STANDARD;
67
+ VETERAN: DeclineProfileEnum.VETERAN;
68
+ IRONMAN: DeclineProfileEnum.IRONMAN;
69
+ }>;
62
70
  boxScores: z.ZodOptional<z.ZodArray<z.ZodObject<{
63
71
  playerId: z.ZodUUID;
64
72
  matchSetId: z.ZodUUID;
@@ -3,6 +3,7 @@ import { Country } from '../../country';
3
3
  import { RoleEnum } from '../role';
4
4
  import { TraitEnum } from '../trait';
5
5
  import { RarityEnum } from '../rarity';
6
+ import { DeclineProfileEnum } from '../decline';
6
7
  import { AttackStatsSchema, BlockStatsSchema, BoxScoreInputSchema, DefenseStatsSchema, MiscStatsSchema, ReceptionStatsSchema, ServeStatsSchema, SettingStatsSchema } from '../../match/schemas/box-score.z';
7
8
  import { PerformanceStatsInputSchema } from './performance-stats.z';
8
9
  export const NameInputSchema = z.object({
@@ -29,6 +30,8 @@ export const PlayerInputSchema = z.object({
29
30
  roles: z.array(z.enum(Object.values(RoleEnum))).min(1),
30
31
  traits: z.array(z.enum(Object.values(TraitEnum))),
31
32
  rarity: z.enum(Object.values(RarityEnum)),
33
+ age: z.number().int().min(0),
34
+ declineProfile: z.enum(Object.values(DeclineProfileEnum)),
32
35
  boxScores: z.array(BoxScoreInputSchema).optional(),
33
36
  boxScoreTotals: BoxScoreTotalsInputSchema.optional()
34
37
  });
@@ -11,6 +11,7 @@ import { MatchSet } from './match/match-set';
11
11
  import { StatusEnum } from '../data/common';
12
12
  import { RarityEnum } from './player/rarity';
13
13
  import { RoleEnum } from './player/role';
14
+ import { DeclineProfileEnum } from './player/decline';
14
15
  import { performanceStatKeys } from './player/performance-stats-keys';
15
16
  // ─── Country ─────────────────────────────────────────────────────────────────
16
17
  export function makeCountry() {
@@ -33,7 +34,9 @@ export function makePlayer(country, statValue = 50) {
33
34
  stats,
34
35
  roles: [RoleEnum.OUTSIDE_HITTER],
35
36
  traits: [],
36
- rarity: RarityEnum.COMMON
37
+ rarity: RarityEnum.COMMON,
38
+ age: 20,
39
+ declineProfile: DeclineProfileEnum.STANDARD
37
40
  });
38
41
  }
39
42
  // ─── Team ─────────────────────────────────────────────────────────────────────
@@ -0,0 +1,6 @@
1
+ [
2
+ { "profile": "PRODIGY", "startAge": 27, "initTicks": 13.0, "accel": 1.00, "peakTicks": 16.0 },
3
+ { "profile": "STANDARD", "startAge": 29, "initTicks": 9.0, "accel": 1.75, "peakTicks": 16.0 },
4
+ { "profile": "VETERAN", "startAge": 32, "initTicks": 10.0, "accel": 1.50, "peakTicks": 16.0 },
5
+ { "profile": "IRONMAN", "startAge": 34, "initTicks": 10.0, "accel": 1.50, "peakTicks": 16.0 }
6
+ ]
@@ -1,4 +1,5 @@
1
1
  import rolesJSON from './roles.json';
2
2
  import traitsJSON from './traits.json';
3
3
  import statCapsJSON from './stat-caps.json';
4
- export { rolesJSON, traitsJSON, statCapsJSON };
4
+ import declineProfilesJSON from './decline-profiles.json';
5
+ export { rolesJSON, traitsJSON, statCapsJSON, declineProfilesJSON };
@@ -1,4 +1,5 @@
1
1
  import rolesJSON from './roles.json';
2
2
  import traitsJSON from './traits.json';
3
3
  import statCapsJSON from './stat-caps.json';
4
- export { rolesJSON, traitsJSON, statCapsJSON };
4
+ import declineProfilesJSON from './decline-profiles.json';
5
+ export { rolesJSON, traitsJSON, statCapsJSON, declineProfilesJSON };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volleyballsimtypes",
3
- "version": "0.0.358",
3
+ "version": "0.0.360",
4
4
  "description": "vbsim types",
5
5
  "main": "./dist/cjs/src/index.js",
6
6
  "module": "./dist/esm/src/index.js",