Steps
Reads are expressions; writes are steps. The verbs that change the world.
Reading the world is an expression. Changing it is a step, and a rule body is a list of steps.
$.adjustField({ field: "satiety", by: -1 })
$.damage({ amount: 3 })
$.narrate("the wound closes over")
The vocabulary
| Step | What it does |
|---|---|
$.adjustField({ field, by }) |
Move a field by an amount |
$.setField({ field, to }) |
Set a field to a value |
$.adjustStat({ stat, by }) |
Move a stat by an amount |
$.setStat({ stat, to }) |
Set a stat to a value |
$.damage({ amount }) |
Deal damage |
$.heal({ amount }) |
Heal |
$.applyEffect({ effect }) |
Apply a status effect by name |
$.give({ item, quantity }) |
Give an item |
$.grantXp(n) |
Award experience |
$.setWorldState({ key, value }) |
Write a world flag |
$.passTime(minutes) |
Advance the clock |
$.narrate(text) |
Tell the narrator something happened |
$.refuse(reason) |
Stop the player's action |
$.veto(reason) |
Stop one proposed event |
Most steps take an optional target — "subject", "player", "target" — defaulting to
whoever the rule is about.
by vs to
adjustField moves a value by an amount. setField sets it to one.
$.adjustField({ field: "fever", by: 1 }) // one worse than before
$.setField({ field: "fever", to: 1 }) // exactly 1, whatever it was
Same pair for stats: adjustStat / setStat. Getting these backwards is a quiet bug —
a fever that never climbs past 1 rather than one that never stops climbing.
refuse vs veto
refuse stops the action the player attempted.
veto stops one specific event that was proposed.
If you are answering another plugin's event — rest:sleep, say — use veto. Using
refuse there tells a player standing still that hunger clawed them out of a sleep they
never started.
Narration never sees numbers
$.narrate("the fever's grip loosens") // good
$.narrate("fever reduced by 2") // wrong shape
The narrator is told what happened in prose, never the mechanics. Numbers, cooldowns and turn counts do not reach it — that is deliberate, and it is why the writing stays in voice. Write the sentence you want a player to read.
Zero-rate damage still costs a point
$.damage({ amount: 0 }) does not do nothing — damage floors at 1. If a dial can legitimately
be tuned to zero, branch around the step rather than passing zero into it:
if ($.dial('starvationDamagePerHour') > 0) {
$.damage({ amount: $.dial('starvationDamagePerHour') })
}
Next
Rule bodies — the syntax rules a body must obey, which are stricter than they look.