From c89f8ea480017ccfc2cc8395a4ebd6d848397fa5 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 21 Jun 2026 19:59:30 +0100 Subject: [PATCH] Add daemon item stat query refs --- Aetheria.State.Verify/Program.cs | 56 +++- Assets/Scripts/Gameplay/ActionGameManager.cs | 5 + Assets/Scripts/UI/Menu/InventoryMenu.cs | 187 ++++++++++- .../AetheriaRuntimeDaemonItemStatQueries.cs | 299 ++++++++++++++++++ ...eriaRuntimePlayerSettingsSurfaceBuilder.cs | 28 ++ 5 files changed, 564 insertions(+), 11 deletions(-) create mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonItemStatQueries.cs diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 1f0ca0120..7ccc698f2 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -4076,22 +4076,48 @@ static void RequireInventoryShipSettingsUseEveSurface(string root) static void RequireInventoryCargoItemDetailsUseEveSurface(string root) { var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); + var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); + var surfaceDocumentPath = Path.Combine( + root, + "Packages", + "org.gamecult.aetheria.state", + "Runtime", + "AetheriaRuntimePlayerSettingsSurfaceBuilder.cs"); + var daemonItemStatQueriesPath = Path.Combine( + root, + "Packages", + "org.gamecult.aetheria.state", + "Runtime", + "AetheriaRuntimeDaemonItemStatQueries.cs"); var cargoItemSurfaceBuilderPath = Path.Combine( root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs"); - if (!File.Exists(inventoryMenuPath)) + var requiredFiles = new[] { - throw new InvalidOperationException("Cannot verify inventory cargo-item shell; InventoryMenu.cs is missing."); - } - if (!File.Exists(cargoItemSurfaceBuilderPath)) + inventoryMenuPath, + actionGameManagerPath, + surfaceDocumentPath, + daemonItemStatQueriesPath, + cargoItemSurfaceBuilderPath + }; + var missingFiles = requiredFiles + .Where(path => !File.Exists(path)) + .Select(path => Path.GetRelativePath(root, path)) + .ToArray(); + if (missingFiles.Length > 0) { - throw new InvalidOperationException("Cannot verify inventory cargo-item shell; AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs is missing."); + throw new InvalidOperationException( + "Cannot verify inventory cargo-item shell; required files are missing: " + + string.Join(", ", missingFiles)); } var source = File.ReadAllText(inventoryMenuPath); + var actionGameManager = File.ReadAllText(actionGameManagerPath); + var surfaceDocument = File.ReadAllText(surfaceDocumentPath); + var daemonItemStatQueries = File.ReadAllText(daemonItemStatQueriesPath); var cargoItemSurfaceBuilder = File.ReadAllText(cargoItemSurfaceBuilderPath); var requiredSymbols = new[] { @@ -4123,6 +4149,26 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root) throw new InvalidOperationException("InventoryMenu cargo click path no longer routes item inspection through the Eve surface."); } + if (!source.Contains("AetheriaRuntimeDaemonItemStatQueries.EvaluatePerformanceStat(", StringComparison.Ordinal) || + source.Contains("GameManager.ItemManager.GetTier", StringComparison.Ordinal) || + source.Contains("GameManager.ItemManager.Evaluate", StringComparison.Ordinal) || + actionGameManager.Contains("ObservedItemStat(", StringComparison.Ordinal) || + actionGameManager.Contains("ItemManager.Evaluate(stat", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "InventoryMenu must project current item stats through shared daemon item-stat queries instead of direct Unity ItemManager stat authority."); + } + + if (!surfaceDocument.Contains("public static class AetheriaRuntimeSurfaceStateRefs", StringComparison.Ordinal) || + !surfaceDocument.Contains("public const string Value = \"valueRef\"", StringComparison.Ordinal) || + !daemonItemStatQueries.Contains("public const string StateRefPrefix = \"aetheria.state/items\"", StringComparison.Ordinal) || + !daemonItemStatQueries.Contains("public static string ItemStatRef(", StringComparison.Ordinal) || + !daemonItemStatQueries.Contains("public string ValueRef =>", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Eve/CultUI item stat surfaces must expose typed daemon state refs that the UI runtime can resolve."); + } + var forbiddenSymbols = new[] { "BuildCargoItemDetailsSurfaceDefinition(", diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index f785510c6..d7c409dac 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -542,6 +542,11 @@ public class ActionGameManager : MonoBehaviour return _observedLoadoutTemplates; } + public (RarityTier tier, int upgrades) ObservedItemTier(CraftedItemInstance item) + { + return ItemManager.GetTier(item); + } + private readonly (float2 direction, string name)[] _directions = { (new float2(0, 1), "Front"), (new float2(1, 0), "Right"), diff --git a/Assets/Scripts/UI/Menu/InventoryMenu.cs b/Assets/Scripts/UI/Menu/InventoryMenu.cs index 4877e04db..00251c2a6 100644 --- a/Assets/Scripts/UI/Menu/InventoryMenu.cs +++ b/Assets/Scripts/UI/Menu/InventoryMenu.cs @@ -506,7 +506,7 @@ public class InventoryMenu : MonoBehaviour if (item is EquippableItem equippableItem) { - var (itemTier, upgrades) = GameManager.ItemManager.GetTier(equippableItem); + var (itemTier, upgrades) = GameManager.ObservedItemTier(equippableItem); tier = $"{itemTier.Name}{new string('+', upgrades)}"; durability = $"{(int)(equippableItem.Durability / GetMaxDurability(typedItem, equippableItem) * 100)}%"; thermalRange = FormatTemperatureRange(typedItem); @@ -548,7 +548,7 @@ public class InventoryMenu : MonoBehaviour new AetheriaRuntimeCargoItemMetric( $"{AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.target", $"{statReference.Target.SplitCamelCase()}:{statReference.Stat.SplitCamelCase()}", - $"{(modifierType == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.RuntimePlayerSettings.Format(GameManager.ItemManager.Evaluate(modifier, equippableItem))}") + $"{(modifierType == StatModifierType.Constant ? "+" : "x")}{FormatCurrentItemStat(modifier, equippableItem)}") }); continue; } @@ -594,7 +594,7 @@ public class InventoryMenu : MonoBehaviour value = ((int)payloadField.Value.NumberValue).ToString(); break; case AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat: - value = ActionGameManager.RuntimePlayerSettings.Format(GameManager.ItemManager.Evaluate(AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(payloadField.Value), equippableItem)); + value = FormatCurrentItemStat(payloadField.Value, equippableItem); break; default: return null; @@ -627,7 +627,7 @@ public class InventoryMenu : MonoBehaviour new AetheriaRuntimeEquippedItemMetric( $"{AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.target", $"{statReference.Target.SplitCamelCase()}:{statReference.Stat.SplitCamelCase()}", - $"{(modifierType == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.RuntimePlayerSettings.Format(GameManager.ItemManager.Evaluate(modifier, equippableItem))}") + $"{(modifierType == StatModifierType.Constant ? "+" : "x")}{FormatCurrentItemStat(modifier, equippableItem)}") }); continue; } @@ -673,7 +673,7 @@ public class InventoryMenu : MonoBehaviour value = ((int)payloadField.Value.NumberValue).ToString(); break; case AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat: - value = ActionGameManager.RuntimePlayerSettings.Format(GameManager.ItemManager.Evaluate(AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(payloadField.Value), equippableItem)); + value = FormatCurrentItemStat(payloadField.Value, equippableItem); break; default: return null; @@ -687,10 +687,185 @@ public class InventoryMenu : MonoBehaviour private string BuildEquippedItemTitle(EquippedItem item, AetheriaRuntimeCatalogItem typedItem) { - var (tier, upgrades) = GameManager.ItemManager.GetTier(item.EquippableItem); + var (tier, upgrades) = GameManager.ObservedItemTier(item.EquippableItem); return $"{typedItem?.Name ?? "Unknown Item"} ({tier.Name}{new string('+', upgrades)})"; } + private static string FormatCurrentItemStat(PerformanceStat stat, EquippableItem item) + { + return FormatCurrentItemStat(ToRuntimeBehaviorValue(stat), item); + } + + private static string FormatCurrentItemStat(AetheriaRuntimeBehaviorValue stat, EquippableItem item) + { + return ActionGameManager.RuntimePlayerSettings.Format( + AetheriaRuntimeDaemonItemStatQueries.EvaluatePerformanceStat( + stat, + ToRuntimeLoadoutItem(item), + item?.Temperature ?? 0)); + } + + private static AetheriaRuntimeLoadoutItemCommit ToRuntimeLoadoutItem(EquippableItem item) + { + return AetheriaRuntimeDaemonItemStatQueries.ItemCommit( + item?.ItemKey ?? "", + item?.Quality ?? 1, + item?.Durability ?? 1, + enabled: true, + overrideShutdown: item != null && item.OverrideShutdown); + } + + private static AetheriaRuntimeBehaviorValue ToRuntimeBehaviorValue(PerformanceStat stat) + { + return new AetheriaRuntimeBehaviorValue( + "performance-stat", + "", + 0, + false, + "", + "", + new[] + { + Number(stat?.Min ?? 0), + Number(stat?.Max ?? 0), + Number(stat?.HeatExponentMultiplier ?? 0), + Number(stat?.DurabilityExponentMultiplier ?? 0), + Number(stat?.QualityExponent ?? 0), + ToRuntimeStatRecipeValue(stat?.Recipe) + }, + Array.Empty()); + } + + private static AetheriaRuntimeBehaviorValue ToRuntimeStatRecipeValue(StatRecipe recipe) + { + if (recipe == null) + return Number(0); + + return new AetheriaRuntimeBehaviorValue( + "stat-recipe", + "", + 0, + false, + "", + "", + new[] + { + Number(recipe.BaseValue), + new AetheriaRuntimeBehaviorValue( + "stat-recipe-modifiers", + "", + 0, + false, + "", + "", + (recipe.Modifiers ?? Array.Empty()) + .Where(modifier => modifier != null) + .Select(ToRuntimeStatRecipeModifierValue) + .ToArray(), + Array.Empty()) + }, + Array.Empty()); + } + + private static AetheriaRuntimeBehaviorValue ToRuntimeStatRecipeModifierValue(StatRecipeModifier modifier) + { + return new AetheriaRuntimeBehaviorValue( + "stat-recipe-modifier", + "", + 0, + false, + "", + "", + new[] + { + Text(ConditionToken(modifier.Condition)), + Text(OperationToken(modifier.Operation)), + Number(modifier.Amount), + Number(0), + Bool(modifier.Enabled) + }, + Array.Empty()); + } + + private static string ConditionToken(StatConditionMask condition) + { + switch (condition) + { + case StatConditionMask.Quality: + return AetheriaRuntimeStatRecipeConditions.Quality; + case StatConditionMask.Durability: + return AetheriaRuntimeStatRecipeConditions.Durability; + case StatConditionMask.Heat: + return AetheriaRuntimeStatRecipeConditions.Heat; + case StatConditionMask.Charge: + return AetheriaRuntimeStatRecipeConditions.Charge; + case StatConditionMask.Ammo: + return AetheriaRuntimeStatRecipeConditions.Ammo; + case StatConditionMask.Range: + return AetheriaRuntimeStatRecipeConditions.Range; + case StatConditionMask.Integrity: + return AetheriaRuntimeStatRecipeConditions.Integrity; + case StatConditionMask.PilotSkill: + return AetheriaRuntimeStatRecipeConditions.PilotSkill; + case StatConditionMask.Environment: + return AetheriaRuntimeStatRecipeConditions.Environment; + default: + return ""; + } + } + + private static string OperationToken(StatModifierOperation operation) + { + switch (operation) + { + case StatModifierOperation.Multiply: + return AetheriaRuntimeStatRecipeOperations.Multiply; + case StatModifierOperation.Override: + return AetheriaRuntimeStatRecipeOperations.Override; + default: + return AetheriaRuntimeStatRecipeOperations.Add; + } + } + + private static AetheriaRuntimeBehaviorValue Number(double value) + { + return new AetheriaRuntimeBehaviorValue( + "number", + "", + value, + false, + "", + "", + Array.Empty(), + Array.Empty()); + } + + private static AetheriaRuntimeBehaviorValue Text(string value) + { + return new AetheriaRuntimeBehaviorValue( + "string", + value ?? "", + 0, + false, + "", + "", + Array.Empty(), + Array.Empty()); + } + + private static AetheriaRuntimeBehaviorValue Bool(bool value) + { + return new AetheriaRuntimeBehaviorValue( + "bool", + "", + 0, + value, + "", + "", + Array.Empty(), + Array.Empty()); + } + private static bool IsWeaponGroupAssigned(EquippedItem item, int groupIndex) { return item?.Entity?.WeaponGroups != null && diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonItemStatQueries.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonItemStatQueries.cs new file mode 100644 index 000000000..4dd8c2adb --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonItemStatQueries.cs @@ -0,0 +1,299 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +#nullable enable + +namespace GameCult.Aetheria.State.Verse +{ + public readonly struct AetheriaRuntimeDaemonItemStatValue + { + public AetheriaRuntimeDaemonItemStatValue( + string itemKey, + string behaviorKind, + int behaviorGroup, + int fieldKey, + string label, + double value) + { + ItemKey = itemKey ?? ""; + BehaviorKind = behaviorKind ?? ""; + BehaviorGroup = behaviorGroup; + FieldKey = fieldKey; + Label = label ?? ""; + Value = value; + } + + public string ItemKey { get; } + public string BehaviorKind { get; } + public int BehaviorGroup { get; } + public int FieldKey { get; } + public string Label { get; } + public double Value { get; } + public string ValueRef => AetheriaRuntimeDaemonItemStatQueries.ItemStatRef( + ItemKey, + BehaviorKind, + BehaviorGroup, + FieldKey); + } + + public static class AetheriaRuntimeDaemonItemStatQueries + { + public const string StateRefPrefix = "aetheria.state/items"; + + public static string ItemStatRef( + string itemKey, + string behaviorKind, + int behaviorGroup, + int fieldKey) + { + return $"{StateRefPrefix}/{Token(itemKey)}/behaviors/{Token(behaviorKind)}/{behaviorGroup}/stats/{fieldKey}"; + } + + public static AetheriaRuntimeStatRecipePreviewState ConditionsFor( + AetheriaRuntimeLoadoutItemCommit? item, + double heat = 0, + double charge = 1, + double ammo = 1, + double range = 1, + double integrity = 1, + double pilotSkill = 0, + double environment = 0) + { + return new AetheriaRuntimeStatRecipePreviewState( + quality: Clamp01(item?.Quality ?? 1), + durability: Clamp01(item?.Durability ?? 1), + heat: Clamp01(heat), + charge: Clamp01(charge), + ammo: Clamp01(ammo), + range: Clamp01(range), + integrity: Clamp01(integrity), + pilotSkill: Clamp01(pilotSkill), + environment: Clamp01(environment)); + } + + public static double EvaluatePerformanceStat( + AetheriaRuntimeBehaviorValue? value, + AetheriaRuntimeLoadoutItemCommit? item, + double heat = 0) + { + return EvaluatePerformanceStat( + ReadPerformanceStat(value), + ConditionsFor(item, heat)); + } + + public static double EvaluatePerformanceStat( + AetheriaRuntimePerformanceStat? stat, + AetheriaRuntimeStatRecipePreviewState conditions) + { + if (stat == null) + return 0; + + conditions ??= AetheriaRuntimeStatRecipePreviewState.Default; + if (stat.Recipe != null) + return EvaluateRecipe(stat.Recipe, conditions); + + var heat = stat.HeatExponentMultiplier == 0 + ? 1 + : Math.Pow(Clamp01(conditions.Heat), stat.HeatExponentMultiplier); + var durability = stat.DurabilityExponentMultiplier == 0 + ? 1 + : Math.Pow(Clamp01(conditions.Durability), stat.DurabilityExponentMultiplier); + var quality = stat.QualityExponent == 0 + ? 1 + : Math.Pow(Clamp01(conditions.Quality), stat.QualityExponent); + + return Lerp(stat.Min, stat.Max, durability * quality * heat); + } + + public static IReadOnlyList QueryPerformanceStats( + AetheriaRuntimeCatalogItem? catalogItem, + AetheriaRuntimeLoadoutItemCommit? item, + double heat = 0) + { + if (catalogItem?.BehaviorPayloads == null || catalogItem.BehaviorPayloads.Count == 0) + return Array.Empty(); + + var conditions = ConditionsFor(item, heat); + var stats = new List(); + foreach (var behavior in catalogItem.BehaviorPayloads) + { + var metadata = AetheriaRuntimeBehaviorMetadataCatalog.Get(behavior.Kind); + foreach (var field in behavior.Fields ?? Array.Empty()) + { + var fieldMetadata = metadata?.DisplayFields.FirstOrDefault(candidate => candidate.Key == field.Key); + if (fieldMetadata?.ValueKind != AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat) + continue; + + stats.Add(new AetheriaRuntimeDaemonItemStatValue( + item?.ItemKey ?? catalogItem.ItemKey, + behavior.Kind, + behavior.Group, + field.Key, + fieldMetadata.Name, + EvaluatePerformanceStat(ReadPerformanceStat(field.Value), conditions))); + } + } + + return stats.Count == 0 ? Array.Empty() : stats; + } + + public static AetheriaRuntimeLoadoutItemCommit ItemCommit( + string itemKey, + double quality, + double durability, + int quantity = 1, + bool enabled = true, + bool overrideShutdown = false) + { + return new AetheriaRuntimeLoadoutItemCommit + { + ItemKey = itemKey ?? "", + Quality = quality, + Durability = durability, + Quantity = quantity, + Enabled = enabled, + OverrideShutdown = overrideShutdown + }; + } + + private static AetheriaRuntimePerformanceStat ReadPerformanceStat(AetheriaRuntimeBehaviorValue? value) + { + return new AetheriaRuntimePerformanceStat( + ChildNumber(value, 0), + ChildNumber(value, 1), + ChildNumber(value, 2), + ChildNumber(value, 3), + ChildNumber(value, 4), + ReadStatRecipe(ChildValue(value, 5))); + } + + private static AetheriaRuntimeStatRecipe? ReadStatRecipe(AetheriaRuntimeBehaviorValue? value) + { + if (value == null || value.Children.Count == 0) + return null; + + var modifiers = ChildValue(value, 1)?.Children + .Select(ReadStatRecipeModifier) + .Where(modifier => modifier != null) + .Cast() + .ToArray() ?? Array.Empty(); + + return new AetheriaRuntimeStatRecipe(ChildNumber(value, 0), modifiers); + } + + private static AetheriaRuntimeStatRecipeModifier? ReadStatRecipeModifier(AetheriaRuntimeBehaviorValue? value) + { + if (value == null) + return null; + + return new AetheriaRuntimeStatRecipeModifier( + ChildString(value, 0), + ChildString(value, 1), + ChildNumber(value, 2), + ReadCurveKeys(ChildValue(value, 3)), + value.Children.Count <= 4 || ChildValue(value, 4)?.BoolValue == true); + } + + private static IReadOnlyList ReadCurveKeys(AetheriaRuntimeBehaviorValue? value) + { + if (value?.Children == null || value.Children.Count == 0) + return Array.Empty(); + + return value.Children + .Where(key => key.Children.Count >= 4) + .Select(key => new AetheriaRuntimeCurveKey( + ChildNumber(key, 0), + ChildNumber(key, 1), + ChildNumber(key, 2), + ChildNumber(key, 3))) + .ToArray(); + } + + private static double EvaluateRecipe( + AetheriaRuntimeStatRecipe recipe, + AetheriaRuntimeStatRecipePreviewState conditions) + { + var value = recipe.BaseValue; + foreach (var modifier in recipe.Modifiers ?? Array.Empty()) + { + if (modifier == null || !modifier.Enabled || string.IsNullOrWhiteSpace(modifier.Condition)) + continue; + + var sample = SampleCurve(modifier.CurveKeys, conditions.GetConditionValue(modifier.Condition)); + switch (modifier.Operation ?? "") + { + case AetheriaRuntimeStatRecipeOperations.Multiply: + value *= 1 + ((modifier.Amount - 1) * sample); + break; + case AetheriaRuntimeStatRecipeOperations.Override: + value = Lerp(value, modifier.Amount, sample); + break; + default: + value += modifier.Amount * sample; + break; + } + } + + return value; + } + + private static double SampleCurve(IReadOnlyList? keys, double value) + { + if (keys == null || keys.Count == 0) + return Clamp01(value); + + var ordered = keys.OrderBy(key => key.Time).ToArray(); + if (value <= ordered[0].Time) + return Clamp01(ordered[0].Value); + + for (var index = 1; index < ordered.Length; index++) + { + var next = ordered[index]; + var previous = ordered[index - 1]; + if (value > next.Time) + continue; + + var span = next.Time - previous.Time; + var t = span <= double.Epsilon ? 1 : Clamp01((value - previous.Time) / span); + return Clamp01(Lerp(previous.Value, next.Value, t)); + } + + return Clamp01(ordered[ordered.Length - 1].Value); + } + + private static double ChildNumber(AetheriaRuntimeBehaviorValue? value, int index) + { + return ChildValue(value, index)?.NumberValue ?? 0; + } + + private static string ChildString(AetheriaRuntimeBehaviorValue? value, int index) + { + return ChildValue(value, index)?.StringValue ?? ""; + } + + private static AetheriaRuntimeBehaviorValue? ChildValue(AetheriaRuntimeBehaviorValue? value, int index) + { + return value != null && value.Children.Count > index ? value.Children[index] : null; + } + + private static double Clamp01(double value) + { + if (value < 0) + return 0; + return value > 1 ? 1 : value; + } + + private static double Lerp(double from, double to, double t) + { + return from + ((to - from) * t); + } + + private static string Token(string value) + { + return string.IsNullOrWhiteSpace(value) + ? "_" + : value.Trim().Replace("/", "%2F"); + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs index 6a2c3feef..d7700dbb2 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs @@ -81,6 +81,34 @@ namespace GameCult.Aetheria.State.Verse public IReadOnlyList Children { get; } } + public static class AetheriaRuntimeSurfaceStateRefs + { + public const string Source = "stateRef"; + public const string Value = "valueRef"; + public const string Label = "labelRef"; + public const string Format = "stateFormat"; + + public static (string Key, string Value) SourceRef(string reference) + { + return (Source, reference ?? ""); + } + + public static (string Key, string Value) ValueRef(string reference) + { + return (Value, reference ?? ""); + } + + public static (string Key, string Value) LabelRef(string reference) + { + return (Label, reference ?? ""); + } + + public static (string Key, string Value) FormatRef(string format) + { + return (Format, format ?? ""); + } + } + public sealed class AetheriaRuntimeSurfaceStyleToken { public AetheriaRuntimeSurfaceStyleToken(string name, string value)