feat(core): D.2b-B — port EncumbranceSystem capacity/load + SetLoadLevel fill/percent
EncumbranceCapacity (decomp 0x004fcc00), Load (0x004fcc40), SetLoadLevel fill=load/3 clamped + percent=floor(load*100) (0x004a6ea0). Golden tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5b3295ad13
commit
5875ac857d
2 changed files with 89 additions and 0 deletions
|
|
@ -216,6 +216,55 @@ public static class BurdenMath
|
|||
{
|
||||
public const int BurdenPerStrength = 150;
|
||||
|
||||
/// <summary>Augmentation bonus-burden per aug rank (retail EncumbranceCapacity, 0x1e).</summary>
|
||||
public const int AugBurdenPerRank = 30;
|
||||
/// <summary>Max augmentation bonus-burden contribution per Strength (retail clamp 0x96).</summary>
|
||||
public const int AugBurdenCap = 150;
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>EncumbranceSystem::EncumbranceCapacity(strength, aug)</c>
|
||||
/// (named-retail decomp 256393, <c>0x004fcc00</c>):
|
||||
/// <c>strength <= 0 ? 0 : strength*150 + clamp(aug*30, 0, 150)*strength</c>.
|
||||
/// </summary>
|
||||
public static int EncumbranceCapacity(int strength, int aug)
|
||||
{
|
||||
if (strength <= 0) return 0;
|
||||
int bonus = aug * AugBurdenPerRank;
|
||||
if (bonus < 0) bonus = 0; // decomp: eax_2 < 0 -> base only
|
||||
if (bonus > AugBurdenCap) bonus = AugBurdenCap;
|
||||
return strength * BurdenPerStrength + bonus * strength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>EncumbranceSystem::Load(capacity, burden)</c> (decomp 256413,
|
||||
/// <c>0x004fcc40</c>): the encumbrance ratio <c>burden / capacity</c>
|
||||
/// (1.0 = at capacity, up to ~3.0). The decompiler mangled the FP divide to
|
||||
/// <c>return burden</c>; the <c>cap <= 0</c> guard + single divide is unambiguous.
|
||||
/// Returns 0 when capacity <= 0 (no-data).
|
||||
/// </summary>
|
||||
public static float LoadRatio(int capacity, int burden)
|
||||
=> capacity <= 0 ? 0f : (float)burden / capacity;
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>gmBackpackUI::SetLoadLevel</c> bar fill (decomp 176542,
|
||||
/// <c>0x004a6ea6</c>): <c>clamp(load * 0.3333…, 0, 1)</c> — the bar is 1/3 full at
|
||||
/// 100% capacity, full at 300%.
|
||||
/// </summary>
|
||||
public static float LoadToFill(float load)
|
||||
{
|
||||
float fill = load / 3f;
|
||||
if (fill < 0f) return 0f;
|
||||
return fill > 1f ? 1f : fill;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>gmBackpackUI::SetLoadLevel</c> percent text (decomp 176576): after
|
||||
/// <c>arg2 = load/3</c>, the text is <c>floor(arg2 * 300) = floor(load * 100)</c>,
|
||||
/// NOT clamped (reads up to 300%+).
|
||||
/// </summary>
|
||||
public static int LoadToPercent(float load)
|
||||
=> (int)System.MathF.Floor(load * 100f);
|
||||
|
||||
public static int ComputeMax(int strength, int bonusBurden)
|
||||
=> BurdenPerStrength * strength + strength * bonusBurden;
|
||||
|
||||
|
|
|
|||
40
tests/AcDream.Core.Tests/Items/BurdenMathTests.cs
Normal file
40
tests/AcDream.Core.Tests/Items/BurdenMathTests.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using AcDream.Core.Items;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Items;
|
||||
|
||||
public class BurdenMathTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(100, 0, 15000)] // base: str*150
|
||||
[InlineData(100, 3, 24000)] // +clamp(3*30,0,150)=90 -> +90*100
|
||||
[InlineData(100, 10, 30000)] // 10*30=300 clamped to 150 -> +150*100
|
||||
[InlineData(0, 5, 0)] // str<=0 -> 0
|
||||
public void EncumbranceCapacity_matches_retail(int str, int aug, int expected)
|
||||
=> Assert.Equal(expected, BurdenMath.EncumbranceCapacity(str, aug));
|
||||
|
||||
[Theory]
|
||||
[InlineData(15000, 7500, 0.5f)]
|
||||
[InlineData(15000, 0, 0f)]
|
||||
[InlineData(0, 100, 0f)] // cap<=0 guard
|
||||
public void LoadRatio_is_burden_over_capacity(int cap, int burden, float expected)
|
||||
=> Assert.Equal(expected, BurdenMath.LoadRatio(cap, burden), 4);
|
||||
|
||||
[Theory]
|
||||
[InlineData(0f, 0f)]
|
||||
[InlineData(0.5f, 0.16667f)]
|
||||
[InlineData(1.0f, 0.33333f)]
|
||||
[InlineData(3.0f, 1.0f)] // full at 300%
|
||||
[InlineData(4.0f, 1.0f)] // clamped
|
||||
public void LoadToFill_is_third_clamped(float load, float expected)
|
||||
=> Assert.Equal(expected, BurdenMath.LoadToFill(load), 4);
|
||||
|
||||
[Theory]
|
||||
[InlineData(0f, 0)]
|
||||
[InlineData(0.5f, 50)]
|
||||
[InlineData(1.0f, 100)]
|
||||
[InlineData(3.0f, 300)]
|
||||
[InlineData(4.0f, 400)] // percent NOT clamped
|
||||
public void LoadToPercent_is_floor_load_times_100(float load, int expected)
|
||||
=> Assert.Equal(expected, BurdenMath.LoadToPercent(load));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue