diff --git a/src/AcDream.Plugins.MossTank/CombatController.cs b/src/AcDream.Plugins.MossTank/CombatController.cs
index cf52b5eac..640434bc2 100644
--- a/src/AcDream.Plugins.MossTank/CombatController.cs
+++ b/src/AcDream.Plugins.MossTank/CombatController.cs
@@ -243,11 +243,11 @@ internal sealed class CombatController
_untilScan -= Math.Max(0d, elapsedSeconds);
if (_untilScan <= 0d)
{
- float acquisitionRange = navigationEnabled
+ double acquisitionRange = navigationEnabled
? Math.Max(_settings.MaximumRange, _settings.ApproachDistance)
: _settings.MaximumRange;
_targets = _host.Automation.Combat.CaptureHostileTargets(
- acquisitionRange);
+ (float)acquisitionRange);
foreach (uint ghost in _failures.ObserveTargets(
_targets,
_now,
@@ -588,7 +588,7 @@ internal sealed class CombatController
float maximumRange = spell.BaseRangeConstant
+ (spell.BaseRangeModifier * skill.Current)
- - _settings.SpellRangeFudge;
+ - (float)_settings.SpellRangeFudge;
return maximumRange <= 0f
|| target.ObjectId == 0u
|| target.Distance <= MathF.Min(75f, maximumRange);
@@ -1087,15 +1087,15 @@ internal sealed class CombatController
targetObjectId,
kind,
height,
- _settings.CollisionProjectileRadius,
- _settings.CollisionStepDistance,
+ (float)_settings.CollisionProjectileRadius,
+ (float)_settings.CollisionStepDistance,
_settings.MaximumCollisionChecksPerTick)
: _host.Automation.Projectiles.EvaluatePath(
targetObjectId,
kind,
height,
- _settings.CollisionProjectileRadius,
- _settings.CollisionStepDistance,
+ (float)_settings.CollisionProjectileRadius,
+ (float)_settings.CollisionStepDistance,
_settings.MaximumCollisionChecksPerTick);
if (_settings.ShowCollisionDebug && result.DebugSamples.Count > 0)
{
diff --git a/src/AcDream.Plugins.MossTank/CombatSettings.cs b/src/AcDream.Plugins.MossTank/CombatSettings.cs
index 54fce0447..d0a325fd5 100644
--- a/src/AcDream.Plugins.MossTank/CombatSettings.cs
+++ b/src/AcDream.Plugins.MossTank/CombatSettings.cs
@@ -69,25 +69,30 @@ internal sealed class CombatSettings
public bool Enabled { get; set; } = true;
/// VTank's hunt-cast skill margin.
public int HuntSkillExcessOverDifficulty { get; set; } = 25;
- public float MaximumRange { get; set; } = 5f;
+ // Declared tDouble in VTank's own Settings table (VtankOptionCatalog):
+ // double, not float, so a loaded .usd round-trips this setting exactly
+ // instead of losing precision below float's ~7-digit guarantee (item I,
+ // slice-1 fix round). Physics/combat consumers that want a float cast
+ // at their own use site.
+ public double MaximumRange { get; set; } = 5d;
///
/// Monsters nearer than this are not valid attack targets. VTank applies
/// this before priority and angle/range ranking.
///
- public float MinimumRange { get; set; }
+ public double MinimumRange { get; set; }
///
/// VTank's Approach Distance. Zero disables monster approach; otherwise
/// navigation may close a selected target from this range down to
/// .
///
- public float ApproachDistance { get; set; }
+ public double ApproachDistance { get; set; }
public bool IdlePeaceMode { get; set; }
public bool StopMacroOnDeath { get; set; } = true;
public bool JumpOutWandCasting { get; set; }
public bool DoJiggle { get; set; }
public TargetSelectionMethod SelectionMethod { get; set; } =
TargetSelectionMethod.Both;
- public float TargetSelectAngleRange { get; set; } = 5f;
+ public double TargetSelectAngleRange { get; set; } = 5d;
public bool TargetLock { get; set; }
public PluginAttackHeight AttackHeight { get; set; } =
PluginAttackHeight.Medium;
@@ -101,15 +106,15 @@ internal sealed class CombatSettings
public double DebuffPrecastSeconds { get; set; } = 5d;
public bool SwitchWandsToDebuff { get; set; }
public UseArcsMode UseArcs { get; set; } = UseArcsMode.AtRange;
- public float SpellRangeFudge { get; set; } = 1f;
+ public double SpellRangeFudge { get; set; } = 1d;
public bool UseBreakableTurnTo { get; set; } = true;
public bool UseProjectileAwareness { get; set; } = true;
- public float CollisionProjectileRadius { get; set; } = 0.4f;
- public float CollisionStepDistance { get; set; } = 0.7f;
+ public double CollisionProjectileRadius { get; set; } = 0.4d;
+ public double CollisionStepDistance { get; set; } = 0.7d;
public bool ShowCollisionDebug { get; set; }
public int MaximumCollisionChecksPerTick { get; set; } = 500;
- public float ArcRange { get; set; } = 5f;
- public float RingDistance { get; set; } = 5f;
+ public double ArcRange { get; set; } = 5d;
+ public double RingDistance { get; set; } = 5d;
public int MinimumRingTargets { get; set; } = 4;
public bool DeleteGhostMonsters { get; set; } = true;
public int GhostMonsterSpellAttemptCount { get; set; } = 200;
@@ -119,7 +124,7 @@ internal sealed class CombatSettings
public double GhostDeleteHealthTrackerSeconds { get; set; } = 30d;
public bool SummonPets { get; set; } = true;
public PetRangeMode PetRangeMode { get; set; } = PetRangeMode.AttackDistance;
- public float PetCustomRange { get; set; } = 5f;
+ public double PetCustomRange { get; set; } = 5d;
public int PetMonsterDensity { get; set; } = 1;
public int PetRefillCountIdle { get; set; } = 3;
public int PetRefillCountNormal { get; set; } = 1;
diff --git a/src/AcDream.Plugins.MossTank/Looting.cs b/src/AcDream.Plugins.MossTank/Looting.cs
index 5474be545..36016d046 100644
--- a/src/AcDream.Plugins.MossTank/Looting.cs
+++ b/src/AcDream.Plugins.MossTank/Looting.cs
@@ -89,8 +89,10 @@ internal sealed class LootSettings
public bool CombineSalvage { get; set; } = true;
public int ManaStoneLootCount { get; set; } = 4;
public int ManaTankMinimumMana { get; set; } = 1000;
- public float CorpseApproachRange { get; set; } = 40f;
- public float CorpseMinimumApproachRange { get; set; } = 3.36f;
+ // Declared tDouble in VTank's own Settings table (item I, slice-1 fix
+ // round) — see CombatSettings.MaximumRange's doc comment for why.
+ public double CorpseApproachRange { get; set; } = 40d;
+ public double CorpseMinimumApproachRange { get; set; } = 3.36d;
public double CorpseOpenTimeoutSeconds { get; set; } = 1.5d;
public double CorpseItemAppearanceTimeoutSeconds { get; set; } = 6d;
public double CorpseItemIdentifyTimeoutSeconds { get; set; } = 60d;
@@ -524,7 +526,7 @@ internal sealed class LootController
_scanRemaining = Math.Clamp(_settings.ScanIntervalSeconds, 0.05d, 5d);
IReadOnlyList corpses = loot.CaptureCorpses(
- Math.Clamp(_settings.CorpseApproachRange, 2f, 100f));
+ (float)Math.Clamp(_settings.CorpseApproachRange, 2d, 100d));
PruneCorpseCache();
foreach (PluginLootContainer seen in corpses)
_corpseFirstSeen.TryAdd(seen.ObjectId, _lifetime);
diff --git a/src/AcDream.Plugins.MossTank/MossTankCommands.cs b/src/AcDream.Plugins.MossTank/MossTankCommands.cs
index 08a47647f..8dedbe613 100644
--- a/src/AcDream.Plugins.MossTank/MossTankCommands.cs
+++ b/src/AcDream.Plugins.MossTank/MossTankCommands.cs
@@ -754,9 +754,9 @@ internal sealed partial class MossTankPanel
private void TestPet()
{
IReadOnlyList targets = _host.Automation.Combat
- .CaptureHostileTargets(_combatSettings.PetRangeMode == PetRangeMode.Custom
+ .CaptureHostileTargets((float)(_combatSettings.PetRangeMode == PetRangeMode.Custom
? _combatSettings.PetCustomRange
- : _combatSettings.MaximumRange);
+ : _combatSettings.MaximumRange));
PetAutomationChoice choice = PetAutomation.Select(
_host.Automation.Items.CaptureOwnedItems(),
targets,
diff --git a/src/AcDream.Plugins.MossTank/MossTankProfileStore.cs b/src/AcDream.Plugins.MossTank/MossTankProfileStore.cs
index 01dd5a764..a68b98677 100644
--- a/src/AcDream.Plugins.MossTank/MossTankProfileStore.cs
+++ b/src/AcDream.Plugins.MossTank/MossTankProfileStore.cs
@@ -430,7 +430,7 @@ internal sealed class MossTankProfileStore
public bool CombineSalvage { get; set; } = true;
public int ManaStoneLootCount { get; set; } = 4;
public int ManaTankMinimumMana { get; set; } = 1000;
- public float CorpseApproachRange { get; set; } = 40f;
+ public double CorpseApproachRange { get; set; } = 40d;
public double CorpseOpenTimeoutSeconds { get; set; } = 1.5d;
public int BlacklistCorpseOpenAttemptCount { get; set; } = 30;
public double BlacklistCorpseOpenTimeoutSeconds { get; set; } = 200d;
@@ -597,12 +597,12 @@ internal sealed class MossTankProfileStore
private sealed class CombatProfileDocument
{
public bool Enabled { get; set; } = true;
- public float MaximumRange { get; set; } = 5f;
- public float ApproachDistance { get; set; }
+ public double MaximumRange { get; set; } = 5d;
+ public double ApproachDistance { get; set; }
public bool IdlePeaceMode { get; set; }
public TargetSelectionMethod SelectionMethod { get; set; } =
TargetSelectionMethod.Both;
- public float TargetSelectAngleRange { get; set; } = 5f;
+ public double TargetSelectAngleRange { get; set; } = 5d;
public bool TargetLock { get; set; }
public PluginAttackHeight AttackHeight { get; set; } =
PluginAttackHeight.Medium;
@@ -616,8 +616,8 @@ internal sealed class MossTankProfileStore
public double DebuffPrecastSeconds { get; set; } = 5d;
public bool SwitchWandsToDebuff { get; set; }
public UseArcsMode UseArcs { get; set; } = UseArcsMode.AtRange;
- public float ArcRange { get; set; } = 5f;
- public float RingDistance { get; set; } = 5f;
+ public double ArcRange { get; set; } = 5d;
+ public double RingDistance { get; set; } = 5d;
public int MinimumRingTargets { get; set; } = 4;
public bool DeleteGhostMonsters { get; set; } = true;
public int GhostMonsterSpellAttemptCount { get; set; } = 200;
@@ -627,7 +627,7 @@ internal sealed class MossTankProfileStore
public double GhostDeleteHealthTrackerSeconds { get; set; } = 30d;
public bool SummonPets { get; set; } = true;
public PetRangeMode PetRangeMode { get; set; } = PetRangeMode.AttackDistance;
- public float PetCustomRange { get; set; } = 5f;
+ public double PetCustomRange { get; set; } = 5d;
public int PetMonsterDensity { get; set; } = 1;
public int PetRefillCountIdle { get; set; } = 3;
public int PetRefillCountNormal { get; set; } = 1;
diff --git a/src/AcDream.Plugins.MossTank/PetAutomation.cs b/src/AcDream.Plugins.MossTank/PetAutomation.cs
index b70c5b42b..d710247e6 100644
--- a/src/AcDream.Plugins.MossTank/PetAutomation.cs
+++ b/src/AcDream.Plugins.MossTank/PetAutomation.cs
@@ -117,9 +117,9 @@ internal sealed class PetAutomation
if (!settings.SummonPets || activeOwnedPetCount > 0)
return PetAutomationChoice.None;
- float range = settings.PetRangeMode == PetRangeMode.Custom
+ float range = (float)(settings.PetRangeMode == PetRangeMode.Custom
? settings.PetCustomRange
- : settings.MaximumRange;
+ : settings.MaximumRange);
int density = Math.Max(1, settings.PetMonsterDensity);
var eligible = new List<(PluginCombatTarget Target, ResolvedMonsterRule Rule)>();
foreach (PluginCombatTarget target in targets)
diff --git a/src/AcDream.Plugins.MossTank/VitalPlan.cs b/src/AcDream.Plugins.MossTank/VitalPlan.cs
index 0b0f5d51d..d1bb3101f 100644
--- a/src/AcDream.Plugins.MossTank/VitalPlan.cs
+++ b/src/AcDream.Plugins.MossTank/VitalPlan.cs
@@ -35,9 +35,11 @@ public sealed class VitalSettings
public double HelperHealth { get; set; } = 0.20;
public double HelperStamina { get; set; } = 0.01;
public double HelperMana { get; set; } = 0.01;
- public float HelperHealthDistance { get; set; } = 59.6f;
- public float HelperStaminaDistance { get; set; } = 59.6f;
- public float HelperManaDistance { get; set; } = 32f;
+ // Declared tDouble in VTank's own Settings table (item I, slice-1 fix
+ // round) — see CombatSettings.MaximumRange's doc comment for why.
+ public double HelperHealthDistance { get; set; } = 59.6d;
+ public double HelperStaminaDistance { get; set; } = 59.6d;
+ public double HelperManaDistance { get; set; } = 32d;
public bool HelpOthers { get; set; } = true;
public bool UseHealersHeart { get; set; } = true;
diff --git a/src/AcDream.Plugins.MossTank/VitalRecharge.cs b/src/AcDream.Plugins.MossTank/VitalRecharge.cs
index ae863a71c..b87d3cca7 100644
--- a/src/AcDream.Plugins.MossTank/VitalRecharge.cs
+++ b/src/AcDream.Plugins.MossTank/VitalRecharge.cs
@@ -147,7 +147,7 @@ internal static class VitalRechargePlanner
IReadOnlyList members =
automation.Fellowship.CaptureMembers();
- foreach ((VitalKind vital, double threshold, float distance, uint baseSpell)
+ foreach ((VitalKind vital, double threshold, double distance, uint baseSpell)
in new[]
{
(VitalKind.Health, settings.HelperHealth,
@@ -158,7 +158,7 @@ internal static class VitalRechargePlanner
settings.HelperManaDistance, (uint)SpellId.GiftOfEssence),
})
{
- PluginFellowMember? target = Lowest(members, vital, threshold, distance);
+ PluginFellowMember? target = Lowest(members, vital, threshold, (float)distance);
if (target is not { } fellow)
continue;
if (vital == VitalKind.Health
diff --git a/src/AcDream.Plugins.MossTank/VtankSettingsProfileSerializer.cs b/src/AcDream.Plugins.MossTank/VtankSettingsProfileSerializer.cs
index 4e3df1b3b..584447dbe 100644
--- a/src/AcDream.Plugins.MossTank/VtankSettingsProfileSerializer.cs
+++ b/src/AcDream.Plugins.MossTank/VtankSettingsProfileSerializer.cs
@@ -147,17 +147,17 @@ internal static class VtankSettingsProfileSerializer
};
///
- /// Exact compare on the cell's own type — a tag
- /// mismatch is now a real bug (fixed above), not something to paper
- /// over with a tolerance. The one narrow exception: several distance
- /// settings (AttackDistance, ArcRange, …) are declared tDouble
- /// but this port still stores their live value in a float field
- /// (CombatSettings.MaximumRange etc.) for the physics/combat math that
- /// consumes them; round-tripping through that float costs precision
- /// below the 15th significant digit, which is exactly float's ~7-digit
- /// guarantee, not a real value change. That residual is bounded by
- /// , not an arbitrary fudge factor.
- /// Every other tag (bool/int/enum/string/uint) compares byte-exact.
+ /// Exact compare on the cell's own type, with no tolerance anywhere —
+ /// a tag mismatch is a real bug, not something to
+ /// paper over. Item I (slice-1 fix round) removed the one narrow
+ /// exception this used to carry: every tDouble-declared distance
+ /// setting (AttackDistance, ArcRange, …) that used to round-trip
+ /// through a float-typed live field (CombatSettings.MaximumRange
+ /// etc., costing precision below float's ~7-digit guarantee) now stores
+ /// that value as a real double — the field widened to match its
+ /// declared type instead of the compare being loosened to match the
+ /// field. Physics/combat consumers that want a float cast it at
+ /// their own use site.
///
private static bool ValuesEqual(VtankCell a, VtankCell b)
{
@@ -169,23 +169,11 @@ internal static class VtankSettingsProfileSerializer
"s" => a.AsString() == b.AsString(),
"i" => a.AsInt() == b.AsInt(),
"u" => a.AsUInt() == b.AsUInt(),
- "d" or "f" => DoublesEqual(a.AsDouble(), b.AsDouble()),
+ "d" or "f" => a.AsDouble() == b.AsDouble(),
_ => ReferenceEquals(a, b),
};
}
- private static bool DoublesEqual(double left, double right)
- {
- double tolerance = Math.Max(Math.Abs(left), Math.Abs(right)) * FloatRoundTripTolerance;
- return Math.Abs(left - right) <= tolerance;
- }
-
- // ~1.2e-7 is float's relative machine epsilon (2^-23); a value that has
- // been through one float round trip (Apply's (float)(... * 240d) cast)
- // can differ from its original double by up to this much and still be
- // the SAME value VTank wrote.
- private const double FloatRoundTripTolerance = 1.5e-7;
-
// ------------------------------------------------------------------
// Apply: .usd cell -> live setting.
// ------------------------------------------------------------------
@@ -219,18 +207,18 @@ internal static class VtankSettingsProfileSerializer
case "recharge-helper-stam": v.HelperStamina = cell.AsDouble() / 100d; break;
case "recharge-helper-mana": v.HelperMana = cell.AsDouble() / 100d; break;
case "dohelp": v.HelpOthers = cell.AsBool(); break;
- case "attackdistance": c.MaximumRange = (float)(cell.AsDouble() * 240d); break;
- case "attackminimumdistance": c.MinimumRange = (float)(cell.AsDouble() * 240d); break;
- case "approachdistance": c.ApproachDistance = (float)(cell.AsDouble() * 240d); break;
- case "ringdistance": c.RingDistance = (float)(cell.AsDouble() * 240d); break;
- case "corpseapproachrange-max": i.Loot.CorpseApproachRange = (float)(cell.AsDouble() * 240d); break;
- case "corpseapproachrange-min": i.Loot.CorpseMinimumApproachRange = (float)(cell.AsDouble() * 240d); break;
+ case "attackdistance": c.MaximumRange = cell.AsDouble() * 240d; break;
+ case "attackminimumdistance": c.MinimumRange = cell.AsDouble() * 240d; break;
+ case "approachdistance": c.ApproachDistance = cell.AsDouble() * 240d; break;
+ case "ringdistance": c.RingDistance = cell.AsDouble() * 240d; break;
+ case "corpseapproachrange-max": i.Loot.CorpseApproachRange = cell.AsDouble() * 240d; break;
+ case "corpseapproachrange-min": i.Loot.CorpseMinimumApproachRange = cell.AsDouble() * 240d; break;
case "navclosestoprange": n.MinimumDistanceMeters = cell.AsDouble() * 240d; break;
case "navfarstoprange": n.MaximumDistanceMeters = cell.AsDouble() * 240d; break;
case "useportaldistance": n.PortalUseDistanceMeters = cell.AsDouble() * 240d; break;
- case "helperdistancehitp": v.HelperHealthDistance = (float)(cell.AsDouble() * 240d); break;
- case "helperdistancestam": v.HelperStaminaDistance = (float)(cell.AsDouble() * 240d); break;
- case "helperdistancemana": v.HelperManaDistance = (float)(cell.AsDouble() * 240d); break;
+ case "helperdistancehitp": v.HelperHealthDistance = cell.AsDouble() * 240d; break;
+ case "helperdistancestam": v.HelperStaminaDistance = cell.AsDouble() * 240d; break;
+ case "helperdistancemana": v.HelperManaDistance = cell.AsDouble() * 240d; break;
case "minimumringtargets": c.MinimumRingTargets = cell.AsInt(); break;
case "defaultmeleeattackheight": c.AttackHeight = (PluginAttackHeight)cell.AsInt(); break;
case "castdispelself": v.CastDispelSelf = cell.AsBool(); break;
@@ -252,9 +240,9 @@ internal static class VtankSettingsProfileSerializer
case "targetlock": c.TargetLock = cell.AsBool(); break;
case "stopmacroondeath": c.StopMacroOnDeath = cell.AsBool(); break;
case "usearcs": c.UseArcs = (UseArcsMode)cell.AsInt(); break;
- case "arcrange": c.ArcRange = (float)(cell.AsDouble() * 240d); break;
+ case "arcrange": c.ArcRange = cell.AsDouble() * 240d; break;
case "targetselectmethod": c.SelectionMethod = (TargetSelectionMethod)(cell.AsInt() - 1); break;
- case "targetselectanglerange": c.TargetSelectAngleRange = (float)(cell.AsDouble() * 240d); break;
+ case "targetselectanglerange": c.TargetSelectAngleRange = cell.AsDouble() * 240d; break;
case "idlebufftopoff": b.IdleBuffTopoff = cell.AsBool(); break;
case "idlebufftopofftimeseconds": b.IdleBuffTopoffSeconds = cell.AsDouble(); break;
case "rebufftimeremainingseconds": b.RebuffWhenUnderSeconds = cell.AsDouble(); break;
@@ -323,7 +311,7 @@ internal static class VtankSettingsProfileSerializer
case "blacklistcorpseopentimeoutseconds": i.Loot.BlacklistCorpseOpenTimeoutSeconds = cell.AsDouble(); break;
case "summonpets": c.SummonPets = cell.AsBool(); break;
case "petrangemode": c.PetRangeMode = (PetRangeMode)cell.AsInt(); break;
- case "petcustomrange": c.PetCustomRange = (float)(cell.AsDouble() * 240d); break;
+ case "petcustomrange": c.PetCustomRange = cell.AsDouble() * 240d; break;
case "petrefillcount-idle": c.PetRefillCountIdle = cell.AsInt(); break;
case "petrefillcount-normal": c.PetRefillCountNormal = cell.AsInt(); break;
case "corpseopentimeoutseconds": i.Loot.CorpseOpenTimeoutSeconds = cell.AsDouble(); break;
@@ -332,11 +320,11 @@ internal static class VtankSettingsProfileSerializer
case "fastcastbuffs": b.FastCastBuffs = cell.AsBool(); break;
case "usebreakableturnto": c.UseBreakableTurnTo = cell.AsBool(); break;
case "useprojectileawareness": c.UseProjectileAwareness = cell.AsBool(); break;
- case "collisionprojectileradius": c.CollisionProjectileRadius = cell.AsFloat(); break;
- case "collisionstepdistance": c.CollisionStepDistance = cell.AsFloat(); break;
+ case "collisionprojectileradius": c.CollisionProjectileRadius = cell.AsDouble(); break;
+ case "collisionstepdistance": c.CollisionStepDistance = cell.AsDouble(); break;
case "showcollisiondebug": c.ShowCollisionDebug = cell.AsBool(); break;
case "maximumcollisioncheckspertick": c.MaximumCollisionChecksPerTick = cell.AsInt(); break;
- case "spellrangefudge": c.SpellRangeFudge = cell.AsFloat(); break;
+ case "spellrangefudge": c.SpellRangeFudge = cell.AsDouble(); break;
case "buffwithuntrained-item": b.BuffWithUntrainedItemSkill = cell.AsInt(); break;
case "buffwithuntrained-creature": b.BuffWithUntrainedCreatureSkill = cell.AsInt(); break;
case "buffwithuntrained-life": b.BuffWithUntrainedLifeSkill = cell.AsInt(); break;
diff --git a/tests/AcDream.Plugins.MossTank.Tests/VtankSettingsProfileSerializerTests.cs b/tests/AcDream.Plugins.MossTank.Tests/VtankSettingsProfileSerializerTests.cs
index 87d93c686..abe1ff1ce 100644
--- a/tests/AcDream.Plugins.MossTank.Tests/VtankSettingsProfileSerializerTests.cs
+++ b/tests/AcDream.Plugins.MossTank.Tests/VtankSettingsProfileSerializerTests.cs
@@ -170,19 +170,14 @@ public sealed class VtankSettingsProfileSerializerTests
break;
case "d":
case "f":
- // A handful of distance settings are declared tDouble but
- // still round-trip through a CombatSettings `float` field
- // for the physics math that consumes them (Apply's
- // `(float)(cell.AsDouble() * 240d)`); that costs precision
- // below float's ~7-significant-digit guarantee, not a real
- // value change. Bounded exactly like
+ // Item I (slice-1 fix round): every tDouble-declared
+ // distance setting's live field was widened from float to
+ // double (CombatSettings.MaximumRange etc.), so this is now
+ // a real exact compare — no tolerance, matching
// VtankSettingsProfileSerializer's own ValuesEqual.
- double expected = fixtureCell.AsDouble();
- double actual = captured.AsDouble();
- double tolerance = Math.Max(Math.Abs(expected), Math.Abs(actual)) * 1.5e-7;
- Assert.True(
- Math.Abs(expected - actual) <= tolerance,
- $"{name}: fixture={expected} captured={actual}");
+ Assert.Equal(
+ fixtureCell.AsDouble(),
+ captured.AsDouble());
break;
default:
Assert.Fail($"{name}: unexpected tag '{fixtureCell.Tag}'.");