diff --git a/Forge.Tests/Statescript/ExpressionResolverTests.cs b/Forge.Tests/Statescript/ExpressionResolverTests.cs
index 2b29780..ae1c2f3 100644
--- a/Forge.Tests/Statescript/ExpressionResolverTests.cs
+++ b/Forge.Tests/Statescript/ExpressionResolverTests.cs
@@ -34,7 +34,7 @@ public void Comparison_resolver_greater_than_returns_true_when_left_exceeds_righ
ComparisonOperation.GreaterThan,
new VariantResolver(new Variant128(10.0), typeof(double))));
- var condition = new ExpressionConditionNode("isAboveThreshold");
+ var condition = new ExpressionNode("isAboveThreshold");
var trueAction = new TrackingActionNode();
var falseAction = new TrackingActionNode();
@@ -71,7 +71,7 @@ public void Comparison_resolver_greater_than_returns_false_when_left_is_less()
ComparisonOperation.GreaterThan,
new VariantResolver(new Variant128(10.0), typeof(double))));
- var condition = new ExpressionConditionNode("isAboveThreshold");
+ var condition = new ExpressionNode("isAboveThreshold");
var trueAction = new TrackingActionNode();
var falseAction = new TrackingActionNode();
@@ -108,7 +108,7 @@ public void Comparison_resolver_equal_returns_true_for_matching_values()
ComparisonOperation.Equal,
new VariantResolver(new Variant128(42.0), typeof(double))));
- var condition = new ExpressionConditionNode("isEqual");
+ var condition = new ExpressionNode("isEqual");
var trueAction = new TrackingActionNode();
var falseAction = new TrackingActionNode();
@@ -148,7 +148,7 @@ public void Comparison_resolver_compares_two_graph_variables()
ComparisonOperation.GreaterThan,
new VariableResolver("threshold", typeof(double))));
- var condition = new ExpressionConditionNode("isHealthAboveThreshold");
+ var condition = new ExpressionNode("isHealthAboveThreshold");
var trueAction = new TrackingActionNode();
var falseAction = new TrackingActionNode();
@@ -185,7 +185,7 @@ public void Comparison_resolver_less_than_or_equal_at_boundary()
ComparisonOperation.LessThanOrEqual,
new VariantResolver(new Variant128(10.0), typeof(double))));
- var condition = new ExpressionConditionNode("atBoundary");
+ var condition = new ExpressionNode("atBoundary");
var trueAction = new TrackingActionNode();
var falseAction = new TrackingActionNode();
@@ -222,7 +222,7 @@ public void Comparison_resolver_not_equal_returns_true_for_different_values()
ComparisonOperation.NotEqual,
new VariantResolver(new Variant128(2.0), typeof(double))));
- var condition = new ExpressionConditionNode("isDifferent");
+ var condition = new ExpressionNode("isDifferent");
var trueAction = new TrackingActionNode();
var falseAction = new TrackingActionNode();
@@ -253,7 +253,7 @@ public void Expression_condition_node_returns_false_for_missing_property()
{
var graph = new Graph();
- var condition = new ExpressionConditionNode("nonexistent");
+ var condition = new ExpressionNode("nonexistent");
var trueAction = new TrackingActionNode();
var falseAction = new TrackingActionNode();
@@ -293,7 +293,7 @@ public void Comparison_resolver_works_with_int_operands()
ComparisonOperation.GreaterThanOrEqual,
new VariableResolver("requiredScore", typeof(int))));
- var condition = new ExpressionConditionNode("hasEnoughScore");
+ var condition = new ExpressionNode("hasEnoughScore");
var trueAction = new TrackingActionNode();
var falseAction = new TrackingActionNode();
@@ -334,7 +334,7 @@ public void Comparison_resolver_works_with_attribute_resolver()
ComparisonOperation.GreaterThanOrEqual,
new VariableResolver("required", typeof(int))));
- var condition = new ExpressionConditionNode("hasEnoughAttribute");
+ var condition = new ExpressionNode("hasEnoughAttribute");
var trueAction = new TrackingActionNode();
var falseAction = new TrackingActionNode();
diff --git a/Forge/Statescript/Node.cs b/Forge/Statescript/Node.cs
index ce43882..fc52fd9 100644
--- a/Forge/Statescript/Node.cs
+++ b/Forge/Statescript/Node.cs
@@ -19,6 +19,11 @@ public abstract class Node
/// The list to add output ports to.
protected abstract void DefinePorts(List inputPorts, List outputPorts);
+ ///
+ /// Gets a description of the node type, which is used in editor tooltips and documentation.
+ ///
+ public virtual string Description => $"{GetType().Name.Replace("Node", string.Empty)} node.";
+
///
/// Gets or sets the unique identifier for this node.
///
diff --git a/Forge/Statescript/Nodes/Action/SetVariableNode.cs b/Forge/Statescript/Nodes/Action/SetVariableNode.cs
index 1969a4f..1c7e9e2 100644
--- a/Forge/Statescript/Nodes/Action/SetVariableNode.cs
+++ b/Forge/Statescript/Nodes/Action/SetVariableNode.cs
@@ -23,6 +23,9 @@ public class SetVariableNode(StringKey sourcePropertyName, StringKey targetVaria
private readonly StringKey _targetVariableName = targetVariableName;
+ ///
+ public override string Description => "Sets a graph variable to the value of a property.";
+
///
protected override void Execute(GraphContext graphContext)
{
diff --git a/Forge/Statescript/Nodes/ActionNode.cs b/Forge/Statescript/Nodes/ActionNode.cs
index 12b045e..fd44ee4 100644
--- a/Forge/Statescript/Nodes/ActionNode.cs
+++ b/Forge/Statescript/Nodes/ActionNode.cs
@@ -26,6 +26,9 @@ public abstract class ActionNode : Node
/// The current graph context.
protected abstract void Execute(GraphContext graphContext);
+ ///
+ public override string Description => $"A {GetType().Name.Replace("Node", string.Empty)} action node.";
+
///
#pragma warning disable SA1202 // Elements should be ordered by access
internal override IEnumerable GetReachableOutputPorts(byte inputPortIndex)
diff --git a/Forge/Statescript/Nodes/Condition/ExpressionConditionNode.cs b/Forge/Statescript/Nodes/Condition/ExpressionNode.cs
similarity index 86%
rename from Forge/Statescript/Nodes/Condition/ExpressionConditionNode.cs
rename to Forge/Statescript/Nodes/Condition/ExpressionNode.cs
index 1fecf55..cf57b3e 100644
--- a/Forge/Statescript/Nodes/Condition/ExpressionConditionNode.cs
+++ b/Forge/Statescript/Nodes/Condition/ExpressionNode.cs
@@ -17,10 +17,13 @@ namespace Gamesmiths.Forge.Statescript.Nodes.Condition;
///
/// The name of the graph property that provides the condition result. Must resolve
/// to a value.
-public class ExpressionConditionNode(StringKey conditionPropertyName) : ConditionNode
+public class ExpressionNode(StringKey conditionPropertyName) : ConditionNode
{
private readonly StringKey _conditionPropertyName = conditionPropertyName;
+ ///
+ public override string Description => "Evaluates the given property to determine which port to activate.";
+
///
protected override bool Test(GraphContext graphContext)
{
diff --git a/Forge/Statescript/Nodes/ConditionNode.cs b/Forge/Statescript/Nodes/ConditionNode.cs
index 4dabde9..7f0544e 100644
--- a/Forge/Statescript/Nodes/ConditionNode.cs
+++ b/Forge/Statescript/Nodes/ConditionNode.cs
@@ -32,6 +32,9 @@ public abstract class ConditionNode : Node
/// if the condition is met; otherwise, .
protected abstract bool Test(GraphContext graphContext);
+ ///
+ public override string Description => $"A {GetType().Name.Replace("Node", string.Empty)} condition node.";
+
///
#pragma warning disable SA1202 // Elements should be ordered by access
internal override IEnumerable GetReachableOutputPorts(byte inputPortIndex)
diff --git a/Forge/Statescript/Nodes/EntryNode.cs b/Forge/Statescript/Nodes/EntryNode.cs
index f25a834..c8cf871 100644
--- a/Forge/Statescript/Nodes/EntryNode.cs
+++ b/Forge/Statescript/Nodes/EntryNode.cs
@@ -15,6 +15,9 @@ public class EntryNode : Node
///
public const byte OutputPort = 0;
+ ///
+ public override string Description => "Entry point of the graph. Emits a message to start execution.";
+
///
/// Starts the graph execution by emitting a message through the output port.
///
diff --git a/Forge/Statescript/Nodes/ExitNode.cs b/Forge/Statescript/Nodes/ExitNode.cs
index 7eefa8a..72cbce0 100644
--- a/Forge/Statescript/Nodes/ExitNode.cs
+++ b/Forge/Statescript/Nodes/ExitNode.cs
@@ -19,6 +19,9 @@ public class ExitNode : Node
///
public const byte InputPort = 0;
+ ///
+ public override string Description => "Exit point of the graph. Stops execution when a message is received.";
+
///
internal override IEnumerable GetReachableOutputPorts(byte inputPortIndex)
{
diff --git a/Forge/Statescript/Nodes/State/TimerNode.cs b/Forge/Statescript/Nodes/State/TimerNode.cs
index 315f317..f345c9e 100644
--- a/Forge/Statescript/Nodes/State/TimerNode.cs
+++ b/Forge/Statescript/Nodes/State/TimerNode.cs
@@ -21,6 +21,9 @@ public class TimerNode(StringKey durationPropertyName) : StateNode
+ public override string Description => "Remains active for a configured duration, then deactivates.";
+
///
protected override void OnActivate(GraphContext graphContext)
{
diff --git a/Forge/Statescript/Nodes/StateNode.cs b/Forge/Statescript/Nodes/StateNode.cs
index 07cdf19..ba6c654 100644
--- a/Forge/Statescript/Nodes/StateNode.cs
+++ b/Forge/Statescript/Nodes/StateNode.cs
@@ -57,6 +57,9 @@ public abstract class StateNode : Node
/// The graph's context.
protected abstract void OnDeactivate(GraphContext graphContext);
+ ///
+ public override string Description => $"A {GetType().Name.Replace("Node", string.Empty)} state node.";
+
///
/// Updates this state node with the given delta time. Only processes the update if the node is currently active.
///