Rules
Gnosis Rules provide a powerful, data-driven system for reacting to engine events and modifying the engine’s behavior. Rules are typically defined in JSON and loaded into the Persistent.configuration.rules domain.
How Rules Work with Events
Rules do not exist in isolation; they are built to react to events. Every rule definition contains a trigger field that matches a specific Event ID.
- The Event Fires: A system publishes an event (e.g.,
"combat.deal_damage") to theGnosisEventBus. - The Match: The Engine looks for all rules where
trigger == "combat.deal_damage". - The Evaluation: For every matching rule, the engine builds an Evaluation Context (using the event’s payload) and checks the rule’s Conditions.
- The Outcome: If conditions pass, the rule’s Outcomes are applied.
Example Rule Definition
Below is a simplified JSON representation of a rule that “intercepts” a combat event to increase damage:
{
"id": "iron_sword_boost",
"category": "INTERCEPTOR",
"trigger": "COMBAT_DEAL_DAMAGE",
"conditions": [
{ "path": "payload.damageType", "op": "EQUALS", "value": "PHYSICAL" }
],
"outcomes": [
{ "type": "MATH_ADD", "target": "amount", "value": 5 }
]
}In this case:
trigger: The rule “wakes up” only when the engine publishes theCOMBAT_DEAL_DAMAGEevent.category: Being anINTERCEPTOR, it will modify theamountvalue inside the event payload before other systems see it.conditions: It only adds the damage if thedamageTypeis"PHYSICAL".
Note: For the rest of this document, we assume an event has been fired and a rule has been triggered. The examples will focus on how that rule then processes data and performs actions.
Rule Categories
Every rule belongs to one of three categories, which determines how its outcomes are applied:
| Category | Description | Common Use Case |
|---|---|---|
| Interceptor | Directly modifies the event’s payload before it is dispatched to subscribers. | Buffs/debuffs that change damage or cost. |
| Observer | Listens for an event and potentially fires a new event in response. | Unlocking achievements or granting bonuses. |
| Trigger | Executes a specific engine action by calling a registered service or function. | Playing sound effects, spawning VFX, or triggering UI. |
Category Examples
Interceptor: Damage Multiplier
Trigger: combat.deal_damage
{
"category": "INTERCEPTOR",
"outcomes": [
{ "type": "MATH_MULTIPLY", "target": "amount", "value": 1.5 }
]
}Observer: Achievement Unlock
Trigger: combat.enemy_killed
{
"category": "OBSERVER",
"conditions": [
{ "path": "persistent.stats.kills", "op": "EQUALS", "value": 100 }
],
"outcomes": [
{ "type": "FIRE_EVENT", "target": "achievement.unlock", "value": { "id": "centurion" } }
]
}Trigger: Play Sound Effect
Trigger: ui.button_click
{
"category": "TRIGGER",
"outcomes": [
{ "type": "CALL_SERVICE", "target": "Audio.PlaySfx", "value": { "clipId": "click_01" } }
]
}Evaluation Context
When a rule is evaluated, it has access to a context containing:
payload: The data associated with the triggering event.ephemeral: Current session-only state.persistent: Permanent state (save-data).parameters: Local parameters defined within the rule itself.calculations: Results from math steps defined in the rule.
Conditions
Conditions determine if a rule’s outcomes should be executed. A rule passes if all its conditions are met.
Path-Based Conditions
Each condition typically consists of a path, an operator, and a value.
| Operator | Description |
|---|---|
EQUALS | Checks if the value at the path equals the specified literal or context-bound value. |
NOT_EQUALS, NE | Inequality check. |
EXISTS | Checks if the path points to a valid GnosisNode. |
NOT_EXISTS | Checks if the path is invalid. |
CONTAINS | For strings, check for substring. For lists, check if the list contains the value. |
GT, LT, GTE, LTE | Numeric comparisons (Greater Than, Less Than, etc.). |
Random Gate
The CHANCE_PERCENT operator provides a built-in random gate. It ignores the path and uses the specified value as a percentage (0-100).
- Example:
{ "op": "CHANCE_PERCENT", "value": 15 }will pass 15% of the time.
Calculations
Rules can define a list of ordered math steps in a calculations array. Each calculation’s result is stored in the context under calculations.<name> for use in conditions or outcomes.
Math Operators
Available operators for calculations:
+(orADD)-(orSUB)*(orMUL,×)/(orDIV)%(orMOD,REM)RANDOM_INT(orRNG_INT,DICE): Returns a random integer between two inclusive bounds.
Example: Complex Calculation
"calculations": [
{
"name": "base_damage",
"fromContext": "payload.amount"
},
{
"name": "total_bonus",
"op": "*",
"args": [
{ "fromContext": "persistent.player.strength" },
0.5
]
},
{
"name": "final_damage",
"op": "+",
"args": [
{ "fromContext": "calculations.base_damage" },
{ "fromContext": "calculations.total_bonus" }
]
}
]Outcomes
Outcomes are the actions taken when all conditions pass. Their behavior depends on the rule’s Category.
Interceptor Outcomes
Modify the current event payload.
SET: Overwrite a value at a target path.MATH_ADD: Add a value to a numeric target.MATH_MULTIPLY: Multiply a numeric target by a factor.
Observer Outcomes
FIRE_EVENT: Publishes a new event to the bus.target: The new event ID.value: An optional object payload.
Trigger Outcomes
CALL_FUNCTION(orCALL_SERVICE): Executes an engine function.target:ServiceId.FunctionName(e.g.,"Audio.PlaySound").value: Parameters passed to the function.
Binding Values (fromContext)
Rather than hard-coding literal values, you can bind values from the evaluation context using the fromContext key.
Outcome Binding Features
fromContext: The path to resolve.default: A literal value or another binding to use if the path is invalid.scale: A numeric factor to multiply the resolved value by.round: One offloor,ceil, orround(default) for integer coercion.
Example: Advanced Outcome Binding
{
"type": "SET",
"target": "modifiedAmount",
"value": {
"fromContext": "calculations.final_damage",
"scale": 1.1,
"round": "ceil"
}
}Rule Ordering
The execution order of rules is determined by:
- ExecutionOrder: An optional integer (lower values run first).
- Registration Order: Ties are broken by the order in which rules were added to the engine.
If you don’t specify an executionOrder, the engine automatically assigns it in increments of 10.