249 lines
8.0 KiB
Markdown
249 lines
8.0 KiB
Markdown
# Weapon and gear stat level scaling
|
||
|
||
This document records the empirically observed rules for adjusting weapon and
|
||
gear stat ranges by item level. The rules apply independently to both endpoints
|
||
of a configured range.
|
||
|
||
These formulas are based on in-game observations rather than published game
|
||
mechanics. Preserve source observations when adding new samples and revise the
|
||
constants if a larger data set disproves them.
|
||
|
||
## Numeric representation and display
|
||
|
||
Stat ranges must retain their unrounded decimal values. The integer shown by the
|
||
game is consistent with truncation:
|
||
|
||
```text
|
||
displayedValue = floor(rawValue)
|
||
```
|
||
|
||
Do not use `ceil`, and do not repeatedly level an already displayed integer.
|
||
Both approaches accumulate errors. A level-1 value displayed as `0` can still
|
||
have a positive fractional raw value that becomes visible 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
|
||
```
|
||
|
||
When only displayed integers are available, a prediction can be off by one.
|
||
That is expected information loss, not evidence for rounding upward.
|
||
|
||
## Shared progression model
|
||
|
||
The stable portion of both progressions is linear, not compounded:
|
||
|
||
```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
|
||
```
|
||
|
||
Never calculate a new level as `previousDisplayedValue × 1.05`; that describes
|
||
compound growth and does not match the observations.
|
||
|
||
For a range `[minimum, maximum]`, calculate each endpoint separately:
|
||
|
||
```text
|
||
scaledMinimum = hiddenMinimum × multiplier(level)
|
||
scaledMaximum = hiddenMaximum × multiplier(level)
|
||
```
|
||
|
||
Only truncate when producing a game-matching display value.
|
||
|
||
## Gear
|
||
|
||
The observed stable gear multiplier is:
|
||
|
||
```text
|
||
gearMultiplier(level) = 0.91 + 0.05 × level
|
||
```
|
||
|
||
This rule is validated for levels 10 through 50 across Standard, Specialized,
|
||
and High-end samples. Quality changes the hidden stat ranges; the observed
|
||
late-level multiplier does not change with quality.
|
||
|
||
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 and do not follow the
|
||
stable multiplier directly. Do not extrapolate that transition from a single
|
||
level-1-to-level-2 change. Until the early curves are sampled more completely,
|
||
store authoritative ranges for those levels or mark 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 is provisional and is currently validated only for the
|
||
level-40-to-level-50 interval. Preserve 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 displayed-source calculation misses by one because `753` is already
|
||
truncated. Applying the ratio to the unrounded source value produces the
|
||
observed `873`. This is why calculations must use 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 to level 1. An Exotic
|
||
sample's displayed level-1 values and level-50 values fit a `2.91` endpoint
|
||
ratio for most stats, which is incompatible with simply extending the
|
||
level-40-to-level-50 offset backward. Weapons therefore also appear to have an
|
||
early transition or another 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`.
|