2026-06-21 09:37:54 +01:00
using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
2026-06-21 09:54:15 +01:00
namespace GameCult.Aetheria.State.Verse
2026-06-21 09:37:54 +01:00
{
public sealed class AetheriaRuntimeStatRecipeSurfaceState
{
public AetheriaRuntimeStatRecipeSurfaceState (
IReadOnlyList < AetheriaRuntimeStatRecipeState > recipes ,
string selectedStatName ,
AetheriaRuntimeStatRecipePreviewState preview ,
string updatedAtUtc )
{
Recipes = recipes ? ? Array . Empty < AetheriaRuntimeStatRecipeState > ( ) ;
SelectedStatName = selectedStatName ? ? "" ;
Preview = preview ? ? AetheriaRuntimeStatRecipePreviewState . Default ;
UpdatedAtUtc = updatedAtUtc ? ? "" ;
}
public IReadOnlyList < AetheriaRuntimeStatRecipeState > Recipes { get ; }
public string SelectedStatName { get ; }
public AetheriaRuntimeStatRecipePreviewState Preview { get ; }
public string UpdatedAtUtc { get ; }
public AetheriaRuntimeStatRecipeState SelectedRecipe = >
Recipes . FirstOrDefault ( recipe = > string . Equals ( recipe . StatName , SelectedStatName , StringComparison . Ordinal ) ) ? ?
Recipes . FirstOrDefault ( ) ;
}
public sealed class AetheriaRuntimeStatRecipeState
{
public AetheriaRuntimeStatRecipeState (
string recipeKey ,
string statName ,
double baseValue ,
IReadOnlyList < AetheriaRuntimeStatInfluenceState > influences )
{
RecipeKey = recipeKey ? ? "" ;
StatName = statName ? ? "" ;
BaseValue = baseValue ;
Influences = influences ? ? Array . Empty < AetheriaRuntimeStatInfluenceState > ( ) ;
}
public string RecipeKey { get ; }
public string StatName { get ; }
public double BaseValue { get ; }
public IReadOnlyList < AetheriaRuntimeStatInfluenceState > Influences { get ; }
}
public sealed class AetheriaRuntimeStatInfluenceState
{
public AetheriaRuntimeStatInfluenceState (
string condition ,
string operation ,
double amount ,
string curveLabel ,
double previewSample ,
bool enabled )
{
Condition = condition ? ? "" ;
Operation = operation ? ? AetheriaRuntimeStatRecipeOperations . Add ;
Amount = amount ;
CurveLabel = curveLabel ? ? "" ;
PreviewSample = previewSample ;
Enabled = enabled ;
}
public string Condition { get ; }
public string Operation { get ; }
public double Amount { get ; }
public string CurveLabel { get ; }
public double PreviewSample { get ; }
public bool Enabled { get ; }
}
public sealed class AetheriaRuntimeStatRecipePreviewState
{
public static readonly AetheriaRuntimeStatRecipePreviewState Default =
new AetheriaRuntimeStatRecipePreviewState (
quality : 1 ,
durability : 1 ,
heat : 0 ,
charge : 1 ,
ammo : 1 ,
range : 1 ,
integrity : 1 ,
pilotSkill : 0 ,
environment : 0 ) ;
public AetheriaRuntimeStatRecipePreviewState (
double quality ,
double durability ,
double heat ,
double charge ,
double ammo ,
double range ,
double integrity ,
double pilotSkill ,
double environment )
{
Quality = quality ;
Durability = durability ;
Heat = heat ;
Charge = charge ;
Ammo = ammo ;
Range = range ;
Integrity = integrity ;
PilotSkill = pilotSkill ;
Environment = environment ;
}
public double Quality { get ; }
public double Durability { get ; }
public double Heat { get ; }
public double Charge { get ; }
public double Ammo { get ; }
public double Range { get ; }
public double Integrity { get ; }
public double PilotSkill { get ; }
public double Environment { get ; }
public double GetConditionValue ( string condition )
{
switch ( condition ? ? "" )
{
case AetheriaRuntimeStatRecipeConditions . Quality :
return Quality ;
case AetheriaRuntimeStatRecipeConditions . Durability :
return Durability ;
case AetheriaRuntimeStatRecipeConditions . Heat :
return Heat ;
case AetheriaRuntimeStatRecipeConditions . Charge :
return Charge ;
case AetheriaRuntimeStatRecipeConditions . Ammo :
return Ammo ;
case AetheriaRuntimeStatRecipeConditions . Range :
return Range ;
case AetheriaRuntimeStatRecipeConditions . Integrity :
return Integrity ;
case AetheriaRuntimeStatRecipeConditions . PilotSkill :
return PilotSkill ;
case AetheriaRuntimeStatRecipeConditions . Environment :
return Environment ;
default :
return 0 ;
}
}
}
public static class AetheriaRuntimeStatRecipeConditions
{
public const string Quality = "quality" ;
public const string Durability = "durability" ;
public const string Heat = "heat" ;
public const string Charge = "charge" ;
public const string Ammo = "ammo" ;
public const string Range = "range" ;
public const string Integrity = "integrity" ;
public const string PilotSkill = "pilotSkill" ;
public const string Environment = "environment" ;
public static readonly IReadOnlyList < string > All = new [ ]
{
Quality ,
Durability ,
Heat ,
Charge ,
Ammo ,
Range ,
Integrity ,
PilotSkill ,
Environment
} ;
}
public static class AetheriaRuntimeStatRecipeOperations
{
public const string Add = "add" ;
public const string Multiply = "multiply" ;
public const string Override = "override" ;
}
public static class AetheriaRuntimeStatRecipeSurfaceBuilder
{
2026-06-28 12:02:16 +01:00
public static AetheriaRuntimeSurfaceDocument BuildFromCatalog (
AetheriaRuntimeCatalogSnapshot ? catalog ,
long version = 1 )
{
var recipes = ( catalog ? . Items ? ? Array . Empty < AetheriaRuntimeCatalogItem > ( ) )
. SelectMany ( ProjectRows )
. OrderBy ( recipe = > recipe . StatName , StringComparer . Ordinal )
. ToArray ( ) ;
2026-06-29 11:24:40 +01:00
return Build (
new AetheriaRuntimeStatRecipeSurfaceState (
recipes ,
recipes . FirstOrDefault ( ) ? . StatName ? ? "" ,
AetheriaRuntimeStatRecipePreviewState . Default ,
DateTime . UtcNow . ToString ( "O" ) ) ,
version ) ;
2026-06-28 12:02:16 +01:00
}
2026-06-21 09:37:54 +01:00
public static AetheriaRuntimeSurfaceDocument Build (
AetheriaRuntimeStatRecipeSurfaceState state ,
long version = 1 )
{
state ? ? = new AetheriaRuntimeStatRecipeSurfaceState (
Array . Empty < AetheriaRuntimeStatRecipeState > ( ) ,
"" ,
AetheriaRuntimeStatRecipePreviewState . Default ,
"" ) ;
var selected = state . SelectedRecipe ;
var previewValue = selected = = null ? 0 : EvaluatePreview ( selected , state . Preview ) ;
var enabledInfluenceCount = selected ? . Influences . Count ( influence = > influence . Enabled ) ? ? 0 ;
return new AetheriaRuntimeSurfaceDocument (
providerId : "aetheria" ,
providerKind : "game.design" ,
title : "Aetheria Stat Recipes" ,
version : version ,
updatedAtUtc : state . UpdatedAtUtc ,
surface : new AetheriaRuntimeSurfaceTree (
AetheriaRuntimeStatRecipeCommands . SurfaceId ,
Node (
"aetheria.statRecipes.root" ,
"surface" ,
Array . Empty < ( string Key , string Value ) > ( ) ,
BuildSummaryCard ( selected , previewValue , enabledInfluenceCount ) ,
BuildRecipeList ( state , selected ) ,
BuildSelectedRecipeCard ( selected ) ,
BuildConditionPalette ( selected ) ,
BuildInfluenceList ( selected , state . Preview ) ,
BuildPreviewCard ( state . Preview ) ) ,
Array . Empty < AetheriaRuntimeSurfaceStyleToken > ( ) ) ,
commands : BuildCommandTemplates ( ) ) ;
}
private static AetheriaRuntimeSurfaceComponent BuildSummaryCard (
AetheriaRuntimeStatRecipeState selected ,
double previewValue ,
int enabledInfluenceCount )
{
var statName = string . IsNullOrWhiteSpace ( selected ? . StatName ) ? "No stat selected" : selected . StatName ;
return Node (
"aetheria.statRecipes.summary" ,
"card" ,
new [ ] { ( "title" , "Stat Recipe" ) } ,
Metric ( "aetheria.statRecipes.summary.stat" , "Selected Stat" , statName ) ,
Metric ( "aetheria.statRecipes.summary.base" , "Base" , Format ( selected ? . BaseValue ? ? 0 ) ) ,
Metric ( "aetheria.statRecipes.summary.preview" , "Preview" , Format ( previewValue ) ) ,
Metric ( "aetheria.statRecipes.summary.influences" , "Active Conditions" , enabledInfluenceCount . ToString ( ) ) ,
Text (
"aetheria.statRecipes.summary.note" ,
"Each recipe owns only the conditions it actually cares about; runtime evaluation can sample those modifiers instead of walking every global performance axis." ) ) ;
}
private static AetheriaRuntimeSurfaceComponent BuildRecipeList (
AetheriaRuntimeStatRecipeSurfaceState state ,
AetheriaRuntimeStatRecipeState selected )
{
var children = state . Recipes
. Select ( ( recipe , index ) = > Button (
$"aetheria.statRecipes.recipe.{index}.select" ,
string . Equals ( recipe . StatName , selected ? . StatName , StringComparison . Ordinal ) ? $"{recipe.StatName} (Selected)" : recipe . StatName ,
AetheriaRuntimeStatRecipeCommands . SelectStat ,
( "recipeKey" , recipe . RecipeKey ) ,
( "statName" , recipe . StatName ) ) )
. Concat ( new [ ]
{
Button (
"aetheria.statRecipes.recipe.add" ,
"Add Stat" ,
AetheriaRuntimeStatRecipeCommands . AddStat )
} )
. ToArray ( ) ;
return Node (
"aetheria.statRecipes.recipes" ,
"card" ,
new [ ] { ( "title" , "Recipes" ) } ,
Node ( "aetheria.statRecipes.recipes.list" , "grid" , Array . Empty < ( string Key , string Value ) > ( ) , children ) ) ;
}
private static AetheriaRuntimeSurfaceComponent BuildSelectedRecipeCard ( AetheriaRuntimeStatRecipeState selected )
{
if ( selected = = null )
{
return Node (
"aetheria.statRecipes.selected" ,
"card" ,
new [ ] { ( "title" , "Selected Recipe" ) } ,
Text ( "aetheria.statRecipes.selected.empty" , "Add or select a stat recipe to start authoring modifiers." ) ) ;
}
return Node (
"aetheria.statRecipes.selected" ,
"card" ,
new [ ] { ( "title" , "Selected Recipe" ) } ,
TextInput (
"aetheria.statRecipes.selected.name" ,
"Stat Name" ,
selected . StatName ,
AetheriaRuntimeStatRecipeCommands . SetStatName ,
( "recipeKey" , selected . RecipeKey ) ,
( "statName" , selected . StatName ) ) ,
NumberInput (
"aetheria.statRecipes.selected.base" ,
"Base Value" ,
selected . BaseValue ,
AetheriaRuntimeStatRecipeCommands . SetBaseValue ,
( "recipeKey" , selected . RecipeKey ) ,
( "statName" , selected . StatName ) ) ,
ButtonRow (
"aetheria.statRecipes.selected.actions" ,
Button (
"aetheria.statRecipes.selected.remove" ,
"Remove Stat" ,
AetheriaRuntimeStatRecipeCommands . RemoveStat ,
( "recipeKey" , selected . RecipeKey ) ,
( "statName" , selected . StatName ) ) ,
Button (
"aetheria.statRecipes.selected.refresh" ,
"Refresh" ,
AetheriaRuntimeStatRecipeCommands . Refresh ) ) ) ;
}
private static AetheriaRuntimeSurfaceComponent BuildConditionPalette ( AetheriaRuntimeStatRecipeState selected )
{
var children = AetheriaRuntimeStatRecipeConditions . All
. Select ( condition = >
{
var influence = selected ? . Influences . FirstOrDefault ( candidate = > string . Equals ( candidate . Condition , condition , StringComparison . Ordinal ) ) ;
var enabled = influence ? . Enabled ? ? false ;
return Toggle (
$"aetheria.statRecipes.conditions.{condition}" ,
ConditionLabel ( condition ) ,
enabled ,
AetheriaRuntimeStatRecipeCommands . ToggleCondition ,
( "recipeKey" , selected ? . RecipeKey ? ? "" ) ,
( "statName" , selected ? . StatName ? ? "" ) ,
( "condition" , condition ) ,
( "enabled" , enabled ? "false" : "true" ) ) ;
} )
. ToArray ( ) ;
return Node (
"aetheria.statRecipes.conditions" ,
"card" ,
new [ ] { ( "title" , "Affected By" ) } ,
Node ( "aetheria.statRecipes.conditions.grid" , "grid" , Array . Empty < ( string Key , string Value ) > ( ) , children ) ) ;
}
private static AetheriaRuntimeSurfaceComponent BuildInfluenceList (
AetheriaRuntimeStatRecipeState selected ,
AetheriaRuntimeStatRecipePreviewState preview )
{
if ( selected = = null | | selected . Influences . Count = = 0 )
{
return Node (
"aetheria.statRecipes.influences" ,
"card" ,
new [ ] { ( "title" , "Modifiers" ) } ,
Text ( "aetheria.statRecipes.influences.empty" , "Choose a condition above to create the first modifier for this stat." ) ) ;
}
return Node (
"aetheria.statRecipes.influences" ,
"card" ,
new [ ] { ( "title" , "Modifiers" ) } ,
Node (
"aetheria.statRecipes.influences.grid" ,
"grid" ,
Array . Empty < ( string Key , string Value ) > ( ) ,
selected . Influences . Select ( influence = > BuildInfluenceCard ( selected , influence , preview ) ) . ToArray ( ) ) ) ;
}
private static AetheriaRuntimeSurfaceComponent BuildInfluenceCard (
AetheriaRuntimeStatRecipeState recipe ,
AetheriaRuntimeStatInfluenceState influence ,
AetheriaRuntimeStatRecipePreviewState preview )
{
var conditionValue = preview . GetConditionValue ( influence . Condition ) ;
var sample = ResolvePreviewSample ( influence , conditionValue ) ;
return Node (
$"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}" ,
"card" ,
new [ ] { ( "title" , ConditionLabel ( influence . Condition ) ) } ,
Metric (
$"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.operation" ,
"Operation" ,
OperationLabel ( influence . Operation ) ) ,
NumberInput (
$"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.amount" ,
"Amount" ,
influence . Amount ,
AetheriaRuntimeStatRecipeCommands . SetInfluenceAmount ,
( "recipeKey" , recipe . RecipeKey ) ,
( "statName" , recipe . StatName ) ,
( "condition" , influence . Condition ) ) ,
TextInput (
$"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.curve" ,
"Curve" ,
influence . CurveLabel ,
AetheriaRuntimeStatRecipeCommands . SetInfluenceCurve ,
( "recipeKey" , recipe . RecipeKey ) ,
( "statName" , recipe . StatName ) ,
( "condition" , influence . Condition ) ) ,
Metric (
$"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.sample" ,
"Preview Sample" ,
Format ( sample ) ) ,
ButtonRow (
$"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.actions" ,
Button (
$"aetheria.statRecipes.influence.{StableToken(recipe.RecipeKey)}.{influence.Condition}.operation.cycle" ,
"Cycle Operation" ,
AetheriaRuntimeStatRecipeCommands . CycleInfluenceOperation ,
( "recipeKey" , recipe . RecipeKey ) ,
( "statName" , recipe . StatName ) ,
( "condition" , influence . Condition ) ) ) ) ;
}
private static AetheriaRuntimeSurfaceComponent BuildPreviewCard ( AetheriaRuntimeStatRecipePreviewState preview )
{
return Node (
"aetheria.statRecipes.preview" ,
"card" ,
new [ ] { ( "title" , "Preview Context" ) } ,
Node (
"aetheria.statRecipes.preview.grid" ,
"grid" ,
Array . Empty < ( string Key , string Value ) > ( ) ,
AetheriaRuntimeStatRecipeConditions . All
. Select ( condition = > Slider (
$"aetheria.statRecipes.preview.{condition}" ,
ConditionLabel ( condition ) ,
preview . GetConditionValue ( condition ) ,
0 ,
1 ,
AetheriaRuntimeStatRecipeCommands . SetPreviewCondition ,
( "condition" , condition ) ) )
. ToArray ( ) ) ) ;
}
private static IReadOnlyList < AetheriaRuntimeSurfaceCommandTemplate > BuildCommandTemplates ( )
{
return new [ ]
{
2026-06-21 13:00:42 +01:00
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . Refresh , "Refresh" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport ) ,
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . SelectStat , "Select Stat" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport ) ,
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . AddStat , "Add Stat" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport ) ,
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . RemoveStat , "Remove Stat" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport ) ,
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . SetStatName , "Set Stat Name" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport ) ,
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . SetBaseValue , "Set Base Value" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport ) ,
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . ToggleCondition , "Toggle Condition" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport ) ,
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . CycleInfluenceOperation , "Cycle Operation" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport ) ,
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . SetInfluenceAmount , "Set Influence Amount" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport ) ,
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . SetInfluenceCurve , "Set Influence Curve" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport ) ,
new AetheriaRuntimeSurfaceCommandTemplate ( AetheriaRuntimeStatRecipeCommands . SetPreviewCondition , "Set Preview Condition" , AetheriaRuntimeSurfaceCommandTemplate . CultMeshTransport )
2026-06-21 09:37:54 +01:00
} ;
}
private static double EvaluatePreview (
AetheriaRuntimeStatRecipeState recipe ,
AetheriaRuntimeStatRecipePreviewState preview )
{
var value = recipe . BaseValue ;
foreach ( var influence in recipe . Influences . Where ( influence = > influence . Enabled ) )
{
var sample = ResolvePreviewSample ( influence , preview . GetConditionValue ( influence . Condition ) ) ;
switch ( influence . Operation )
{
case AetheriaRuntimeStatRecipeOperations . Multiply :
value * = 1 + ( ( influence . Amount - 1 ) * sample ) ;
break ;
case AetheriaRuntimeStatRecipeOperations . Override :
value = Lerp ( value , influence . Amount , sample ) ;
break ;
default :
value + = influence . Amount * sample ;
break ;
}
}
return value ;
}
private static double ResolvePreviewSample (
AetheriaRuntimeStatInfluenceState influence ,
double conditionValue )
{
if ( influence . PreviewSample > 0 )
return Clamp01 ( influence . PreviewSample ) ;
return Clamp01 ( conditionValue ) ;
}
private static double Clamp01 ( double value )
{
if ( value < 0 )
return 0 ;
if ( value > 1 )
return 1 ;
return value ;
}
private static double Lerp ( double from , double to , double sample )
{
return from + ( ( to - from ) * Clamp01 ( sample ) ) ;
}
private static string Format ( double value )
{
return value . ToString ( "0.###" , CultureInfo . InvariantCulture ) ;
}
private static string ConditionLabel ( string condition )
{
switch ( condition ? ? "" )
{
case AetheriaRuntimeStatRecipeConditions . Quality :
return "Quality" ;
case AetheriaRuntimeStatRecipeConditions . Durability :
return "Durability" ;
case AetheriaRuntimeStatRecipeConditions . Heat :
return "Heat" ;
case AetheriaRuntimeStatRecipeConditions . Charge :
return "Charge" ;
case AetheriaRuntimeStatRecipeConditions . Ammo :
return "Ammo" ;
case AetheriaRuntimeStatRecipeConditions . Range :
return "Range" ;
case AetheriaRuntimeStatRecipeConditions . Integrity :
return "Integrity" ;
case AetheriaRuntimeStatRecipeConditions . PilotSkill :
return "Pilot Skill" ;
case AetheriaRuntimeStatRecipeConditions . Environment :
return "Environment" ;
default :
return condition ? ? "" ;
}
}
private static string OperationLabel ( string operation )
{
switch ( operation ? ? "" )
{
case AetheriaRuntimeStatRecipeOperations . Multiply :
return "Multiply" ;
case AetheriaRuntimeStatRecipeOperations . Override :
return "Override" ;
default :
return "Add" ;
}
}
private static string StableToken ( string value )
{
if ( string . IsNullOrWhiteSpace ( value ) )
return "none" ;
return new string ( value
. Select ( character = > char . IsLetterOrDigit ( character ) ? character : '_' )
. ToArray ( ) ) ;
}
private static AetheriaRuntimeSurfaceComponent Metric ( string id , string label , string value )
{
return Node ( id , "metric" , new [ ] { ( "label" , label ? ? "" ) , ( "value" , value ? ? "" ) } ) ;
}
private static AetheriaRuntimeSurfaceComponent Text ( string id , string value )
{
return Node ( id , "text" , new [ ] { ( "value" , value ? ? "" ) } ) ;
}
private static AetheriaRuntimeSurfaceComponent Button (
string id ,
string label ,
string command ,
params ( string Key , string Value ) [ ] extraProps )
{
var props = new List < ( string Key , string Value ) >
{
( "label" , label ? ? "" ) ,
( "command" , command ? ? "" )
} ;
if ( extraProps ! = null )
{
props . AddRange ( extraProps ) ;
}
return Node ( id , "control.button" , props ) ;
}
private static AetheriaRuntimeSurfaceComponent Toggle (
string id ,
string label ,
bool value ,
string command ,
params ( string Key , string Value ) [ ] extraProps )
{
var props = new List < ( string Key , string Value ) >
{
( "label" , label ? ? "" ) ,
( "value" , value ? "true" : "false" ) ,
( "command" , command ? ? "" )
} ;
if ( extraProps ! = null )
{
props . AddRange ( extraProps ) ;
}
return Node ( id , "control.toggle" , props ) ;
}
private static AetheriaRuntimeSurfaceComponent Slider (
string id ,
string label ,
double value ,
double min ,
double max ,
string command ,
params ( string Key , string Value ) [ ] extraProps )
{
var props = new List < ( string Key , string Value ) >
{
( "label" , label ? ? "" ) ,
( "value" , Format ( value ) ) ,
( "min" , Format ( min ) ) ,
( "max" , Format ( max ) ) ,
( "command" , command ? ? "" )
} ;
if ( extraProps ! = null )
{
props . AddRange ( extraProps ) ;
}
return Node ( id , "control.slider" , props ) ;
}
private static AetheriaRuntimeSurfaceComponent NumberInput (
string id ,
string label ,
double value ,
string command ,
params ( string Key , string Value ) [ ] extraProps )
{
var props = new List < ( string Key , string Value ) >
{
( "label" , label ? ? "" ) ,
( "value" , Format ( value ) ) ,
( "command" , command ? ? "" ) ,
( "valueKind" , "number" )
} ;
if ( extraProps ! = null )
{
props . AddRange ( extraProps ) ;
}
return Node ( id , "control.text" , props ) ;
}
private static AetheriaRuntimeSurfaceComponent TextInput (
string id ,
string label ,
string value ,
string command ,
params ( string Key , string Value ) [ ] extraProps )
{
var props = new List < ( string Key , string Value ) >
{
( "label" , label ? ? "" ) ,
( "value" , value ? ? "" ) ,
( "command" , command ? ? "" )
} ;
if ( extraProps ! = null )
{
props . AddRange ( extraProps ) ;
}
return Node ( id , "control.text" , props ) ;
}
2026-06-28 12:02:16 +01:00
private static IEnumerable < AetheriaRuntimeStatRecipeState > ProjectRows (
AetheriaRuntimeCatalogItem item )
{
foreach ( var behavior in item . BehaviorPayloads ? ? Array . Empty < AetheriaRuntimeBehaviorPayload > ( ) )
{
var metadata = AetheriaRuntimeBehaviorMetadataCatalog . Get ( behavior . Kind ) ;
foreach ( var field in behavior . Fields ? ? Array . Empty < AetheriaRuntimeBehaviorField > ( ) )
{
var fieldMetadata = metadata ? . DisplayFields . FirstOrDefault ( candidate = > candidate . Key = = field . Key ) ;
if ( fieldMetadata ? . ValueKind ! = AetheriaRuntimeBehaviorFieldValueKind . PerformanceStat )
continue ;
yield return ProjectRow ( item , behavior , field , fieldMetadata . Name ) ;
}
}
}
private static AetheriaRuntimeStatRecipeState ProjectRow (
AetheriaRuntimeCatalogItem item ,
AetheriaRuntimeBehaviorPayload behavior ,
AetheriaRuntimeBehaviorField field ,
string fieldName )
{
var value = field . Value ;
var recipe = ReadRecipe ( value ) ;
var statName = string . Join (
" / " ,
new [ ] { item . Name , behavior . Kind , fieldName }
. Where ( part = > ! string . IsNullOrWhiteSpace ( part ) ) ) ;
var recipeKey = $"{item.ItemKey}|{behavior.Kind}|{behavior.Group}|{field.Key}" ;
var baseValue = recipe ? . BaseValue ? ? ReadBaseValue ( value ) ;
return new AetheriaRuntimeStatRecipeState (
recipeKey ,
statName ,
baseValue ,
( recipe ? . Modifiers ? ? Array . Empty < AetheriaRuntimeStatRecipeModifier > ( ) )
. Select ( ProjectInfluence )
. ToArray ( ) ) ;
}
private static double ReadBaseValue ( AetheriaRuntimeBehaviorValue value )
{
if ( value ? . Children = = null | | value . Children . Count < 2 )
return 0 ;
return value . Children [ 1 ] . NumberValue ;
}
private static AetheriaRuntimeStatRecipe ? ReadRecipe ( AetheriaRuntimeBehaviorValue ? value )
{
if ( value ? . Children = = null | | value . Children . Count < = 5 )
return null ;
var recipeValue = value . Children [ 5 ] ;
if ( recipeValue ? . Children = = null | | recipeValue . Children . Count = = 0 )
return null ;
var baseValue = recipeValue . Children [ 0 ] . NumberValue ;
var modifierValues = recipeValue . Children . Count > 1
? recipeValue . Children [ 1 ] . Children ? ? Array . Empty < AetheriaRuntimeBehaviorValue > ( )
: Array . Empty < AetheriaRuntimeBehaviorValue > ( ) ;
var modifiers = modifierValues
. Select ( ReadModifier )
. OfType < AetheriaRuntimeStatRecipeModifier > ( )
. ToArray ( ) ;
return new AetheriaRuntimeStatRecipe ( baseValue , modifiers ) ;
}
private static AetheriaRuntimeStatRecipeModifier ? ReadModifier (
AetheriaRuntimeBehaviorValue ? value )
{
if ( value ? . Children = = null )
return null ;
return new AetheriaRuntimeStatRecipeModifier (
ReadChildString ( value , 0 ) ,
ReadChildString ( value , 1 ) ,
ReadChildNumber ( value , 2 ) ,
ReadCurveKeys ( ReadChildValue ( value , 3 ) ) ,
value . Children . Count < = 4 | | ReadChildValue ( value , 4 ) ? . BoolValue ! = false ) ;
}
private static IReadOnlyList < AetheriaRuntimeCurveKey > ReadCurveKeys (
AetheriaRuntimeBehaviorValue ? value )
{
if ( value ? . Children = = null | | value . Children . Count = = 0 )
return Array . Empty < AetheriaRuntimeCurveKey > ( ) ;
return value . Children
. Where ( key = > key . Children ! = null & & key . Children . Count > = 4 )
. Select ( key = > new AetheriaRuntimeCurveKey (
ReadChildNumber ( key , 0 ) ,
ReadChildNumber ( key , 1 ) ,
ReadChildNumber ( key , 2 ) ,
ReadChildNumber ( key , 3 ) ) )
. ToArray ( ) ;
}
private static AetheriaRuntimeStatInfluenceState ProjectInfluence (
AetheriaRuntimeStatRecipeModifier modifier )
{
return new AetheriaRuntimeStatInfluenceState (
modifier . Condition ,
string . IsNullOrWhiteSpace ( modifier . Operation ) ? AetheriaRuntimeStatRecipeOperations . Add : modifier . Operation ,
modifier . Amount ,
CurveLabel ( modifier . CurveKeys ) ,
SampleCurve ( modifier . CurveKeys , AetheriaRuntimeStatRecipePreviewState . Default . GetConditionValue ( modifier . Condition ) ) ,
modifier . Enabled ) ;
}
private static string CurvePresetLabel ( IReadOnlyList < AetheriaRuntimeCurveKey > keys )
{
if ( keys = = null | | keys . Count = = 0 )
return "" ;
var ordered = keys . OrderBy ( key = > key . Time ) . ToArray ( ) ;
if ( ordered . Length = = 2 & &
Math . Abs ( ordered [ 0 ] . Time ) < 0.0001 & &
Math . Abs ( ordered [ 0 ] . Value ) < 0.0001 & &
Math . Abs ( ordered [ 1 ] . Time - 1 ) < 0.0001 & &
Math . Abs ( ordered [ 1 ] . Value - 1 ) < 0.0001 )
return "linear" ;
return "" ;
}
private static string CurveLabel ( IReadOnlyList < AetheriaRuntimeCurveKey > keys )
{
if ( keys = = null | | keys . Count = = 0 )
return "linear" ;
var preset = CurvePresetLabel ( keys ) ;
return string . IsNullOrWhiteSpace ( preset ) ? $"{keys.Count} keys" : preset ;
}
private static double SampleCurve (
IReadOnlyList < AetheriaRuntimeCurveKey > keys ,
double value )
{
if ( keys = = null | | keys . Count = = 0 )
return ClampCurveSample ( value ) ;
var ordered = keys . OrderBy ( key = > key . Time ) . ToArray ( ) ;
if ( value < = ordered [ 0 ] . Time )
return ClampCurveSample ( 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 : ClampCurveSample ( ( value - previous . Time ) / span ) ;
return ClampCurveSample ( previous . Value + ( ( next . Value - previous . Value ) * t ) ) ;
}
return ClampCurveSample ( ordered [ ordered . Length - 1 ] . Value ) ;
}
private static double ClampCurveSample ( double value )
{
if ( value < 0 )
return 0 ;
return value > 1 ? 1 : value ;
}
private static string ReadChildString ( AetheriaRuntimeBehaviorValue ? value , int index )
{
return ReadChildValue ( value , index ) ? . StringValue ? ? "" ;
}
private static double ReadChildNumber ( AetheriaRuntimeBehaviorValue ? value , int index )
{
return ReadChildValue ( value , index ) ? . NumberValue ? ? 0 ;
}
private static AetheriaRuntimeBehaviorValue ? ReadChildValue (
AetheriaRuntimeBehaviorValue ? value ,
int index )
{
return value ? . Children ! = null & & value . Children . Count > index ? value . Children [ index ] : null ;
}
2026-06-21 09:37:54 +01:00
private static AetheriaRuntimeSurfaceComponent ButtonRow (
string id ,
params AetheriaRuntimeSurfaceComponent [ ] children )
{
return Node ( id , "row" , Array . Empty < ( string Key , string Value ) > ( ) , children ) ;
}
private static AetheriaRuntimeSurfaceComponent Node (
string id ,
string kind ,
IEnumerable < ( string Key , string Value ) > props ,
params AetheriaRuntimeSurfaceComponent [ ] children )
{
return new AetheriaRuntimeSurfaceComponent (
id ,
kind ,
props . ToDictionary ( prop = > prop . Key , prop = > prop . Value , StringComparer . Ordinal ) ,
children ? ? Array . Empty < AetheriaRuntimeSurfaceComponent > ( ) ) ;
}
}
}