Object Flow integration
Components are not a separate scripting language—they plug into the same Object Flow graphs you already use for taps, drags, and scene logic.
Node catalog
When an object is selected in Object Flow, the node library includes a Components group built from:
- Attached components on that object (actions + conditions).
- Optionally, catalog entries derived from the global registry (see
catalog.js).
Each catalog item becomes either:
| Catalog kind | Flow node action.type / condition.type | Label example |
|---|---|---|
componentAction | componentAction | health.damage |
componentCondition | componentCondition | health.isAlive |
Payload shape for actions:
{
"type": "componentAction",
"component": "health",
"method": "damage",
"params": { "amount": 10 }
}Payload shape for conditions:
{
"type": "componentCondition",
"component": "health",
"method": "isAlive",
"params": {}
}The Flow Inspector renders dropdowns for component, method, and typed parameters based on the definition schema.
Core graph nodes use the same modular pattern through defineFlowNode() and registerFlowNode(). A registered flow node owns its catalog item, creation defaults, normalization, canvas layout, labels, and graph-lint behavior. Built-ins are registered from src/game/flow/nodes/, and project/plugin node modules can register additional node types without editing the central type list.
Wired action parameters
Registry actions (moveTo, positionAbove, snapToTarget, …) expose param: data pins derived from their field schema. Connect a Get Value node (for example Active Turn Object Id or Event Payload Field) into param:targetObjectId or param:objectId so the target updates at runtime without duplicating rules per combatant.
The Position Above action snaps or tweens an object above another using each thing’s height plus an optional offsetY.
Runtime execution
rules.js dispatches during graph evaluation:
- Conditions:
thing.components.check(component, method, params, eventCtx) - Actions:
thing.components.run(component, method, params, eventCtx)
eventCtx carries event, source, and target from the active rule so actions can respect interaction context.
The host object for the graph is the Thing that owns the rule. Component actions always target that Thing’s ComponentHost unless your action implementation resolves another object (e.g. Team conditions with objectRef params).
Expressions in graph fields
Many flow node fields accept inline expressions prefixed with =:
=self.hp > 0
=params.amount * 2
=Math.min(self.hp, self.maxHp)Evaluator: src/game/components/expressions.js (evalValue).
Available bindings
| Identifier | Description |
|---|---|
self | Component fields when the node targets a componentAction / componentCondition; otherwise null |
params | Node parameter bag |
event | Active event payload (includes payload for custom / component events) |
source / target | Interaction actors |
thing | Resolved object for the node (objectId or event actor) |
vars | Binding/scripting view of state (vars.counters / vars.flags still resolve for expressions; prefer declared GameState and object variables) |
get(id) | Sibling component fields on thing, e.g. get("health")?.hp |
Math | Standard Math object |
Event Payload Getters
Component events can declare a typed payload:
events: {
damaged: {
label: "Damaged",
payload: {
amount: { type: "number", label: "Amount" },
sourceId: { type: "objectRef", label: "Source" },
},
},
}Object Flow uses that schema to populate Event Data getter nodes and typed data pins on explicit event nodes for the active component event graph. The persisted getter nodes stay generic getValue records with valueSource.source === "eventField" and paths such as payload.amount.
Event nodes are explicit start nodes, not a generated list of every possible rule. A designer places or opens the event they need, such as On Tap for a button object or On Event for start_game, then wires that event node into actions like Emit Event. Other objects can subscribe by placing/opening their own explicit On Event graph for the same key.
Opening an object's Flow Graph shows the explicit event nodes that have been placed on that object. It does not create a node for every event type or every legacy rule. Right-clicking the canvas can place On Tap, On Event, or a component-defined event; action nodes belong to whichever event flow they are added to or connected from.
Value nodes and data wires
Object Flow supports typed value wires alongside exec wires:
| Node | Purpose |
|---|---|
Number / Text / Boolean (literal) | Constant value on the graph |
Get (getValue) | Read GameState / scene variables, object variables, component fields, event fields, or active turn id |
Set Value (setValue) | Write GameState / object variables or exposed component fields when exec reaches the node |
Connect a value node's value pin (right) to an action's param: pin (left), e.g. param:amount on health.damage. Data edges use gold cables; exec edges stay white/green.
Implementation: src/game/flow/bindings.js, src/game/flow/ports.js.
Component fields can opt into setter nodes:
fields: {
hp: { type: "number", default: 10, flow: { set: true } },
}Fields, params, actions, conditions, and events can also use flow.expose: false to stay out of the graph catalog. Action params can use flow.pin: false to remain editable without creating a data pin.
Safety rules
Expressions must be single expressions (no ;, no if/for, no new, no eval/Function, no window/document). Failures log a warning and evaluate to undefined so play mode keeps running.
Use Script action nodes when you need multiple statements.
Script action node
Flow node type: action.type === "script".
Body is a multi-statement script compiled with compileScript:
// Example body in Flow Inspector
const hp = thing.components.get("health")?.fields;
if (hp && hp.hp > 0) {
thing.components.run("health", "damage", { amount: params.power || 5 });
}Available: self, params, event, source, target, thing, components.
Expression condition node
Flow node type: condition.type === "expression".
Body is one expression returning truthy/falsey:
self?.hp > 0Branches use standard true / false ports to chain actions.
Subscribing to component events (graphs)
Component events (e.g. health.died) are declared on the definition. To react in graphs:
- Use On Event / custom event nodes where the engine bridges component emissions into the rule system, or
- Use sibling handlers on another component on the same object (see Code authoring), or
- Call follow-up actions in the same graph after a componentAction node.
Health Bar’s health.damaged handler is an example of (2)— no graph required for the flash.
Scene Flow vs Object Flow
| Scope | Graph location | Component nodes |
|---|---|---|
| Per object | Object Flow | Yes — catalog from attachments |
| Scene-wide | Scene Flow | Use Script / scene actions unless scene rules gain component targets |
For battle controllers, either attach Turn Queue to a dedicated object and use Object Flow on that object, or invoke component actions from scene rules via script that looks up the controller by id.
Empty graphs and “On Tap”
The editor only counts flows with real logic when showing “events with logic,” and avoids auto-creating empty On Tap rules. Component nodes count as logic once placed on the canvas.
Debugging checklist
| Symptom | Check |
|---|---|
| Node missing in catalog | Component attached? Registry loaded? |
| Action no-op | Method name typo? Params coerced? invulnerable? |
| Condition always false | Params default? Wrong object ref? |
| Expression undefined | Missing =? Forbidden token? |
| Works in editor, not play | Play reset restored fields? onPlayStart refill? |
| Warning badge | Missing component/action/condition/event/field, stale param, or component not attached |
Related files
src/game/components/catalog.js— catalog → flow nodesrc/game/flow/define.js— flow node definition helpersrc/game/flow/registry.js— flow node registry and lookup helperssrc/game/flow/nodes/— built-in modular flow node registrationssrc/game/interaction/inspector.js— component + script editorssrc/game/interaction/model.js— node labels (health.damage)src/game/rules/runner.js— runtime dispatch
Back to Getting started · Built-in reference