This commit is contained in:
Dominic Ferrando
2025-11-08 19:53:39 -05:00
parent 8adb1c4566
commit f12453434e
2 changed files with 124 additions and 51 deletions
+70 -31
View File
@@ -220,7 +220,10 @@ export type StandardsConfig = {
enableGeneration: boolean; enableGeneration: boolean;
difficultyModifier: number; difficultyModifier: number;
peakAge: number; peakAge: number;
stretch: number; stretch: {
upper: number;
lower: number;
};
} }
>; >;
}; };
@@ -246,7 +249,10 @@ export class Standards {
difficultyModifier: difficultyModifier:
activity === Activity.BroadJump ? 0.05 : 0, activity === Activity.BroadJump ? 0.05 : 0,
peakAge: 27, peakAge: 27,
stretch: 0, stretch: {
upper: 0,
lower: 0,
},
}, },
]), ]),
) as Record<Activity, StandardsConfig["activity"][Activity]>; ) as Record<Activity, StandardsConfig["activity"][Activity]>;
@@ -264,15 +270,15 @@ export class Standards {
// STRETCH // STRETCH
for (const activity of Object.values(Activity)) { for (const activity of Object.values(Activity)) {
const BASE_MAX_LEVEL = 5; const BASE_MAX_LEVEL = 5;
const newLevelCount = Math.max(
Math.round(this.cfg.activity[activity].stretch),
0,
);
function expDecayModel([A, B, C]: number[]) { function expDecayModel([A, B, C]: number[]) {
return (i: number) => A * Math.exp(-B * i) + C; return (i: number) => A * Math.exp(-B * i) + C;
} }
if (!this.cfg.activity[activity].enableGeneration) {
continue;
}
for (const gender of Object.values(Gender)) { for (const gender of Object.values(Gender)) {
const standardsByGender = this.activityStandards[ const standardsByGender = this.activityStandards[
activity activity
@@ -327,29 +333,43 @@ export class Standards {
// UPDATE THE DATA // UPDATE THE DATA
for (const standard of standards) { for (const standard of standards) {
const newLevels: Levels = {}; const newLevels: Levels = {};
const newLowerLevelCount = Math.max(
Math.round(
this.cfg.activity[activity].stretch.lower,
),
0,
);
const newUpperLevelCount = Math.max(
Math.round(
this.cfg.activity[activity].stretch.upper,
),
0,
);
let prev = standard.levels["1"]; const oldLevels = standard.levels as Levels;
for (const i of range(newLevelCount).reverse()) { let prev = oldLevels["1"];
for (const i of range(newLowerLevelCount).reverse()) {
const level = i + 1; const level = i + 1;
const ratio = getDecayRatio(-i); const ratio = getDecayRatio(-i - 1);
prev = isIncreasing ? prev / ratio : prev * ratio; prev = isIncreasing ? prev / ratio : prev * ratio;
newLevels[level] = Math.round(prev); newLevels[level] = Math.round(prev);
} }
const oldLevels = standard.levels as Levels;
for (const i of range(BASE_MAX_LEVEL)) { for (const i of range(BASE_MAX_LEVEL)) {
const level = i + 1; const level = i + 1;
newLevels[level + newLevelCount] = oldLevels[level]; newLevels[level + newLowerLevelCount] =
oldLevels[level];
} }
prev = standard.levels[BASE_MAX_LEVEL]; prev = oldLevels[BASE_MAX_LEVEL];
for (const i of range(newLevelCount)) { for (const i of range(newUpperLevelCount)) {
const level = const level =
BASE_MAX_LEVEL + newLevelCount + (i + 1); BASE_MAX_LEVEL + newLowerLevelCount + (i + 1);
const ratio = getDecayRatio(level - 1); const ratio = getDecayRatio(level - 1);
prev = isIncreasing ? prev * ratio : prev / ratio; prev = isIncreasing ? prev * ratio : prev / ratio;
newLevels[level] = Math.round(prev); newLevels[level] = Math.round(prev);
} }
standard.levels = newLevels; standard.levels = newLevels;
} }
} }
@@ -796,12 +816,16 @@ export class Standards {
if (i == 0) { if (i == 0) {
return levels; return levels;
} }
const levelKeys = Object.keys(levels)
.map(Number)
.sort((a, b) => a - b);
const newLevels: Levels = {}; const newLevels: Levels = {};
let j = 1; let j = 1;
for (let k = 0; k < Object.keys(levels).length; ++k) {
const currLevel = Object.keys(levels)[k]; for (let k = 0; k < levelKeys.length; ++k) {
const nextLevel = Object.keys(levels)[k + 1]; const currLevel = levelKeys[k];
const nextLevel = levelKeys[k + 1];
newLevels[j++] = levels[currLevel]; newLevels[j++] = levels[currLevel];
@@ -813,28 +837,43 @@ export class Standards {
return this.expandLevels(newLevels, i - 1); return this.expandLevels(newLevels, i - 1);
} }
private compressLevels(levels: Levels, targetLevel: number): Levels { private compressLevels(levels: Levels, targetLvl: number): Levels {
const levelsAmount = Object.keys(levels).length; const keys = Object.keys(levels)
if (levelsAmount < targetLevel) { .map(Number)
.sort((a, b) => a - b);
const lvlCnt = keys.length;
if (targetLvl <= 0) {
throw new Error("Target levels amount must be a positive integer");
}
if (lvlCnt === 0) {
return {};
}
if (targetLvl === 1) {
return { 1: levels[keys[0]] };
}
if (lvlCnt < targetLvl) {
throw new Error( throw new Error(
"Target levels amount must be greater than or equal to the current levels amount", "Target levels amount must be less than or equal to the current levels amount",
); );
} }
const ratio = levelsAmount / targetLevel; // Linear resample
const compressedLevels: Levels = {}; const compressedLevels: Levels = {};
for (let i = 0; i < targetLevel; ++i) { for (let targetIndex = 0; targetIndex < targetLvl; ++targetIndex) {
const ratioIndex = i * ratio; const sourcePos = (targetIndex * (lvlCnt - 1)) / (targetLvl - 1);
const lowerIndex = Math.floor(ratioIndex);
const upperIndex = Math.ceil(ratioIndex);
const lowerValue = levels[lowerIndex + 1]; const sourceIndexLower = Math.floor(sourcePos);
const upperValue = levels[upperIndex + 1]; const sourceIndexUpper = Math.min(sourceIndexLower + 1, lvlCnt - 1);
const weight = ratioIndex - lowerIndex; const interpolationWeight = sourcePos - sourceIndexLower;
compressedLevels[i + 1] =
lowerValue + (upperValue - lowerValue) * weight; const lowerValue = levels[keys[sourceIndexLower]];
const upperValue = levels[keys[sourceIndexUpper]];
compressedLevels[targetIndex + 1] =
lowerValue + (upperValue - lowerValue) * interpolationWeight;
} }
return compressedLevels; return compressedLevels;
+53 -19
View File
@@ -50,9 +50,16 @@
peakAge: String( peakAge: String(
defaultStandards.cfg.activity[activity].peakAge, defaultStandards.cfg.activity[activity].peakAge,
), ),
stretch: String( stretch: {
defaultStandards.cfg.activity[activity].stretch, upper: String(
defaultStandards.cfg.activity[activity]
.stretch.upper,
), ),
lower: String(
defaultStandards.cfg.activity[activity]
.stretch.lower,
),
},
}, },
]; ];
}), }),
@@ -83,7 +90,10 @@
difficultyModifier: difficultyModifier:
+input.cfg.activity[activity].difficultyModifier, +input.cfg.activity[activity].difficultyModifier,
peakAge: +input.cfg.activity[activity].peakAge, peakAge: +input.cfg.activity[activity].peakAge,
stretch: +input.cfg.activity[activity].stretch, stretch: {
lower: +input.cfg.activity[activity].stretch.lower,
upper: +input.cfg.activity[activity].stretch.upper,
},
}, },
]), ]),
), ),
@@ -147,22 +157,6 @@
bind:value={input.cfg.global.maxLevel} bind:value={input.cfg.global.maxLevel}
/> />
</label> </label>
<label>
<span class="label mb-1">Stretch</span>
<input
class="input w-full"
type="number"
min="0"
max="10"
step="1"
bind:value={
input.cfg.activity[selected.activity].stretch
}
disabled={!selected.cfg.activity[selected.activity]
.enableGeneration}
/>
</label>
</div> </div>
</fieldset> </fieldset>
@@ -262,6 +256,46 @@
.enableGeneration} .enableGeneration}
/> />
</label> </label>
<fieldset class="fieldset bg-base-200 p-3 max-w-xl">
<legend class="fieldset-legend text-sm font-semibold">
Stretch
</legend>
<label>
<span class="label mb-1">Lower</span>
<input
class="input w-full"
type="number"
min="0"
max="10"
step="1"
bind:value={
input.cfg.activity[selected.activity]
.stretch.lower
}
disabled={!selected.cfg.activity[
selected.activity
].enableGeneration}
/>
</label>
<label>
<span class="label mb-1">Upper</span>
<input
class="input w-full"
type="number"
min="0"
max="10"
step="1"
bind:value={
input.cfg.activity[selected.activity]
.stretch.upper
}
disabled={!selected.cfg.activity[
selected.activity
].enableGeneration}
/>
</label>
</fieldset>
</div> </div>
</fieldset> </fieldset>
</div> </div>