diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 64ef91817..76797bcee 100644 Binary files a/.voidbot/state/aetheria.cc and b/.voidbot/state/aetheria.cc differ diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index fea7f376f..b112f4eb9 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -97,7 +97,9 @@ simulation APIs still accept DTO objects. The old `RuntimeCatalogEntry` and `RuntimeItemProjectionEntry` base names no longer appear in live source. The dead `InspectableRuntimeCatalogLinkAttribute` inspector metadata is gone; typed runtime projection still uses `LegacyPayloadKeyAttribute` only where the -temporary mapper needs a deterministic field order. +catalog importer and migration readers need deterministic field layout. Runtime +behavior config construction uses an explicit typed behavior-kind mapper instead +of reflecting over that metadata. Agent task DTOs no longer inherit the item projection base; they are local runtime AI work orders until a typed Verse task document exists. @@ -129,7 +131,7 @@ behavior-private simulation state remain legacy until dedicated typed runtime documents exist. Live behavior instances expose typed behavior kind and group through `Behavior.Kind` and `Behavior.Group`; the remaining `RuntimeBehaviorConfig` objects are construction configs, not runtime identity -owners. +owners. `ItemManager` currently owns the explicit typed-payload-to-config bridge. Typed name-file documents are `aetheria.name_file.v2` records. They carry the legacy ID, display name, count, compact sample names for surfaces, and the full name array needed to move `Galaxy`/Markov name generation off legacy diff --git a/Assets/Scripts/ServerShared/ItemManager.cs b/Assets/Scripts/ServerShared/ItemManager.cs index 155ab3873..e93188204 100644 --- a/Assets/Scripts/ServerShared/ItemManager.cs +++ b/Assets/Scripts/ServerShared/ItemManager.cs @@ -3,10 +3,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Reflection; using GameCult.Aetheria.State.Unity; using Unity.Mathematics; using static Unity.Mathematics.math; @@ -31,40 +29,6 @@ public class ItemManager // private Guid _forceLoadZone; private readonly IRuntimeItemCatalogReader _runtimeItems; - private static readonly IReadOnlyDictionary BehaviorTypesByKind = new Dictionary(StringComparer.Ordinal) - { - { "GuidedWeapon", typeof(GuidedWeaponConfig) }, - { "Launcher", typeof(LauncherConfig) }, - { "Reactor", typeof(ReactorConfig) }, - { "Radiator", typeof(RadiatorConfig) }, - { "StatModifier", typeof(StatModifierConfig) }, - { "Sensor", typeof(SensorConfig) }, - { "Reflector", typeof(ReflectorConfig) }, - { "Shield", typeof(ShieldConfig) }, - { "Thruster", typeof(ThrusterConfig) }, - { "Wear", typeof(WearConfig) }, - { "VelocityConversion", typeof(VelocityConversionConfig) }, - { "VelocityLimit", typeof(VelocityLimitConfig) }, - { "AetherDrive", typeof(AetherDriveConfig) }, - { "Cooldown", typeof(CooldownConfig) }, - { "Heat", typeof(HeatConfig) }, - { "ItemUsage", typeof(ItemUsageConfig) }, - { "Switch", typeof(SwitchConfig) }, - { "Trigger", typeof(TriggerConfig) }, - { "Visibility", typeof(VisibilityConfig) }, - { "Thermotoggle", typeof(ThermotoggleConfig) }, - { "EnergyDraw", typeof(EnergyDrawConfig) }, - { "MiningTool", typeof(MiningToolConfig) }, - { "ResourceScanner", typeof(ResourceScannerConfig) }, - { "Capacitor", typeof(CapacitorConfig) }, - { "Cockpit", typeof(CockpitConfig) }, - { "HeatStorage", typeof(HeatStorageConfig) }, - { "TurretController", typeof(TurretControllerConfig) }, - { "InstantWeapon", typeof(InstantWeaponConfig) }, - { "ConstantWeapon", typeof(ConstantWeaponConfig) }, - { "ChargedWeapon", typeof(ChargedWeaponConfig) }, - { "AutoWeapon", typeof(AutoWeaponConfig) } - }; public GameplaySettings GameplaySettings { get; } @@ -128,158 +92,427 @@ public class ItemManager private static RuntimeBehaviorConfig BuildBehaviorConfig(AetheriaRuntimeBehaviorPayload payload) { - if (!BehaviorTypesByKind.TryGetValue(payload.Kind, out var behaviorType)) + switch (payload.Kind) { - return null; + case "AetherDrive": return Configure(new AetherDriveConfig(), payload, ApplyAetherDriveConfig); + case "AutoWeapon": return Configure(new AutoWeaponConfig(), payload, ApplyInstantWeaponConfig); + case "Capacitor": return Configure(new CapacitorConfig(), payload, ApplyCapacitorConfig); + case "ChargedWeapon": return Configure(new ChargedWeaponConfig(), payload, ApplyChargedWeaponConfig); + case "Cockpit": return Configure(new CockpitConfig(), payload, null); + case "ConstantWeapon": return Configure(new ConstantWeaponConfig(), payload, ApplyConstantWeaponConfig); + case "Cooldown": return Configure(new CooldownConfig(), payload, ApplyCooldownConfig); + case "EnergyDraw": return Configure(new EnergyDrawConfig(), payload, ApplyEnergyDrawConfig); + case "GuidedWeapon": return Configure(new GuidedWeaponConfig(), payload, ApplyGuidedWeaponConfig); + case "Heat": return Configure(new HeatConfig(), payload, ApplyHeatConfig); + case "HeatStorage": return Configure(new HeatStorageConfig(), payload, null); + case "InstantWeapon": return Configure(new InstantWeaponConfig(), payload, ApplyInstantWeaponConfig); + case "ItemUsage": return Configure(new ItemUsageConfig(), payload, ApplyItemUsageConfig); + case "Launcher": return Configure(new LauncherConfig(), payload, ApplyLauncherConfig); + case "MiningTool": return Configure(new MiningToolConfig(), payload, ApplyMiningToolConfig); + case "Radiator": return Configure(new RadiatorConfig(), payload, ApplyRadiatorConfig); + case "Reactor": return Configure(new ReactorConfig(), payload, ApplyReactorConfig); + case "Reflector": return Configure(new ReflectorConfig(), payload, ApplyReflectorConfig); + case "ResourceScanner": return Configure(new ResourceScannerConfig(), payload, ApplyResourceScannerConfig); + case "Sensor": return Configure(new SensorConfig(), payload, ApplySensorConfig); + case "Shield": return Configure(new ShieldConfig(), payload, ApplyShieldConfig); + case "StatModifier": return Configure(new StatModifierConfig(), payload, ApplyStatModifierConfig); + case "Switch": return Configure(new SwitchConfig(), payload, null); + case "Thermotoggle": return Configure(new ThermotoggleConfig(), payload, ApplyThermotoggleConfig); + case "Thruster": return Configure(new ThrusterConfig(), payload, ApplyThrusterConfig); + case "Trigger": return Configure(new TriggerConfig(), payload, null); + case "TurretController": return Configure(new TurretControllerConfig(), payload, null); + case "VelocityConversion": return Configure(new VelocityConversionConfig(), payload, ApplyVelocityConversionConfig); + case "VelocityLimit": return Configure(new VelocityLimitConfig(), payload, ApplyVelocityLimitConfig); + case "Visibility": return Configure(new VisibilityConfig(), payload, ApplyVisibilityConfig); + case "Wear": return Configure(new WearConfig(), payload, ApplyWearConfig); + default: return null; } + } - var behavior = Activator.CreateInstance(behaviorType) as RuntimeBehaviorConfig; - if (behavior == null) + private static T Configure(T config, AetheriaRuntimeBehaviorPayload payload, Action apply) + where T : RuntimeBehaviorConfig + { + var reader = new BehaviorPayloadReader(payload); + config.Kind = payload.Kind; + config.Group = payload.Group; + apply?.Invoke(config, reader); + return config; + } + + private static void ApplyAetherDriveConfig(AetherDriveConfig config, BehaviorPayloadReader reader) + { + config.RotorDiameter = reader.Float3(1, config.RotorDiameter); + config.RotorMass = reader.Float3(2, config.RotorMass); + config.MaximumRpm = reader.PerformanceStat(3, config.MaximumRpm); + config.CouplingLambda = reader.Float3(4, config.CouplingLambda); + config.LambdaMultiplier = reader.PerformanceStat(5, config.LambdaMultiplier); + config.CouplingEfficiency = reader.PerformanceStat(6, config.CouplingEfficiency); + config.Torque = reader.PerformanceStat(7, config.Torque); + config.TorqueProfile = reader.BezierCurve(8, config.TorqueProfile); + config.EnergyDraw = reader.PerformanceStat(9, config.EnergyDraw); + config.PassiveCoupling = reader.PerformanceStat(10, config.PassiveCoupling); + config.RpmAudioParameter = reader.UInt(11, config.RpmAudioParameter); + config.TorqueRatioAudioParameter = reader.UInt(12, config.TorqueRatioAudioParameter); + config.Particles = reader.String(13, config.Particles); + } + + private static void ApplyCapacitorConfig(CapacitorConfig config, BehaviorPayloadReader reader) + { + config.Capacity = reader.PerformanceStat(1, config.Capacity); + config.Efficiency = reader.PerformanceStat(2, config.Efficiency); + } + + private static void ApplyCooldownConfig(CooldownConfig config, BehaviorPayloadReader reader) + { + config.Cooldown = reader.PerformanceStat(1, config.Cooldown); + } + + private static void ApplyEnergyDrawConfig(EnergyDrawConfig config, BehaviorPayloadReader reader) + { + config.EnergyDraw = reader.PerformanceStat(1, config.EnergyDraw); + config.PerSecond = reader.Bool(2, config.PerSecond); + } + + private static void ApplyHeatConfig(HeatConfig config, BehaviorPayloadReader reader) + { + config.Heat = reader.PerformanceStat(1, config.Heat); + config.PerSecond = reader.Bool(2, config.PerSecond); + } + + private static void ApplyItemUsageConfig(ItemUsageConfig config, BehaviorPayloadReader reader) + { + config.Item = reader.Guid(1, config.Item); + } + + private static void ApplyMiningToolConfig(MiningToolConfig config, BehaviorPayloadReader reader) + { + config.DamagePerSecond = reader.PerformanceStat(1, config.DamagePerSecond); + config.Efficiency = reader.PerformanceStat(2, config.Efficiency); + config.Penetration = reader.PerformanceStat(3, config.Penetration); + config.Range = reader.PerformanceStat(4, config.Range); + } + + private static void ApplyRadiatorConfig(RadiatorConfig config, BehaviorPayloadReader reader) + { + config.Emissivity = reader.PerformanceStat(1, config.Emissivity); + config.PumpedHeat = reader.PerformanceStat(2, config.PumpedHeat); + config.TemperatureFloor = reader.Float(3, config.TemperatureFloor); + config.WasteHeat = reader.PerformanceStat(4, config.WasteHeat); + config.EnergyUsage = reader.PerformanceStat(5, config.EnergyUsage); + config.ThermalMass = reader.PerformanceStat(6, config.ThermalMass); + } + + private static void ApplyReactorConfig(ReactorConfig config, BehaviorPayloadReader reader) + { + config.Charge = reader.PerformanceStat(1, config.Charge); + config.Efficiency = reader.PerformanceStat(2, config.Efficiency); + config.OverloadEfficiency = reader.PerformanceStat(3, config.OverloadEfficiency); + config.ThrottlingFactor = reader.PerformanceStat(4, config.ThrottlingFactor); + } + + private static void ApplyReflectorConfig(ReflectorConfig config, BehaviorPayloadReader reader) + { + config.CrossSection = reader.PerformanceStat(1, config.CrossSection); + } + + private static void ApplyResourceScannerConfig(ResourceScannerConfig config, BehaviorPayloadReader reader) + { + config.Range = reader.PerformanceStat(1, config.Range); + config.MinimumDensity = reader.PerformanceStat(2, config.MinimumDensity); + config.ScanDuration = reader.PerformanceStat(3, config.ScanDuration); + } + + private static void ApplySensorConfig(SensorConfig config, BehaviorPayloadReader reader) + { + config.Sensitivity = reader.PerformanceStat(3, config.Sensitivity); + config.SensitivityCurve = reader.BezierCurve(4, config.SensitivityCurve); + config.PingBoost = reader.PerformanceStat(5, config.PingBoost); + config.PingEnergy = reader.PerformanceStat(6, config.PingEnergy); + config.PingVisibility = reader.PerformanceStat(7, config.PingVisibility); + config.PingRange = reader.PerformanceStat(8, config.PingRange); + config.PingCooldown = reader.PerformanceStat(9, config.PingCooldown); + config.PingDuration = reader.Float(10, config.PingDuration); + config.PingRadiusExponent = reader.Float(11, config.PingRadiusExponent); + } + + private static void ApplyShieldConfig(ShieldConfig config, BehaviorPayloadReader reader) + { + config.Efficiency = reader.PerformanceStat(1, config.Efficiency); + config.EnergyUsage = reader.PerformanceStat(2, config.EnergyUsage); + } + + private static void ApplyStatModifierConfig(StatModifierConfig config, BehaviorPayloadReader reader) + { + config.Stat = reader.StatReference(1, config.Stat); + config.Modifier = reader.PerformanceStat(2, config.Modifier); + config.Type = reader.Enum(3, config.Type); + config.RequireBehavior = reader.String(4, config.RequireBehavior); + } + + private static void ApplyThermotoggleConfig(ThermotoggleConfig config, BehaviorPayloadReader reader) + { + config.TargetTemperature = reader.Float(1, config.TargetTemperature); + config.HighPass = reader.Bool(2, config.HighPass); + config.Adjustable = reader.Bool(3, config.Adjustable); + } + + private static void ApplyThrusterConfig(ThrusterConfig config, BehaviorPayloadReader reader) + { + config.Thrust = reader.PerformanceStat(1, config.Thrust); + config.Visibility = reader.PerformanceStat(2, config.Visibility); + config.Heat = reader.PerformanceStat(3, config.Heat); + config.EnergyUsage = reader.PerformanceStat(4, config.EnergyUsage); + config.ParticlesPrefab = reader.String(5, config.ParticlesPrefab); + } + + private static void ApplyVelocityConversionConfig(VelocityConversionConfig config, BehaviorPayloadReader reader) + { + config.Lambda = reader.PerformanceStat(1, config.Lambda); + } + + private static void ApplyVelocityLimitConfig(VelocityLimitConfig config, BehaviorPayloadReader reader) + { + config.TopSpeed = reader.PerformanceStat(1, config.TopSpeed); + } + + private static void ApplyVisibilityConfig(VisibilityConfig config, BehaviorPayloadReader reader) + { + config.Visibility = reader.PerformanceStat(1, config.Visibility); + config.VisibilityDecay = reader.PerformanceStat(2, config.VisibilityDecay); + } + + private static void ApplyWearConfig(WearConfig config, BehaviorPayloadReader reader) + { + config.PerSecond = reader.Bool(1, config.PerSecond); + } + + private static void ApplyWeaponConfig(WeaponConfig config, BehaviorPayloadReader reader) + { + config.DamageType = reader.Enum(1, config.DamageType); + config.Damage = reader.PerformanceStat(2, config.Damage); + config.Penetration = reader.PerformanceStat(3, config.Penetration); + config.DamageSpread = reader.PerformanceStat(4, config.DamageSpread); + config.MinRange = reader.PerformanceStat(5, config.MinRange); + config.Range = reader.PerformanceStat(6, config.Range); + config.DamageCurve = reader.BezierCurve(7, config.DamageCurve); + config.EffectPrefab = reader.String(8, config.EffectPrefab); + config.Energy = reader.PerformanceStat(9, config.Energy); + config.Heat = reader.PerformanceStat(10, config.Heat); + config.Visibility = reader.PerformanceStat(11, config.Visibility); + config.AmmoType = reader.Guid(12, config.AmmoType); + config.MagazineSize = reader.Int(13, config.MagazineSize); + config.ReloadTime = reader.Float(14, config.ReloadTime); + config.Spread = reader.PerformanceStat(15, config.Spread); + config.Velocity = reader.PerformanceStat(16, config.Velocity); + } + + private static void ApplyInstantWeaponConfig(InstantWeaponConfig config, BehaviorPayloadReader reader) + { + ApplyWeaponConfig(config, reader); + config.Count = reader.PerformanceStat(17, config.Count); + config.BurstTime = reader.PerformanceStat(18, config.BurstTime); + config.Cooldown = reader.PerformanceStat(19, config.Cooldown); + config.SingleAmmoBurst = reader.Bool(20, config.SingleAmmoBurst); + } + + private static void ApplyConstantWeaponConfig(ConstantWeaponConfig config, BehaviorPayloadReader reader) + { + ApplyWeaponConfig(config, reader); + config.AmmoInterval = reader.Float(17, config.AmmoInterval); + } + + private static void ApplyChargedWeaponConfig(ChargedWeaponConfig config, BehaviorPayloadReader reader) + { + ApplyInstantWeaponConfig(config, reader); + config.ChargeTime = reader.PerformanceStat(21, config.ChargeTime); + config.ChargeEnergy = reader.PerformanceStat(22, config.ChargeEnergy); + config.ChargeHeat = reader.PerformanceStat(23, config.ChargeHeat); + config.CanFireEarly = reader.Bool(24, config.CanFireEarly); + config.FailureCharge = reader.Float(25, config.FailureCharge); + config.FailureDamage = reader.Float(26, config.FailureDamage); + config.ChargeFiringDamageMultiplier = reader.Float(27, config.ChargeFiringDamageMultiplier); + config.ChargeFiringSpreadMultiplier = reader.Float(28, config.ChargeFiringSpreadMultiplier); + config.ChargeFiringBurstCountMultiplier = reader.Float(29, config.ChargeFiringBurstCountMultiplier); + config.ChargeFiringVisibilityMultiplier = reader.Float(30, config.ChargeFiringVisibilityMultiplier); + config.ChargeFiringVelocityMultiplier = reader.Float(31, config.ChargeFiringVelocityMultiplier); + config.ChargeFiringHeatMultiplier = reader.Float(32, config.ChargeFiringHeatMultiplier); + } + + private static void ApplyLockWeaponConfig(LockWeaponConfig config, BehaviorPayloadReader reader) + { + ApplyInstantWeaponConfig(config, reader); + config.LockSpeed = reader.PerformanceStat(21, config.LockSpeed); + config.SensorImpact = reader.PerformanceStat(22, config.SensorImpact); + config.LockAngle = reader.PerformanceStat(23, config.LockAngle); + config.DirectionImpact = reader.PerformanceStat(24, config.DirectionImpact); + config.Decay = reader.PerformanceStat(25, config.Decay); + } + + private static void ApplyLauncherConfig(LauncherConfig config, BehaviorPayloadReader reader) + { + ApplyLockWeaponConfig(config, reader); + config.GuidanceCurve = reader.Float4Array(26, config.GuidanceCurve); + config.ThrustCurve = reader.Float4Array(27, config.ThrustCurve); + config.LiftCurve = reader.Float4Array(28, config.LiftCurve); + config.Thrust = reader.PerformanceStat(29, config.Thrust); + config.DodgeFrequency = reader.Float(30, config.DodgeFrequency); + config.MissileVelocity = reader.PerformanceStat(31, config.MissileVelocity); + } + + private static void ApplyGuidedWeaponConfig(GuidedWeaponConfig config, BehaviorPayloadReader reader) + { + ApplyInstantWeaponConfig(config, reader); + config.GuidanceCurve = reader.Float4Array(21, config.GuidanceCurve); + config.ThrustCurve = reader.Float4Array(22, config.ThrustCurve); + config.LiftCurve = reader.Float4Array(23, config.LiftCurve); + config.Thrust = reader.PerformanceStat(24, config.Thrust); + config.DodgeFrequency = reader.Float(25, config.DodgeFrequency); + config.MissileVelocity = reader.PerformanceStat(26, config.MissileVelocity); + } + + private sealed class BehaviorPayloadReader + { + private readonly IReadOnlyDictionary _fields; + + public BehaviorPayloadReader(AetheriaRuntimeBehaviorPayload payload) { - return null; - } - - behavior.Kind = payload.Kind; - behavior.Group = payload.Group; - - foreach (var field in GetKeyedFields(behaviorType)) - { - var payloadField = payload.Fields.FirstOrDefault(candidate => candidate.Key == field.key); - if (payloadField == null) + var fields = new Dictionary(); + foreach (var field in payload.Fields) { - continue; + if (!fields.ContainsKey(field.Key)) + { + fields.Add(field.Key, field.Value); + } } - field.field.SetValue(behavior, ConvertValue(payloadField.Value, field.field.FieldType)); + _fields = fields; } - return behavior; - } - - private static IEnumerable<(int key, FieldInfo field)> GetKeyedFields(Type type) - { - return type - .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Select(field => (attribute: field.GetCustomAttribute(), field)) - .Where(entry => entry.attribute != null) - .Select(entry => (entry.attribute.Key, entry.field)); - } - - private static object ConvertValue(AetheriaRuntimeBehaviorValue value, Type targetType) - { - if (targetType == typeof(string)) return value.StringValue ?? ""; - if (targetType == typeof(bool)) return value.BoolValue; - if (targetType == typeof(float)) return (float)value.NumberValue; - if (targetType == typeof(double)) return value.NumberValue; - if (targetType == typeof(int)) return checked((int)value.NumberValue); - if (targetType == typeof(uint)) return checked((uint)value.NumberValue); - if (targetType == typeof(Guid)) return ParseGuid(value.LegacyIdValue); - if (targetType == typeof(float2)) return ConvertFloat2(value); - if (targetType == typeof(float3)) return ConvertFloat3(value); - if (targetType == typeof(float4)) return ConvertFloat4(value); - if (targetType == typeof(int2)) return ConvertInt2(value); - if (targetType.IsEnum) return ParseEnumValue(value, targetType); - - if (targetType.IsArray) + public string String(int key, string fallback = "") { - return ConvertArray(value, targetType.GetElementType()); + return _fields.TryGetValue(key, out var value) ? value.StringValue ?? "" : fallback; } - if (typeof(IList).IsAssignableFrom(targetType) && targetType.IsGenericType) + public bool Bool(int key, bool fallback = false) { - return ConvertList(value, targetType); + return _fields.TryGetValue(key, out var value) ? value.BoolValue : fallback; } - if (GetKeyedFields(targetType).Any()) + public float Float(int key, float fallback = 0) { - return ConvertKeyedObject(value, targetType); + return _fields.TryGetValue(key, out var value) ? (float)value.NumberValue : fallback; } - return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; - } - - private static object ConvertArray(AetheriaRuntimeBehaviorValue value, Type elementType) - { - var array = Array.CreateInstance(elementType, value.Children.Count); - for (var index = 0; index < value.Children.Count; index++) + public int Int(int key, int fallback = 0) { - array.SetValue(ConvertValue(value.Children[index], elementType), index); + return _fields.TryGetValue(key, out var value) ? checked((int)value.NumberValue) : fallback; } - return array; - } - - private static object ConvertList(AetheriaRuntimeBehaviorValue value, Type targetType) - { - var elementType = targetType.GetGenericArguments()[0]; - var list = Activator.CreateInstance(targetType) as IList; - foreach (var child in value.Children) + public uint UInt(int key, uint fallback = 0) { - list.Add(ConvertValue(child, elementType)); + return _fields.TryGetValue(key, out var value) ? checked((uint)value.NumberValue) : fallback; } - return list; - } - - private static object ConvertKeyedObject(AetheriaRuntimeBehaviorValue value, Type targetType) - { - var instance = Activator.CreateInstance(targetType); - var children = value.Children; - foreach (var field in GetKeyedFields(targetType)) + public Guid Guid(int key, Guid fallback = default) { - if (field.key >= 0 && field.key < children.Count) + return _fields.TryGetValue(key, out var value) && System.Guid.TryParse(value.LegacyIdValue, out var result) + ? result + : fallback; + } + + public T Enum(int key, T fallback) where T : struct + { + if (!_fields.TryGetValue(key, out var value)) { - field.field.SetValue(instance, ConvertValue(children[field.key], field.field.FieldType)); + return fallback; } + + if (!string.IsNullOrWhiteSpace(value.StringValue) && + System.Enum.TryParse(value.StringValue, true, out T parsed)) + { + return parsed; + } + + return (T)System.Enum.ToObject(typeof(T), checked((int)value.NumberValue)); } - return instance; - } - - private static object ParseEnumValue(AetheriaRuntimeBehaviorValue value, Type targetType) - { - if (!string.IsNullOrWhiteSpace(value.StringValue)) + public float3 Float3(int key, float3 fallback = default) { - return Enum.Parse(targetType, value.StringValue, true); + return _fields.TryGetValue(key, out var value) ? ToFloat3(value) : fallback; } - return Enum.ToObject(targetType, checked((int)value.NumberValue)); - } + public float4[] Float4Array(int key, float4[] fallback) + { + return _fields.TryGetValue(key, out var value) + ? value.Children.Select(ToFloat4).ToArray() + : fallback; + } - private static Guid ParseGuid(string value) - { - return Guid.TryParse(value, out var result) ? result : Guid.Empty; - } + public PerformanceStat PerformanceStat(int key, PerformanceStat fallback) + { + return _fields.TryGetValue(key, out var value) ? ToPerformanceStat(value) : fallback; + } - private static float2 ConvertFloat2(AetheriaRuntimeBehaviorValue value) - { - return new float2( - value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0, - value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0); - } + public BezierCurve BezierCurve(int key, BezierCurve fallback) + { + if (!_fields.TryGetValue(key, out var value)) + { + return fallback; + } - private static float3 ConvertFloat3(AetheriaRuntimeBehaviorValue value) - { - return new float3( - value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0, - value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0, - value.Children.Count > 2 ? (float)value.Children[2].NumberValue : 0); - } + return new BezierCurve + { + Keys = value.Children.Count > 0 + ? value.Children[0].Children.Select(ToFloat4).ToArray() + : Array.Empty() + }; + } - private static float4 ConvertFloat4(AetheriaRuntimeBehaviorValue value) - { - return new float4( - value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0, - value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0, - value.Children.Count > 2 ? (float)value.Children[2].NumberValue : 0, - value.Children.Count > 3 ? (float)value.Children[3].NumberValue : 0); - } + public StatReference StatReference(int key, StatReference fallback) + { + if (!_fields.TryGetValue(key, out var value)) + { + return fallback; + } - private static int2 ConvertInt2(AetheriaRuntimeBehaviorValue value) - { - return new int2( - value.Children.Count > 0 ? checked((int)value.Children[0].NumberValue) : 0, - value.Children.Count > 1 ? checked((int)value.Children[1].NumberValue) : 0); + return new StatReference + { + Target = ChildString(value, 1), + Stat = ChildString(value, 2) + }; + } + + private static PerformanceStat ToPerformanceStat(AetheriaRuntimeBehaviorValue value) + { + return new PerformanceStat + { + Min = ChildFloat(value, 0), + Max = ChildFloat(value, 1), + HeatExponentMultiplier = ChildFloat(value, 2), + DurabilityExponentMultiplier = ChildFloat(value, 3), + QualityExponent = ChildFloat(value, 4) + }; + } + + private static float3 ToFloat3(AetheriaRuntimeBehaviorValue value) + { + return new float3(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2)); + } + + private static float4 ToFloat4(AetheriaRuntimeBehaviorValue value) + { + return new float4(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2), ChildFloat(value, 3)); + } + + private static float ChildFloat(AetheriaRuntimeBehaviorValue value, int index) + { + return value.Children.Count > index ? (float)value.Children[index].NumberValue : 0; + } + + private static string ChildString(AetheriaRuntimeBehaviorValue value, int index) + { + return value.Children.Count > index ? value.Children[index].StringValue ?? "" : ""; + } } public Shape GetRuntimeShape(ItemInstance item) diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index fe8c2735e..6173735e7 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -229,7 +229,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. catalog rows. `EquippedItem` and `ConsumableItemEffect` now create behavior instances through `ItemManager.CreateRuntimeBehaviors`; the remaining `RuntimeBehaviorConfig` bridge is explicitly temporary and internal to - behavior construction. `StatModifier` requirement and stat-target lookup now inspects + behavior construction. Behavior config construction now switches on stable + typed behavior kind and uses an explicit per-kind payload mapper; layout + metadata no longer selects or mutates runtime behavior configs. + `StatModifier` requirement and stat-target lookup now inspects the live equipped behavior instances and asks package-owned typed behavior metadata for kind/family matching, not a freshly rebuilt config list or a behavior-local class taxonomy. The unused `TradeMenuDebug` script has been @@ -607,8 +610,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. whole-item DTO projection cache. `AetheriaRuntimeItemCatalog` exposes typed item rows only. `ItemManager.CreateRuntimeBehaviors` owns the temporary projection from typed behavior payloads into `RuntimeBehaviorConfig` objects - required by the current behavior constructors. Direct config reads no longer - escape `ItemManager`; + required by the current behavior constructors. This projection is an explicit + typed behavior-kind switch with per-kind field mapping; it no longer reflects + over `LegacyPayloadKeyAttribute` to select or assign config fields. Direct + config reads no longer escape `ItemManager`; gameplay stat modifiers target live behavior instances and package-owned behavior metadata instead of rebuilding config DTOs for lookup. `ItemInstance.ItemId` is the runtime identity surface and @@ -629,7 +634,8 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. docking-bay coverage checks instead of hardcoding migrated class-name strings. Item/behavior DTO field layout for the temporary projection bridge is now marked with project-owned `LegacyPayloadKeyAttribute`, not MessagePack - metadata. Item properties + metadata. That attribute is migration/layout metadata only; runtime behavior + construction does not use it as an authority. Item properties manufacturer display is a typed snapshot consumer; loadout generation uses typed catalog rows for hull type, shape, category, hardpoint type, behavior kind prefilters, and selected-item instantiation. The old trade debug UI @@ -1315,6 +1321,10 @@ First Aetheria surfaces to publish: `Behavior` instances expose `Config`, while typed behavior kind strings such as `Cockpit`, `TurretController`, and `Capacitor` remain stable catalog facts rather than class-name-derived selectors. + - Done: replace `ItemManager` behavior config reflection with an explicit + typed behavior-kind mapper. `LegacyPayloadKeyAttribute` remains + migration/layout metadata only and no longer selects or mutates runtime + behavior configs. - Done: quarantine the vendored Unity `MessagePack` assembly by disabling asmdef auto-reference; only explicit state-spine assemblies should see it while the Unity CultCache bridge still needs a low-level `.cc` codec.