diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index b693bbe53..7150282c1 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Assets/Scripts/ServerShared/Entity.cs b/Assets/Scripts/ServerShared/Entity.cs index 952663500..4744bf3b7 100644 --- a/Assets/Scripts/ServerShared/Entity.cs +++ b/Assets/Scripts/ServerShared/Entity.cs @@ -1271,6 +1271,7 @@ public class EquippedItem public Shape InsetShape { get; } public Entity Entity { get; } public EquippableItemData Data { get; } + public AetheriaRuntimeCatalogItem RuntimeItem { get; } public ReactiveProperty ThermalOnline { get; } = new ReactiveProperty(false); public ReactiveProperty DurabilityOnline { get; } = new ReactiveProperty(false); @@ -1283,6 +1284,7 @@ public class EquippedItem public Subject<(uint id, float v)> AudioParameters { get; } = new Subject<(uint id, float v)>(); public Dictionary AudioParameterValues { get; } = new Dictionary(); + private readonly BezierCurve _thermalPerformanceCurve; private float oldTemperature; public float Temperature { @@ -1372,9 +1374,10 @@ public class EquippedItem Entity = entity; EquippableItem = item; Position = position; - var typedItem = ItemManager.GetRuntimeItem(item); - Conductivity = typedItem != null ? (float)typedItem.Conductivity : Data.Conductivity; - MaxDurability = typedItem?.Durability > 0 ? (float)typedItem.Durability : Math.Max(item.Durability, 1f); + RuntimeItem = ItemManager.GetRuntimeItem(item); + Conductivity = RuntimeItem != null ? (float)RuntimeItem.Conductivity : Data.Conductivity; + MaxDurability = RuntimeItem?.Durability > 0 ? (float)RuntimeItem.Durability : Math.Max(item.Durability, 1f); + _thermalPerformanceCurve = CreateThermalPerformanceCurve(RuntimeItem); ThermalExponent = lerp( ItemManager.GameplaySettings.ThermalQualityMin, ItemManager.GameplaySettings.ThermalQualityMax, @@ -1443,7 +1446,7 @@ public class EquippedItem public void UpdatePerformance() { var temp = Temperature; - ThermalPerformance = Data.Performance(temp); + ThermalPerformance = EvaluateThermalPerformance(temp); var deltaTemp = math.abs(temp - oldTemperature); DurabilityPerformance = EquippableItem.Durability / MaxDurability; var performanceThreshold = Entity.Settings.ShutdownPerformance; @@ -1457,6 +1460,34 @@ public class EquippedItem oldTemperature = temp; } + private float EvaluateThermalPerformance(float temperature) + { + if (_thermalPerformanceCurve == null || + RuntimeItem == null || + RuntimeItem.MaximumTemperature <= RuntimeItem.MinimumTemperature) + { + return Data.Performance(temperature); + } + + var t = unlerp((float)RuntimeItem.MinimumTemperature, (float)RuntimeItem.MaximumTemperature, temperature); + return saturate(_thermalPerformanceCurve.Evaluate(t)); + } + + private static BezierCurve CreateThermalPerformanceCurve(AetheriaRuntimeCatalogItem item) + { + if (item?.ThermalPerformanceCurveKeys == null || item.ThermalPerformanceCurveKeys.Count == 0) + { + return null; + } + + return new BezierCurve + { + Keys = item.ThermalPerformanceCurveKeys + .Select(key => new float4((float)key.Time, (float)key.Value, (float)key.InTangent, (float)key.OutTangent)) + .ToArray() + }; + } + public void Update(float delta) { foreach (var audioStat in Data.AudioStats) diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index e493cac82..1eb82f879 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -233,10 +233,11 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. performance stats. `ItemManager.CreateInstance(CraftedItemData, float)` initializes new equippable runtime durability from the typed runtime item row; `EquippableItemData.Durability` is only an incomplete-row fallback. - `EquippedItem` also takes conductivity and max durability from the typed - runtime item row for heat conduction, durability performance, and wear - scaling; the legacy equippable DTO still owns thermal curve, thermal - resilience, and audio-stat residue until those surfaces are imported. + `EquippedItem` also takes conductivity, max durability, thermal bounds, and + thermal performance curve keys from the typed runtime item row for heat + conduction, durability performance, thermal performance, and wear scaling; + the legacy equippable DTO still owns thermal resilience and audio-stat + residue until those surfaces are imported. Inventory drag preview occupancy now projects typed item shape cells only into the local `Shape` grid, and final fit/equip acceptance shares the typed runtime catalog geometry path in `Entity.ItemFits` and `TryEquip`. @@ -774,6 +775,9 @@ First Aetheria surfaces to publish: - Done: move `EquippedItem` conductivity and max-durability performance/wear inputs onto typed runtime item rows; legacy thermal resilience and audio stats remain explicit DTO residues. + - Done: move `EquippedItem` thermal performance evaluation onto typed + runtime item thermal bounds and curve keys; legacy `Data.Performance` + remains only as an incomplete-row fallback. - Done: import action-bar icon resource paths into typed item definitions and move `ActionBarSlot` custom gear icon lookup onto typed runtime item rows. - Done: move `Ship` drag, combat/turret predicted shot height, and thruster