Calculator simplification

This commit is contained in:
Dominic Ferrando
2025-10-17 16:36:18 -04:00
parent 331d20d272
commit 06580e0a81
5 changed files with 109 additions and 145 deletions
+17 -4
View File
@@ -1,10 +1,12 @@
<script lang="ts">
import type {
Metrics,
Standard,
import {
Standards,
StandardsConfig,
type ActivityStandards,
type Metrics,
type Standard,
type StandardsConfig,
} from "$lib/services/calculator/main";
import allStandardsRaw from "$lib/data/standards.json" assert { type: "json" };
import {
Activity,
cmToIn,
@@ -24,6 +26,17 @@
const { selected, allStandards }: Props = $props();
const allAnchorStandards = $derived.by(() => {
const standardsCfg = structuredClone(selected.cfg);
for (const activityCfg of Object.values(standardsCfg.activity)) {
activityCfg.disableGeneration = true;
}
return new Standards(
allStandardsRaw as ActivityStandards,
standardsCfg,
);
});
const getLvlValue = (
activity: Activity,
standard: Standard,
+5 -20
View File
@@ -4738,10 +4738,7 @@
"attribute": "Endurance",
"generators": [
{
"metric": "weight",
"spread": 3,
"step": 12,
"ratio": "normal"
"metric": "weight"
}
],
"unit": "ms",
@@ -5231,16 +5228,10 @@
"attribute": "Power",
"generators": [
{
"metric": "age",
"spread": 4,
"step": 10,
"ratio": "inverse"
"metric": "age"
},
{
"metric": "weight",
"spread": 3,
"step": 12,
"ratio": "inverse"
"metric": "weight"
}
],
"unit": "cm",
@@ -5282,16 +5273,10 @@
"attribute": "Agility",
"generators": [
{
"metric": "weight",
"spread": 3,
"step": 12,
"ratio": "normal"
"metric": "weight"
},
{
"metric": "age",
"spread": 4,
"step": 10,
"ratio": "normal"
"metric": "age"
}
],
"unit": "ms",
+26 -33
View File
@@ -10,11 +10,6 @@ import { Activity, Attribute, Gender, getAvgWeight, kgToLb, lbToKg, type Activit
// Broad Jump:
// https://nrpt.co.uk/training/tests/power/broad.htm
type LevelCalculatorConfig = {
expandIters?: number;
compressTo?: number;
}
export type LevelCalculatorOutput = {
player: number,
attributes: Record<Attribute, number>
@@ -36,10 +31,7 @@ export interface Standard {
}
type Generator = {
metric: NumberMetric,
spread: number,
step: number,
ratio: "normal" | "inverse"
metric: NumberMetric
}
export type ActivityStandards = Record<Activity, {
@@ -185,8 +177,8 @@ export type StandardsConfig = {
weightModifier: number,
weightSkew: number,
ageModifier: number,
disableGeneration: boolean,
performanceSkew: number
enableGeneration: boolean,
difficultyModifier: number
}>
}
@@ -195,15 +187,18 @@ export class Standards {
private activityStandards: ActivityStandards;
constructor(activityStandards: ActivityStandards, cfg?: Partial<StandardsConfig>) {
// TODO: set these somewhere
const defaultActivitiesConfig = Object.fromEntries(
Object.values(Activity).map(activity => [
activity,
{
disableGeneration: false,
weightModifier: 0.1,
enableGeneration: true,
weightModifier: activity === Activity.BroadJump ?
-0.1 :
0.1,
weightSkew: 0,
ageModifier: 0.1,
performanceSkew: 0
difficultyModifier: 0
},
])
) as Record<Activity, StandardsConfig["activity"][Activity]>;
@@ -226,22 +221,24 @@ export class Standards {
// apply skew config
for (const level in standard.levels) {
standard.levels[level] = standard.levels[level] * (1 + this.cfg.activity[activity].performanceSkew)
standard.levels[level] = standard.levels[level] * (1 + this.cfg.activity[activity].difficultyModifier)
}
}
// generate data
const allGenerators = this.cfg.activity[activity].disableGeneration ?
[] :
this.activityStandards[activity].metadata.generators;
const allGenerators = this.cfg.activity[activity].enableGeneration ?
this.activityStandards[activity].metadata.generators :
[];
const ageGenerators = allGenerators.filter(g => g.metric === "age");
for (const ageGenerator of ageGenerators) {
for (const gender of Object.values(Gender)) {
const referenceAge = 50;
const ageStep = 10;
const referenceAge = 25;
const medianAge = 50;
const minAge = referenceAge - (ageGenerator.step * ageGenerator.spread);
const maxAge = referenceAge + (ageGenerator.step * ageGenerator.spread);
const minAge = medianAge - (3 * ageStep);
const maxAge = medianAge + (3 * ageStep);
const referenceStandard = this
.byActivity(activity)
@@ -264,9 +261,7 @@ export class Standards {
if (!referenceStandard.levels[lvl]) continue;
const scalingExponent = this.cfg.activity[activity].ageModifier;
const coefficient = ageGenerator.ratio === "inverse" ?
referenceAge / currAge :
currAge / referenceAge;
const coefficient = currAge / referenceAge;
newStandard.levels[lvl] = referenceStandard.levels[lvl] * (coefficient ** scalingExponent);
}
@@ -278,7 +273,7 @@ export class Standards {
if (!overlappingStandard)
this.activityStandards[activity].standards.push(newStandard);
currAge += ageGenerator.step;
currAge += ageStep;
}
}
@@ -291,11 +286,11 @@ export class Standards {
for (const weightGenerator of weightGenerators) {
for (const gender of Object.values(Gender)) {
for (const age of this.agesFor(activity, gender)) {
const weightStep = 12;
const referenceWeight = getAvgWeight(gender, age);
const skewRatio = this.cfg.activity[activity].weightSkew; // 0 = normal, 1 = max skew
const minWeight = referenceWeight - (weightGenerator.step * weightGenerator.spread * (1 - skewRatio));
const maxWeight = referenceWeight + (weightGenerator.step * weightGenerator.spread * (1 + skewRatio));
const minWeight = referenceWeight - (3 * weightStep);
const maxWeight = referenceWeight + (3 * weightStep);
const referenceStandard = this
.byActivity(activity)
@@ -317,10 +312,8 @@ export class Standards {
for (const lvl in referenceStandard.levels) {
if (!referenceStandard.levels[lvl]) continue;
const scalingExponent = this.cfg.activity[activity].weightModifier;
const coefficient = weightGenerator.ratio === "inverse" ?
referenceWeight / currWeight :
currWeight / referenceWeight;
const scalingExponent = this.cfg.activity[activity].weightModifier
const coefficient = currWeight / referenceWeight;
newStandard.levels[lvl] = referenceStandard.levels[lvl] * (coefficient ** scalingExponent);
}
@@ -333,7 +326,7 @@ export class Standards {
if (!overlappingStandard)
this.activityStandards[activity].standards.push(newStandard);
currWeight += weightGenerator.step;
currWeight += weightStep;
}
}
}
-37
View File
@@ -67,43 +67,6 @@ export const msToTime = (ms: number, includeMs = false): string => {
export const range = (length: number) => Array.from({ length: length }, (_, i) => i + 1);
// export const getAvgWeight = (gender: Gender, age: number) => {
// type AvgWeightData = {
// metadata: {
// unit: StandardUnit
// },
// weights: Metrics[]
// }
// let avgWeightData = avgWeightDataRaw as AvgWeightData;
//
// const avgWeights = avgWeightData.weights.filter(a => a.gender === gender);
//
// let lower = avgWeights[0]; // best <= target
// let upper = avgWeights[0]; // best >= target
// let hasLower = false, hasUpper = false;
//
// for (const avg of avgWeights) {
// if (avg.age <= age) {
// if (!hasLower || avg.age > lower.age) { lower = avg; hasLower = true; }
// }
// if (avg.age >= age) {
// if (!hasUpper || avg.age < upper.age) { upper = avg; hasUpper = true; }
// }
// }
//
// // clamp to ends if one side missing
// if (!hasLower) lower = avgWeights[0];
// if (!hasUpper) upper = avgWeights[avgWeights.length - 1];
//
// // lerp
// let ageRatio = upper === lower ? 1 : (age - lower.age) / (upper.age - lower.age);
// ageRatio = Math.max(0, Math.min(1, ageRatio));
// const interpolatedAvg = lower.weight + (upper.weight - lower.weight) * ageRatio;
//
// return interpolatedAvg;
// }
export const getAvgWeight = (gender: Gender, age: number) => {
type AvgWeightData = {
metadata: {
+61 -51
View File
@@ -12,7 +12,11 @@
let activeTab = $state("standards");
let inputSelected = $state({
const defaultStandards = new Standards(
allStandardsRaw as ActivityStandards,
);
let input = $state({
activity: Activity.BenchPress,
metrics: {
gender: Gender.Male,
@@ -21,51 +25,57 @@
},
cfg: {
global: {
maxLevel: "100",
maxLevel: String(defaultStandards.cfg.global.maxLevel),
},
activity: Object.fromEntries(
Object.values(Activity).map((activity) => [
activity,
{
enableGeneration: true,
weightModifier: ".1",
// weightSkew: "0",
ageModifier: ".1",
performanceSkew: "0",
},
]),
Object.values(Activity).map((activity) => {
return [
activity,
{
enableGeneration:
defaultStandards.cfg.activity[activity]
.enableGeneration,
weightAdvantage: String(
defaultStandards.cfg.activity[activity]
.weightModifier,
),
ageModifier: String(
defaultStandards.cfg.activity[activity]
.ageModifier,
),
difficultyModifier: String(
defaultStandards.cfg.activity[activity]
.difficultyModifier,
),
},
];
}),
),
},
});
const selected = $derived({
activity: inputSelected.activity,
activity: input.activity,
metrics: {
gender: inputSelected.metrics.gender,
age: +inputSelected.metrics.age,
weight: lbToKg(+inputSelected.metrics.weight),
gender: input.metrics.gender,
age: +input.metrics.age,
weight: lbToKg(+input.metrics.weight),
} as Metrics,
cfg: {
global: {
maxLevel: +inputSelected.cfg.global.maxLevel,
maxLevel: +input.cfg.global.maxLevel,
},
activity: Object.fromEntries(
Object.values(Activity).map((activity) => [
activity,
{
weightModifier:
+inputSelected.cfg.activity[activity]
.weightModifier,
weightSkew: 0,
// +inputSelected.cfg.activity[activity].weightSkew,
ageModifier:
+inputSelected.cfg.activity[activity].ageModifier,
disableGeneration:
!inputSelected.cfg.activity[activity]
.enableGeneration,
performanceSkew:
+inputSelected.cfg.activity[activity]
.performanceSkew,
+input.cfg.activity[activity].weightAdvantage,
ageModifier: +input.cfg.activity[activity].ageModifier,
enableGeneration:
input.cfg.activity[activity].enableGeneration,
difficultyModifier:
+input.cfg.activity[activity].difficultyModifier,
},
]),
),
@@ -78,16 +88,16 @@
$effect(() => {
console.log(selected);
if (!inputSelected.metrics.age) inputSelected.metrics.weight = "";
if (!input.metrics.age) input.metrics.weight = "";
});
$effect(() => {
const savedParameters = localStorage.getItem("parameters");
if (savedParameters) inputSelected = JSON.parse(savedParameters);
if (savedParameters) input = JSON.parse(savedParameters);
});
$effect(() => {
localStorage.setItem("parameters", JSON.stringify(inputSelected));
localStorage.setItem("parameters", JSON.stringify(input));
});
</script>
@@ -110,7 +120,7 @@
min="1"
max="100"
placeholder="5"
bind:value={inputSelected.cfg.global.maxLevel}
bind:value={input.cfg.global.maxLevel}
/>
</label>
</div>
@@ -126,7 +136,7 @@
<input
type="checkbox"
bind:checked={
inputSelected.cfg.activity[selected.activity]
input.cfg.activity[selected.activity]
.enableGeneration
}
class="toggle toggle-lg m-auto"
@@ -142,11 +152,11 @@
step=".05"
placeholder=".1"
bind:value={
inputSelected.cfg.activity[selected.activity]
.weightModifier
input.cfg.activity[selected.activity]
.weightAdvantage
}
disabled={selected.cfg.activity[selected.activity]
.disableGeneration}
disabled={!selected.cfg.activity[selected.activity]
.enableGeneration}
/>
</label>
<!-- <label> -->
@@ -161,8 +171,8 @@
<!-- inputSelected.cfg.activity[selected.activity] -->
<!-- .weightSkew -->
<!-- } -->
<!-- disabled={selected.cfg.activity[selected.activity] -->
<!-- .disableGeneration} -->
<!-- disabled={!selected.cfg.activity[selected.activity] -->
<!-- .enableGeneration} -->
<!-- /> -->
<!-- </label> -->
<label>
@@ -174,11 +184,11 @@
max="1"
step=".05"
bind:value={
inputSelected.cfg.activity[selected.activity]
.performanceSkew
input.cfg.activity[selected.activity]
.difficultyModifier
}
disabled={selected.cfg.activity[selected.activity]
.disableGeneration}
disabled={!selected.cfg.activity[selected.activity]
.enableGeneration}
/>
</label>
<label>
@@ -190,11 +200,11 @@
max="1"
step=".05"
bind:value={
inputSelected.cfg.activity[selected.activity]
input.cfg.activity[selected.activity]
.ageModifier
}
disabled={selected.cfg.activity[selected.activity]
.disableGeneration}
disabled={!selected.cfg.activity[selected.activity]
.enableGeneration}
/>
</label>
</div>
@@ -213,7 +223,7 @@
<select
class="select w-full"
name="activities"
bind:value={inputSelected.activity}
bind:value={input.activity}
>
{#each Object.values(Activity) as activity}
<option value={activity}>
@@ -229,7 +239,7 @@
<select
class="select w-full"
name="genders"
bind:value={inputSelected.metrics.gender}
bind:value={input.metrics.gender}
>
{#each Object.values(Gender) as gender}
<option value={gender}>{gender}</option>
@@ -245,7 +255,7 @@
min="0"
max="100"
placeholder="18"
bind:value={inputSelected.metrics.age}
bind:value={input.metrics.age}
/>
</label>
@@ -259,7 +269,7 @@
placeholder={selected.metrics.age
? "170"
: "Requires age"}
bind:value={inputSelected.metrics.weight}
bind:value={input.metrics.weight}
disabled={!selected.metrics.age}
/>
</label>