Table Design Basics

table scoring switches score

Table Design Basics

Almost every table is built from two kinds of pieces: scoring elements that award points when something happens, and modes that change the rules for a stretch of play. This guide builds one of each from scratch, so you come away knowing the shape of both.

It assumes you can already load and run a .cade file. If not, walk through Getting Started first — it covers installing Cade, validating a table, and launching the console.

Scoring Elements

A scoring element is a score "event" block. When the event you name fires, Cade evaluates an expression and adds the result to the player’s score. The two attributes you always provide are when (the event to listen for) and points (how much to award):

# A variable holds the point value so you can tune it in one place.
variable "int" "bumper_value" {
  initial = 1000
  scope   = "player"
}

score "event" "bumper_hit" {
  when   = device.bumper.activated     # the event that fires this rule
  points = var.bumper_value              # how many points to award
}

That is a complete, working scoring rule: every time the bumper fires its activated event, the player gets bumper_value points.

The attributes you’ll reach for

AttributeRequiredWhat it does
whenyesThe event that fires the rule, for example "device.ramp.cleared"
pointsyesAn expression for the points to award
conditionnoA boolean gate — the rule scores only when it is true
updatenoVariable changes applied after scoring
emitnoAn event to emit once scoring completes
prioritynoExecution order when several rules match (lower runs first)

A richer rule that gates on game state and feeds back into a variable — here a ramp that scores more each time it’s hit during the ball:

variable "int" "ramp_value" {
  initial = 5000
  scope   = "player"
}

variable "int" "combo_multiplier" {
  initial = 100   # 100 means 1.0x — see "integer-first arithmetic"
  scope   = "ball"
}

score "event" "ramp_shot" {
  when      = device.ramp.cleared
  condition = var.combo_multiplier > 0
  points    = var.ramp_value * var.combo_multiplier / 100

  update {
    combo_multiplier = var.combo_multiplier + 10        # +0.1x each ramp
  }
}

The points expression and the condition gate are written in Cade’s expression language. For how var.… references, the / 100 basis-points multiplier convention, and integer-first arithmetic work, see Writing Expressions. For every scoring attribute and the modifier and accumulator block types, see the Score reference.

Modes

A mode is a stretch of play with its own rules — a timed frenzy, multiball, a wizard mode. While the mode is active, the scoring rules and variables you put inside it switch on; when it ends, they switch off automatically.

You author a mode as a module with a mode { } block inside it. Here is a complete timed mode: for 20 seconds, bumpers are worth double.

module "bumper_frenzy" {
  description = "Timed mode: bumpers score double for 20 seconds"

  mode {
    priority    = 300
    stop_events = ["frenzy_timeout"]

    on_start {
      display_text "BUMPER FRENZY!" 3000
    }

    timers {
      frenzy_timer {
        duration = 20000               # milliseconds
        on_expire { emit "frenzy_timeout" }
      }
    }

    on_end {
      display_text "FRENZY OVER" 2000
    }
  }

  # Active only while this mode is running — Cade adds that guard for you.
  score "modifier" "frenzy_boost" {
    apply {
      bumper_value = var.bumper_value * 2
    }
  }
}

The pieces:

  • module "…" { mode { } } — the mode lives in a mode block inside a module. (The older top-level mode "x" and game_mode "x" blocks are no longer valid; everything is a module now.)
  • priority — when more than one mode is active at once, higher priority runs first.
  • on_start / on_end — actions that run as the mode begins and ends: post a display message, play a sound, set a variable.
  • timers — a named timer with a duration in milliseconds; when it expires, on_expire runs — here it emits the event that stops the mode.
  • stop_events — the events that end the mode.
  • the score "modifier" block — anything you put inside the module (scoring rules, modifiers, variables) is active only while the mode is running. You don’t write a condition for it; Cade guards it with the mode’s active state for you.

Starting and ending a mode

A mode sits dormant until something starts it. Start it from an event handler with the start_mode action — for example, when a particular target is hit:

module "base" {
  mode {
    priority = 100

    events {
      on device.frenzy_target.hit { start_mode "bumper_frenzy" }
    }
  }
}

The mode ends when one of its stop_events fires — in this case the frenzy_timeout event the timer emits after 20 seconds. You can also end a mode explicitly with the end_mode action inside any of its handlers, which is how modes like multiball wind down when the last ball drains.

Mode modules vs. infrastructure modules

A module with a mode { } block is a mode: it switches on and off, and its contents are active only while it runs. A module without a mode { } block is an infrastructure module — it is always on from the start of the game, useful for ball tracking, tilt handling, or the base ruleset that starts other modes. Same container, two lifecycles.

Where to Go Next