Formula Variables (formula_variable)
A formula variable is a built-in name that pulls a number out of the objects the formula is evaluated against. mxt:formula_variable is a code registry like the action, condition and number provider families: a data pack cannot add entries or configure existing ones, and there is no JSON file behind it.
Variables are read on demand. Nothing is computed when a context is created, so an expression only pays for the names it actually mentions. Values that are not a property of an object — the damage of a hit, the coordinates of a broken block, a flag such as breakthrough — are not variables; they are explicit values that the caller puts into the context.
Lookup Order
Reading a name resolves in this order:
- The explicit value, if the context carries one. This covers event payloads, values the mod adds for one evaluation, and the
paramsof the expression itself. - The variable registry, which resolves the name from the context objects.
- Nothing, which is a content bug and is reported as described below.
params is the highest priority and only affects the one expression that declares it. See Expression Strings.
Unknown Names and Failures
A misspelled name, a name that the context cannot provide, or a variable that fails is reported at runtime, because nothing can decide it while the data pack is parsed:
| Situation | Development environment | Production |
|---|---|---|
The name is unknown, or the context cannot provide it (for example caster_health in a context that has no caster) | Full ERROR log with the failing name | One warning line per distinct message, the expression continues with 0 |
A variable produces NaN or infinity | Full ERROR log | One warning line, 0 |
| A variable throws while reading an object | Full ERROR log, including the exception and its stack trace | One warning line, 0 |
None of them stops the evaluation: the expression continues with 0, so a bad name cannot abort the ability or the tick that used it.
A formula that is broken in a way the codec can see is a decode error instead: an empty or malformed expression, invalid params, a weighted list with no entries, or a context variable with a blank name. The loader collects every failing entry of that load and reports them together, then the whole load fails.
Unknown names are not silent
A name that no variable provides never quietly becomes 0. A development environment prints the whole error at the first evaluation, and production keeps one warning line per distinct message, so a typo can still be found in a server log.
Reading a name that only some contexts provide is not an error by itself. realm_rank is perfectly valid in a resource formula and unavailable in an ability formula; using it in the ability formula is what gets reported.
Registry Variables
These two names work in every context, including a context with no objects at all:
| Variable | Description |
|---|---|
zero | Always 0, for switching a term off without editing the expression |
random | A new random double between 0 and 1, drawn from the authoritative random source of the context |
random is authoritative because the context carries the random source of the entity or the level it was built from. Never re-roll a value on the client to decide a game result.
Entity Variables
A context built from an entity provides two families, one for the acting entity (caster_) and, in a bi-entity formula, one for the second entity (target_):
| Variable | Description |
|---|---|
caster_health / target_health | Current health, only when the entity is a living entity |
caster_max_health / target_max_health | Maximum health, only when the entity is a living entity |
caster_level / target_level | Vanilla experience level when the entity is a player, otherwise 0 |
caster_<resource> / target_<resource> | Current amount of that resource; 0 when the entity does not hold it |
caster_<attribute> / target_<attribute> | Current value of that attribute |
caster_element_<element> / target_element_<element> | 1 when the entity's spirit roots name that element, otherwise 0 |
caster_element_count / target_element_count | How many elements the entity's spirit roots name |
<resource> and <attribute> are the registry ID with the namespace and the path joined by _; /, . and - inside the path also become _:
| Registry ID | Variable |
|---|---|
mxt:common | caster_mxt_common |
example:fire/qi | caster_example_fire_qi |
minecraft:max_health | caster_minecraft_max_health |
minecraft:attack_damage | caster_minecraft_attack_damage |
example:fire (an element) | caster_element_example_fire |
Every resource and attribute of the loaded registries can be named this way, whether or not the entity currently uses it. Because two different IDs can flatten to the same name (example:fire_qi and example_fire:qi both become example_fire_qi), a collision is reported instead of being resolved silently.
On the client only the attributes that the client receives are readable; an attribute the client does not track reads 0, so a client-side preview never invents a value the server has not synchronized.
caster_level is not the realm
caster_level is the vanilla experience level. The realm rank is the separate level / realm_rank variable described below, and it only exists in a resource context.
Resource Variables
Formulas evaluated for one value — resource.max, resource.bars, and the fields of its aura definition such as regen and the conversions, plus resource costs, realm stages and breakthrough thresholds — carry the cultivation state of that value as well:
| Variable | Description |
|---|---|
realm | Rank of the current realm in this resource's chain, counting a mortal as 0 so the first realm is 1 and each stage above it follows; also 0 when the current stage is not on the chain |
realm_rank | Same value as realm |
level | Same value as realm, kept as a shorthand for realm formulas |
absorbed_aura | Accumulated cultivation progress of this resource; 0 when the chain does not match |
cultivation_progress | Same value as absorbed_aura |
minor_stage | 0-based index of the sub-stage this progress sits in, cut evenly out of the target stage's breakthrough_exp (the count comes from the current stage); NaN when that realm names no sub-stages, when there is no realm yet, or when the chain has run out |
A value's max and its aura definition's regen can therefore write:
// data/example/mxt/resource/qi.json
{
"max": "100 + realm_rank * 50 + absorbed_aura * 0.5"
}// data/example/mxt/aura/qi.json
{
"resource": "example:qi",
"regen": "0.25 + realm_rank * 0.1"
}Values That Are Not Variables
These names are provided as explicit context values by the system that starts the evaluation, so they are only readable where that system runs.
Ability Values
| Variable | Available when | Description |
|---|---|---|
element_modifier | The ability declares a non-empty element_affinity | The element affinity multiplier computed for the caster. Layer one of the damage system multiplies this value into the damage that cast deals itself, so a damage formula must not write * element_modifier by hand — that would be the same number multiplied twice. It is still readable where the value is not damage, such as costs or durations |
damage_multiplier | The cast belongs to a progression chain that grants the ability | The damage_multiplier of the level the caster stands on, which the damage system also applies to the damage this cast deals. A physique's own damage_dealt_multiplier / damage_taken_multiplier are not put into the formula context: only the pipeline reads them |
aura_radius | mxt:aura evaluates its target action | The radius resolved for this pulse |
distance | mxt:aura evaluates its target action | Distance in blocks between the caster and the current target |
Trigger Values
An ability with the mxt:triggered type is evaluated when its trigger fires, and the trigger adds these names:
| Trigger | Variables added | Description |
|---|---|---|
tick | — | Entity context only |
attack | target_is_living, target_health | target_is_living is 1 when the attacked entity is a living entity |
hurt | damage | Damage that was actually inflicted on the caster |
kill | target_health | Health of the killed entity at the moment of death |
death | victim_health | Health of the dying entity, floored at 0 |
block_break, block_use | block_x, block_y, block_z | Coordinates of the block involved |
item_use | use_duration | Ticks the used item took to finish |
equip | equipment_slot | Ordinal of the changed equipment slot |
breakthrough | breakthrough | Always 1, so it can be used as a flag |
progression_level | level | Zero-based rank of the level that was just reached; the signal also carries the owner ID as an extension value for scripted matchers |
Values From Other Systems
| Variable | Provided by | Description |
|---|---|---|
damage | Contract combat actions | Damage the contracted spirit beast just dealt |
formation_radius | A formation's per-entity beats | Radius of the active formation |
distance | A formation's per-entity beats | Distance in blocks between the formation centre and the entity |
aura_tribulation_modifier | Tribulation timeline | Local aura influence, taken from the tribulation_modify rule of the aura zone and sampled once when each timeline entry begins |
Secret Realm Values
Any formula evaluated for a member of a secret realm can read the instance itself — most usefully the definition's own enter_condition, exit_condition, enter_action and exit_action:
| Variable | Description |
|---|---|
secret_realm_members | How many members are inside the instance right now |
secret_realm_limit | The instance's member cap, taken from max_members; -1 when it is unlimited |
secret_realm_elapsed | Ticks since the current visit began |
secret_realm_duration | The configured duration_ticks |
secret_realm_index | The index of this instance, counted from 0 |
secret_realm_is_owner | 1 when the reading entity owns the instance, otherwise 0 |
The names carry the secret_realm_ prefix because realm, realm_rank and level already mean a cultivation stage. Outside any secret realm they cannot be provided at all, so they are reported as an error instead of silently reading as 0.
Lifespan Values
A context built from an entity can read that entity's lifespan ledger through two names, both measured in ticks:
| Variable | Description |
|---|---|
lifespan_remaining | Ticks of life left before the lifespan runs out |
lifespan_total | The ceiling this life was granted |
They read the entity the formula is evaluated for (the context's caster, or the player when the context has no caster). A body with no ledger reads NaN for both, so a condition can tell "never accounted for" from "accounted for and already spent", which reads 0. How a ledger comes to exist is on Lifespan.
Pill Toxicity and Herb Age
| Variable | Available when | Description |
|---|---|---|
pill_toxicity | The context has an entity | Pill toxicity accumulated on the entity the formula is evaluated for. An entity that has never taken a pill reads 0 and does not gain an empty record for it; in a Level-only or empty context this name cannot be provided at all. |
herb_age | Only inside that herb's own formulas | The herb's age, a non-negative integer. It is local: it is injected into main_effects, auxiliary_effects and catalyst_power (which read the stack's copy) and also into growth_rate, condition and costs (which read the plant's own age), it cannot be read anywhere else, and the pipeline never multiplies age in a second time. |
Write the age bonus into the expression yourself when a potency formula needs it, for example "3 + herb_age / 50". On the potency side that reads the stack's mxt:herb_age component, falling back to that herb's default_age; on the growth side it reads the plant's current age, the number in the chunk attachment.
Where Each Variable Is Available
Which variables a formula can read is decided by the objects the caller puts into the context. The table below lists the main evaluation sites.
| Formula | Context objects | Variables available |
|---|---|---|
| Ability cast time, cooldown, charges, channel interval, conditions, target selection | caster | Entity family; element_modifier when the ability declares element_affinity (the damage system applies that value to the cast's own damage, so read it here only for non-damage numbers); damage_multiplier when a progression chain granting the ability is known; the payload of the trigger that started the ability |
An ability's target_condition and bi_entity_action (the four action fields are declared and run by each type that runs actions) | caster + target | Entity and target families; the same event payload |
mxt:aura interval and radius | caster | Entity family |
The mxt:resource entry of an ability's costs | caster + the spent value | Entity family + resource family of that value |
An ability's mxt:item cost | caster | Entity family |
Resource default_value, min, max, regen, use condition, burst amount | caster, plus the resource where the definition is evaluated for one resource | Entity family; resource family where a resource is bound |
| Resource conversions, realm-stage thresholds, breakthrough threshold | caster + the resource | Entity family + resource family |
| Cultivate action conditions and amounts | caster, plus a resource context for the per-resource fields | Entity family; resource family where a resource is bound |
| Realm-stage and technique passive attribute modifiers | caster | Entity family |
| Curse duration, tick interval, conditions, actions | caster (or the context of whatever applied the curse) | Entity family, plus the payload of the ability that applied it |
| Item, weapon, pill and technique bindings, item quality, pill toxicity | the user or holder entity | Entity family (including pill_toxicity), plus target_health / target_is_living on a weapon attack |
Spirit herb potency (main_effects, auxiliary_effects, catalyst_power) | The stack itself, plus whatever context the caller supplies | The local herb_age; the entity family when the context carries an entity |
Spirit herb growth (growth_rate, condition, costs) | The Level the plant stands in | The local herb_age (the plant's own age, not the stack's), plus whatever a Level-only context can read |
| A pill recipe's own formula fields (thresholds, temperature, duration, environment gate) | the player starting the batch, plus the core tier's position | The entity family; furnace_rank (where the core tier sits on its own ladder, reading 0 when no core is in hand) |
| Forging, formations, contracts, creature profiles, secret realms, artifacts | the player, owner or creature | Entity family (+ formation_radius / distance for a formation's per-entity beats; + the secret realm family inside an instance) |
| Tribulation timeline entry duration and conditions | caster | Entity family + aura_tribulation_modifier |
Formulas evaluated from a Level alone instead of an entity: a formation's own passes (the block action of mxt:activate / mxt:tick / mxt:inactivate), spirit crafting table costs, KubeJS block actions and conditions | nothing | zero, random only |
| Client-side previews that use an empty context: item and weapon tooltips, item-aura capacity, currency value checks | nothing | zero, random only |
Empty contexts are common
Several display paths evaluate a formula with an empty context, so a definition that reads caster_mxt_common shows the real number in game and 0 in a tooltip. Keep display-only formulas free of entity variables, or accept that they cannot be previewed on the client.
Overriding a Name
mxt:expression accepts params, whose values are themselves NumberProviders and replace the variable of the same name inside that expression only:
{
"type": "mxt:expression",
"expression": "realm_rank * scale + bonus",
"params": {
"scale": 1.5,
"bonus": "caster_minecraft_attack_damage * 0.5"
}
}Use params when one formula must depend on a value the context does not provide, or when the same expression is reused in several tables with different constants.