Correct builder item level scaling
This commit is contained in:
@@ -49,6 +49,36 @@ const itemLevel = (value: unknown) => Math.round(clamp(value, 1, 50));
|
||||
export const gearMultiplier = (level: unknown) => 0.91 + 0.05 * itemLevel(level);
|
||||
export const weaponMultiplier = (level: unknown) => 1.14 + 0.05 * itemLevel(level);
|
||||
|
||||
export function gearBaseStatRange(minimum: unknown, maximum: unknown, targetLevel: unknown): LevelScaledRange {
|
||||
const target = itemLevel(targetLevel);
|
||||
const configuredMin = Number(minimum);
|
||||
const configuredMax = Number(maximum);
|
||||
const rawMin = (Number.isFinite(configuredMin) ? configuredMin : 0) * gearMultiplier(target);
|
||||
const rawMax = (Number.isFinite(configuredMax) ? configuredMax : 0) * gearMultiplier(target);
|
||||
return {
|
||||
rawMin,
|
||||
rawMax,
|
||||
min: Math.floor(rawMin),
|
||||
max: Math.floor(rawMax),
|
||||
provisional: target < 10
|
||||
};
|
||||
}
|
||||
|
||||
export function weaponBaseRange(minimum: unknown, maximum: unknown, targetLevel: unknown): LevelScaledRange {
|
||||
const target = itemLevel(targetLevel);
|
||||
const configuredMin = Number(minimum);
|
||||
const configuredMax = Number(maximum);
|
||||
const rawMin = (Number.isFinite(configuredMin) ? configuredMin : 0) * weaponMultiplier(target);
|
||||
const rawMax = (Number.isFinite(configuredMax) ? configuredMax : 0) * weaponMultiplier(target);
|
||||
return {
|
||||
rawMin,
|
||||
rawMax,
|
||||
min: Math.floor(rawMin),
|
||||
max: Math.floor(rawMax),
|
||||
provisional: target < 40
|
||||
};
|
||||
}
|
||||
|
||||
export function levelScaledRange(
|
||||
minimum: unknown,
|
||||
maximum: unknown,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import RangeValueControl from '$lib/components/RangeValueControl.svelte';
|
||||
import SegmentedChoice from '$lib/components/SegmentedChoice.svelte';
|
||||
import {
|
||||
blankRoll, clamp, createDraft, gearSlots, levelScaledRange, remapRangeValue, scaled,
|
||||
blankRoll, clamp, createDraft, gearBaseStatRange, gearSlots, levelScaledRange, remapRangeValue, scaled, weaponBaseRange,
|
||||
type LoadoutDraft, type Roll
|
||||
} from '$lib/builder/loadout';
|
||||
|
||||
@@ -37,9 +37,11 @@
|
||||
const scaledRange = (minimum: unknown, maximum: unknown, level: unknown, referenceLevel: unknown, source: 'gear' | 'weapon') =>
|
||||
levelScaledRange(minimum, maximum, level, referenceLevel ?? 50, source);
|
||||
const gearBaseRange = (item: any, key: 'health' | 'armor', level: number) =>
|
||||
scaledRange(item?.[`base_${key}_min`], item?.[`base_${key}_max`], level, item?.range_reference_level, 'gear');
|
||||
gearBaseStatRange(item?.[`base_${key}_min`], item?.[`base_${key}_max`], level);
|
||||
const weaponStatRange = (range: any, key: typeof weaponRangeKeys[number], level: number) =>
|
||||
scaledRange(range?.[`${key}_min`], range?.[`${key}_max`], level, range?.reference_level ?? 50, 'weapon');
|
||||
key === 'damage'
|
||||
? weaponBaseRange(range?.damage_min, range?.damage_max, level)
|
||||
: scaledRange(range?.[`${key}_min`], range?.[`${key}_max`], level, range?.reference_level ?? 50, 'weapon');
|
||||
const coreAllocationRange = (tier: number, level: number, source: 'gear' | 'weapon') => {
|
||||
const profile = data.coreRanges.find((item) => Number(item.tier) === Number(tier));
|
||||
const nominal = scaledRange(profile?.nominal_min, profile?.nominal_max, level, profile?.reference_level ?? 50, source);
|
||||
@@ -337,7 +339,7 @@
|
||||
<BuilderRadioGrid label="BRAND SET" name={`gear-${slot}-brand`} items={item.brandIds.map((id: string) => brand(id)).filter(Boolean)} bind:value={selection.brandId} columns={Math.min(item.brandIds.length,4)} iconOnly required/>
|
||||
{#if item.talentIds.length}<BuilderRadioGrid label="EQUIPPED TALENT" name={`gear-${slot}-talent`} items={item.talentIds.map((id: string) => talent(id)).filter(Boolean)} bind:value={selection.talentId} columns={Math.min(item.talentIds.length,4)} searchable={item.talentIds.length > 6} required/>{/if}
|
||||
</div>
|
||||
<p class:provisional={healthRange.provisional} class="builder-scaling-note">{healthRange.provisional ? 'Estimated range: levels 1–9 remain provisional while additional game samples are collected.' : `Ranges scaled from the stored level-${item.range_reference_level ?? 50} decimal endpoints. Display values are truncated to match the game.`}</p>
|
||||
<p class:provisional={healthRange.provisional} class="builder-scaling-note">{healthRange.provisional ? 'Estimated range: levels 1–9 remain provisional while additional game samples are collected.' : 'Base Health and Armor are calculated from the stored gear base ranges at the selected item level. Display values are truncated to match the game.'}</p>
|
||||
<div class="builder-range-grid"><RangeValueControl label="BASE HEALTH" min={healthRange.min} max={healthRange.max} provisional={healthRange.provisional} bind:value={selection.health}/>{#if armorRange}<RangeValueControl label="BASE ARMOR" min={armorRange.min} max={armorRange.max} provisional={armorRange.provisional} bind:value={selection.armor}/>{/if}</div>
|
||||
{@const gearCore = coreAllocationRange(Number(item.tier), selection.level, 'gear')}
|
||||
<div class="builder-section-title"><div><span>CORE ATTRIBUTE ALLOCATION</span><small>SHARED T{item.tier} POOL · VALUES MAY BE REDISTRIBUTED BETWEEN CORES</small></div><b class:restricted={coreTotal(selection) < gearCore.poolMin || coreTotal(selection) > gearCore.poolMax}>{coreTotal(selection)} / {gearCore.poolMin}–{gearCore.poolMax}</b></div>
|
||||
@@ -360,7 +362,7 @@
|
||||
</div>
|
||||
{@const range = item.ranges.find((entry: any) => Number(entry.tier) === selection.tier)}
|
||||
{#if range}
|
||||
<p class:provisional={selection.level < 40} class="builder-scaling-note">{selection.level < 40 ? 'Estimated range: the weapon curve below level 40 is provisional and may change as observations are added.' : `Damage scaled from the stored level-${range.reference_level ?? 50} decimal endpoints. Core attributes use the shared tier allocation pool.`}</p>
|
||||
<p class:provisional={selection.level < 40} class="builder-scaling-note">{selection.level < 40 ? 'Estimated range: the weapon curve below level 40 is provisional and may change as observations are added.' : 'Damage is calculated from the stored weapon base range at the selected item level. Core attributes use the shared level-50 tier allocation pool.'}</p>
|
||||
{@const damageRange = weaponStatRange(range,'damage',selection.level)}
|
||||
<div class="builder-range-grid weapon-damage"><RangeValueControl label="DAMAGE" min={damageRange.min} max={damageRange.max} provisional={damageRange.provisional} bind:value={selection.damage}/></div>
|
||||
{@const weaponCore = coreAllocationRange(selection.tier, selection.level, 'weapon')}
|
||||
|
||||
Reference in New Issue
Block a user