Files
shd.build/docs/STAT_LEVEL_SCALING.md

245 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Weapon and gear stat level scaling
Use these observed rules to adjust weapon and gear stat ranges by item level.
Apply each rule to both endpoints of a configured range.
In-game observations, rather than published game mechanics, support these
formulas. Preserve source observations with new samples and revise constants
when a larger data set disproves them.
## Numeric representation and display
Store stat ranges as unrounded decimals. The game display truncates them:
```text
displayedValue = floor(rawValue)
```
Avoid `ceil` and repeated leveling of displayed integers. Both accumulate
errors. A level-1 display of `0` can hide a positive fractional raw value that
appears at a higher level.
For example, a standard gear item displayed `0 ENG` at level 1 and `2 ENG` at
level 42. With a level-42 multiplier of `3.01`, a raw base near `0.7` explains
both values:
```text
floor(0.7) = 0
floor(0.7 × 3.01) = 2
```
Predictions from displayed integers can miss by one because truncation loses
information. That error does not indicate rounding upward.
## Shared progression model
The stable portion of both progressions is linear:
```text
rawStat(level) = hiddenBase × multiplier(level)
```
The multiplier increases by `0.05` per level. Equivalently, each level adds a
fixed `5%` of the hidden base:
```text
rawStat(level + 1) - rawStat(level) = hiddenBase × 0.05
```
Do not calculate a new level as `previousDisplayedValue × 1.05`. That compound
growth model conflicts with the observations.
For a range `[minimum, maximum]`, calculate each endpoint separately:
```text
scaledMinimum = hiddenMinimum × multiplier(level)
scaledMaximum = hiddenMaximum × multiplier(level)
```
Truncate at the game-display boundary.
## Gear
The observed stable gear multiplier is:
```text
gearMultiplier(level) = 0.91 + 0.05 × level
```
Samples across Standard, Specialized, and High-end qualities validate this rule
for levels 10 through 50. Quality changes hidden stat ranges; the observed
late-level multiplier remains the same.
Given hidden level-independent endpoints:
```text
rawMinimum(level) = hiddenMinimum × gearMultiplier(level)
rawMaximum(level) = hiddenMaximum × gearMultiplier(level)
```
ISAC gear `base_health` and `base_armor` endpoints are these hidden,
level-independent bases. Builder base survivability therefore uses the direct
multiplier form above rather than treating those endpoints as observations at
the row's legacy reference level. Rolled Attributes and shared core profiles
retain their separately defined reference semantics.
Given an unrounded range recorded at a known reference level:
```text
targetRaw =
referenceRaw
× gearMultiplier(targetLevel)
/ gearMultiplier(referenceLevel)
```
### Gear example
At level 42:
```text
gearMultiplier(42) = 0.91 + 0.05 × 42 = 3.01
```
A Standard item with approximate hidden bases of `615.0 HP`, `107.4 AR`,
`10.0 FP`, `11.4 TGH`, and `0.7 ENG` produces:
```text
HP = floor(615.0 × 3.01) = 1851
AR = floor(107.4 × 3.01) = 323
FP = floor(10.0 × 3.01) = 30
TGH = floor(11.4 × 3.01) = 34
ENG = floor(0.7 × 3.01) = 2
```
### Early gear levels
Levels 1 through 9 show a quality-dependent transition outside the stable
multiplier. Do not extrapolate it from one level-1-to-level-2 change. Until
more samples define the early curves, store authoritative ranges or label
calculated results as estimates.
## Weapons
Weapon observations from levels 40 through 50 show the same fixed `0.05`
multiplier increase per level, but a different offset:
```text
weaponMultiplier(level) ≈ 1.14 + 0.05 × level
```
This gives approximately:
```text
weaponMultiplier(40) = 3.14
weaponMultiplier(50) = 3.64
```
and predicts a level-40-to-level-50 ratio of:
```text
3.64 / 3.14 ≈ 1.15924
```
Two observed weapons produced ratios of approximately `1.15960` and `1.15936`.
The small difference is consistent with hidden fractional bases and truncated
display values.
Given an unrounded weapon range at a known reference level:
```text
targetRaw =
referenceRaw
× weaponMultiplier(targetLevel)
/ weaponMultiplier(referenceLevel)
```
Per-weapon damage endpoints in ISAC are hidden base ranges, not observations at
the shared core profile's reference level. Calculate weapon damage directly:
```text
damageRaw(level) = storedDamageBase × weaponMultiplier(level)
```
Firepower, Toughness, and Engineering remain shared reference-level
observations and continue to use the ratio form.
SHD.build stores the reference level associated with each weapon-tier range.
Builder calculations use that recorded observation level in the ratio above.
The curve's level-1 origin does not imply that every stored range was observed
at level 1. T2 availability limits likewise do not change a range's recorded
reference level.
Firepower, Toughness, and Engineering nominal tier ranges are stored once in
the shared core-allocation configuration at reference level 50. Scale those
nominal endpoints to the target level using the appropriate equipment-family
multiplier, then derive the combined pool bounds by multiplying each endpoint
by three. Redistribution changes the split between the three cores without
changing the selected item's total allocation.
The `1.14` offset remains provisional; observations only validate it from level
40 to 50. Retain enough decimal precision to revise it without migrating
rounded data.
### Weapon example
A weapon changed from `753 DMG` at level 40 to `873 DMG` at level 50:
```text
753 × (3.64 / 3.14) ≈ 872.95
floor(872.95) = 872
```
The calculation misses by one because `753` is truncated. The ratio applied to
the unrounded source value produces `873`, so calculations require stored
decimal endpoints.
The observed changes over those ten levels were linear:
```text
DMG: 753 → 873 (12.0 raw points per level)
FP: 44 → 51 (about 0.7 per level)
TGH: 15 → 18 (about 0.3 per level)
ENG: 45 → 52 (about 0.7 per level)
```
### Early weapon levels
Do not apply the provisional late-level weapon offset at level 1. An Exotic
sample's level-1 and level-50 displays fit a `2.91` endpoint ratio for most
stats, which conflicts with extending the level-40-to-level-50 offset
backward. Weapons appear to have an early transition or quality-dependent input.
Until more intermediate samples are available:
- use the weapon formula only within the validated level 40–50 interval;
- do not derive a hidden base directly from a displayed level-1 value;
- prefer authoritative low-level ranges where exact results are required.
## Implementation requirements
1. Store range endpoints as decimals and retain their reference level.
2. Scale minimum and maximum independently using raw values.
3. Calculate directly from the reference value; never chain per-level rounded
results.
4. Truncate only at the presentation boundary when matching the game display.
5. Clamp supported levels to `1–50`.
6. Treat gear levels below 10 and weapon levels below 40 as provisional until
their curves are verified.
7. Add observed intermediate values as regression fixtures before changing a
multiplier or rounding rule.
## Current regression observations
Useful known outputs include:
- Specialized gear at level 27: `1610 HP`, `280 AR`, `29 FP`, `23 TGH`,
`10 ENG`.
- Standard gear at level 42: `1851 HP`, `323 AR`, `30 FP`, `34 TGH`, `2 ENG`.
- High-end gear: level 40 `2682 HP`, `464 AR`, `47 FP`, `16 TGH`, `36 ENG`;
level 41 `2728 HP`, `472 AR`, `48 FP`, `16 TGH`, `37 ENG`; level 50
`3143 HP`, `544 AR`, `55 FP`, `19 TGH`, `43 ENG`.
- Weapon sample: level 40 `1886 DMG`, `120 FP`, `123 TGH`, `31 ENG`; level 50
`2187 DMG`, `139 FP`, `142 TGH`, `36 ENG`.
- Weapon sample: level 40 `753 DMG`, `44 FP`, `15 TGH`, `45 ENG`; level 50
`873 DMG`, `51 FP`, `18 TGH`, `52 ENG`.