Rule bodies
A rule body is not free JavaScript — every line is a step call or an if.
This is the rule that catches everyone once, human and AI alike.
Every line in a rule body must be a step call or an if. No variables. No loops. No
function declarations.
What works
if ($.player.fields.satiety <= 0) {
if ($.dial('starvationDamagePerHour') > 0) {
$.damage({ amount: $.dial('starvationDamagePerHour') })
}
}
Branching is fine. Nesting is fine. Arithmetic inside an argument is fine.
What is rejected
var rate = $.dial('starvationDamagePerHour')
$.damage({ amount: rate })
var is not a step. And it does not fail alone — it takes the whole body with it. The
rule does not partially run; it does not run.
That is the trap: you get a rule that silently never fires, and the cause is a line that looks like ordinary, correct JavaScript.
Say it again without the variable
Whatever you wanted the variable for, put it in the argument:
$.damage({ amount: $.dial('starvationDamagePerHour') })
If you used it twice, write it twice. Bodies are short by design; the duplication you are avoiding costs less than the rule not running.
The conditional operator is fine inside an argument
// WORKS — ?: lives inside the argument
$.adjustField({ field: "satiety", by: -($.timed
? ($.event.minutes / 60) * $.dial('foodDrainPerHour')
: $.dial('foodDrainPerTurn')) })
It is a ?: used as a statement, outside any argument, that fails.
When you genuinely need code
$.script(`
for (var i = 0; i < 3; i++) { $.damage(1) }
`)
$.script() takes a real block of JavaScript. The cost: you lose the round-trip to the
visual editor, and the body becomes opaque to anyone editing with steps. Reach for it last.
Inside a script, the verbs are positional rather than object-shaped, and the list is shorter:
$.damage(...)$.heal(...)$.adjustField(...)$.setField(...)$.narrate(...)$.refuse(...)$.giveItem(...)$.setWorldState(...)$.passTime(...)
A script may record at most 200 steps. That is a bound on runaway loops, not a budget to spend — real rules record one or two.
Code view and step view are one rule
The editor shows a rule either as visual steps or as code. They are two renderings of the same artifact — editing either edits the same rule.
The round-trip is lossless or refused: roll tables (whose entries are not in the text) and step types the renderer cannot yet write stay read-only rather than being guessed at. You will never silently lose a step by switching views.
Image:
docs/rule-code-and-steps.pngThe same rule shown twice side by side — the visual step list and the code view — so the correspondence between one step and one line is obvious.
Next
Hooks — when your rule body actually runs.