Writing Expressions

expressions syntax scoring

Writing Expressions

Expressions are the small formulas you write inside .cade config attributes to decide whether something happens and how much it is worth. Anywhere a value should depend on game state — a point award that scales with a multiplier, a rule that only fires during multiball, a variable computed from two others — you write an expression instead of a fixed number.

This page covers the basics: where expressions go, how to reference game state, how the arithmetic behaves, and how conditions gate a rule. For the complete operator and built-in-function reference, see Console Expressions. For the additional noise and randomness functions, see the Noise Generator.

Where Expressions Appear

Several attributes across the scoring and variable system accept an expression rather than a literal value:

AttributeBlockWhat it computes
conditionscore "event", score "modifier"A boolean gate — the rule applies only when it is true
pointsscore "event"The number of points to award
formulacomputed variableThe variable’s value, recomputed from other state
assignments in update / applyscore blocksThe new value to store in a variable after scoring
pitch, velocitysource "synth" in a routeA runtime binding resolved when the synth fires

The when attribute on a scoring rule names the event that triggers it — that is an event reference, not a value to compute, so it is covered by the Score reference rather than here. It still follows the same quoting rule as everything else, below.

One part of that reference is worth knowing before you write your first rule: in device.<identifier>.<action>, the identifier may be a device name or a device subtype, and the two look identical. when = device.bumper.activated fires for every bumper, not for a device named bumper. See Naming the event.

Quoting: Bare by Default

There is one rule for the whole config syntax:

Write every value bare. Quote only when bare can’t express it.

This holds for expressions and names. An event name, a clip, an emitted event — all bare:

score "event" "bumper" {
  when      = device.bumper.hit       # event name — bare
  condition = var.combo_active        # expression — bare
  points    = var.bumper_value * 2
  emit      = bumper_scored
  audio { clip = bumper_pop }
}

A bare name like device.bumper.hit is captured as exactly the string "device.bumper.hit", so there is no behavioral difference — bare is simply the canonical style.

Quote only when it is required:

Must quoteWhyExample
Wildcard / pattern topics* isn’t valid bare syntaxwhen = "*.complete", when = "shot.*"
Durationsno bare duration literaldelay = "50ms", window = "5s"
Names with non-identifier charactershyphens, spaces, leading digits, slashesclip = "loop-1"
A string compared inside an expressionit is a literal operandcondition = var.mode == "wizard"
Block labelsHCL requires quoted labelsscore "event" "bumper_hit"

Everything else — including event names in when / event / signal / emit and resource names in clip — is written bare.

Referencing Game State

Expressions read live game state through a handful of namespaces:

ReferenceResolves to
var.<name>The current value of a game variable
event.<field>A field on the event that triggered the rule, for example event.transit_time or event.target_id
mode.<name>.activeWhether the named mode is currently running
signal.<name>Whether the named signal or detected pattern is active

A reference that cannot be resolved — a missing variable, or an event field that is not present — falls back to its zero value (0, false, or "") rather than failing the rule. This keeps a rule from breaking mid-game over an absent field, but it also means a typo in a name silently reads as zero, so check names when a value comes out unexpectedly low.

Write Expressions Bare

Write an expression directly — unquoted — wherever an attribute expects one. What you type is what the engine evaluates:

points = var.bumper_value * var.combo_multiplier / 100

There is no need to wrap the expression in quotes or in ${...}. The ${...} interpolation form is rejected in expression attributes — points = "${var.bumper_value} * 100" is a configuration error that stops the table from loading. Write it bare:

# Correct
points = var.bumper_value * 100

# Error — ${...} is not accepted in expression fields
points = "${var.bumper_value} * 100"

Migrating from ${...}: Older tables wrote interpolated assignments like update { score = "${var.score} + 100" }. These no longer load. Rewrite them bare: update { score = var.score + 100 }.

Back-compat: A fully quoted expression — condition = "var.x > 5" — still parses and means the same thing as the bare form condition = var.x > 5. It is tolerated for older configs, but bare is canonical and is what every example here uses. The ${...} interpolation form is not tolerated.

Integer-First Arithmetic

Scoring math is integer-based, which keeps point totals exact and reproducible — there is no floating-point drift to round away. Division truncates toward zero rather than rounding:

10 / 3   ->  3      (not 3.33)
7 / 2    ->  3      (not 4)

Because of this, fractional multipliers use a basis-points convention: store the multiplier scaled by 100 (so 100 means 1.0×, 150 means 1.5×, 250 means 2.5×) and divide by 100 at the end of the calculation:

# combo_multiplier holds 250 to mean 2.5x
points = var.base_value * var.combo_multiplier / 100
# 1000 * 250 / 100  ->  2500

Multiplying first and dividing last keeps the most precision. When you do need explicit rounding, use the round, floor, and ceil functions from the function reference.

String Literals

When an expression compares against a fixed string — a mode name, an enum value, a target id — write that string with double quotes:

condition = var.current_mode == "wizard"
condition = list.contains(var.achievements, "super_jackpot")

Single quotes ('wizard') are not valid in a bare expression and will fail to parse. A double-quoted string is only meaningful as an operand inside a larger expression, like the comparison above.

A Bare Word Is a Variable, Not a String

A lone identifier on the right-hand side is read as a variable reference, not as a literal string:

value = ready        # reads the variable named "ready"

Quoting it does not turn it into a string constant — value = "ready" resolves to the same variable reference. There is effectively no standalone “string value”: if you need to store a fixed label or state, model it as a variable rather than trying to assign a bare string.

Conditions

A condition is a boolean expression that gates whether a rule applies. The rule runs only when the condition evaluates true; otherwise it is skipped:

score "event" "skill_shot" {
  when      = game.skill_shot_made
  condition = var.skill_shot_active && var.combo_count > 0
  points    = var.skill_shot_value
}

Conditions combine comparisons (>, >=, ==, !=) with the logical operators &&, ||, and !, and can read any namespace — for example condition = mode.multiball.active to apply a rule only during multiball.

Operators and Functions at a Glance

The expression language supports the operators and functions you would expect; the full tables with examples are on the Console Expressions page.

  • Arithmetic+, -, *, /, % (modulo), ^ (power)
  • Comparison>, <, >=, <=, ==, !=
  • Logical&&, ||, !
  • Ternarycondition ? value_if_true : value_if_false
  • Math functionsmin, max, abs, round, floor, ceil
  • String and list functions — the string.* and list.* namespaces
  • Noise and randomnessnoise, random_range, weighted_choice, and more, documented on the Noise Generator page

Worked Examples

Award a flat value, then bump a counter for next time:

score "event" "ramp_shot" {
  when   = device.ramp.cleared
  points = var.ramp_base_value + (var.ramp_count * 1000)

  update {
    ramp_count = var.ramp_count + 1
  }
}

Scale points by how fast the shot was made, reading a field off the event:

score "event" "fast_ramp_shot" {
  when      = device.ramp.cleared
  condition = event.transit_time < 1500
  points    = 5000 + (2000 - event.transit_time)
}

Pick the reward with a ternary, choosing between two values inline:

score "event" "combo_award" {
  when   = device.combo_target.hit
  points = var.combo_count >= 3 ? var.combo_value * 2 : var.combo_value
}

Double values during multiball with a conditional modifier — no points awarded, just variable changes:

score "modifier" "multiball_bonus" {
  condition = mode.multiball.active

  apply {
    bumper_value    = var.bumper_value * 2
    slingshot_value = var.slingshot_value * 2
  }
}

Keep a variable derived from others up to date with a formula:

variable "int" "ramp_progressive_value" {
  formula  = var.ramp_base_value + (var.ramp_count * 1000)
  computed = true
}

Where to Go Next

  • Console Expressions — the full operator and function reference, plus how to test expressions interactively with eval
  • Noise Generator — deterministic random and noise functions for natural variation
  • Runtime Bindings — let synth parameters resolve from a literal, an event field, or a full expression at trigger time