Migrating from Visual Pinball

vpx visual-pinball migration

Migrating from Visual Pinball

If you have built tables in Visual Pinball X, you already know the hardware model — switches, coils, lights, flippers, drop targets — and you have probably written a lot of VBScript to glue them together: Sub Bumper1_Hit, PlaySound, manual ball counters, light .State toggling, timer-driven resets.

Cade keeps the hardware model and replaces the glue. Instead of writing event subroutines, you declare what the machine is and what should happen, and the runtime does the wiring. The same table definition then runs against VPX over the gRPC bridge or on real hardware without changes.

The shift: imperative script → declarative config

A VPX table mixes the playfield, the physics, and the rules together in VBScript. A Cade table separates them into two files:

  • Machine definition (*.cade) — devices, events, signals, scoring, and audio. This is portable and describes what the machine is and how it scores.
  • Runtime configuration (the Cade config file) — which platform driver to use, the web server, logging. This is environment-specific.

You stop writing Sub … End Sub handlers, hand-computed PlaySound pan/fade, manual balls-in-play arithmetic, and .State light flips. You start writing device, event_handler, signal, and score blocks. Behavior emerges from the rules rather than from a script you have to keep in sync.

How VBScript concepts map to Cade

In Visual Pinball (VBScript)In Cade (HCL)
Sub Object_Hit()An event_handler or score rule keyed off the device’s event
Dim x / variable assignmentA variable "int" / variable "bool" block
Light.State = 1 / = 0A light driven by a bool variable, toggled in an event_handler
PlaySound "fx", …, AudioPan, AudioFadeAn audio block with spatial = true — panning is computed from device position
DropTarget.isDropped = 0 + a reset timerA target_group device with a reset_delay setting
Manual BIP (balls-in-play) counterA ball device plus eject_ball / kick_ball actions
Score arithmetic in a SubA score block with a points expression
Combo flags + timers across several subsA signal block that detects the sequence for you

See Signals vs Event Handlers for when to reach for each layer.

What the config looks like

A flipper — a coil and a switch wired to a flipper device, replacing the VBScript that drove RotateToEnd/RotateToStart:

device "flipper" "standard" "left_flipper" {
  id = 1
  hardware {
    coil   = "C01"
    switch = "S01"
  }
  settings {
    strength  = 75
    hold_time = "250ms"
  }
}

A pop bumper that scores, with its sound attached — no Sub Bumper1_Hit, no PlaySound:

score "event" "bumper_hit" {
  when    = device.bumper.activated
  points  = 100
  audio {
    clip   = bumper
    volume = 0.8
  }
}

A drop-target bank that resets itself, replacing the .isDropped bookkeeping and reset timer:

device "target_group" "drop" "drop_targets" {
  id = 30
  hardware {
    switches   = ["S30", "S31", "S32"]
    reset_coil = "C30"
  }
  settings {
    reset_delay = "500ms"
  }
}

A light toggled by play, replacing Light.State = … in a Sub:

event_handler "rollover_lit" {
  when      = device.rollover_lane.rollover
  condition = var.lane_lit == false
  actions {
    toggle_variable = "lane_lit"
    set_light "light" {
      device = lane_insert
      state  = "on"
    }
  }
}

Connecting to VPX

Cade talks to Visual Pinball X over the gRPC bridge. The connection settings, the platform "grpc" block, and the supported capabilities (switches, coils, lights, the hybrid flipper/autofire model) are documented on the Visual Pinball driver page. Configure that block in your runtime config, then launch Cade against your table directory:

cade console -d /path/to/table

The console is where you confirm the bridge is live and watch switch hits arrive and coil/light commands fire as you play.

A migration workflow

  1. Install Cade and walk through Getting Started so you can validate and run a .cade file.
  2. Declare your devices. Translate each VPX switch, coil, light, flipper, bumper, and target into a device block, mapping VPX objects to Cade hardware addresses (S01, C01, …).
  3. Replace the subs. Turn each Sub …_Hit into an event_handler or score rule. Move combo/flag logic into signal blocks.
  4. Move audio over. Declare your clips once and attach them with audio blocks; use spatial = true instead of hand-computed pan/fade. See Audio & Sound.
  5. Connect and play. Point the gRPC platform at VPX, start a game, and debug live in the console.

Tips and gotchas

  • Let ball devices count for you. Use eject_ball (creates and launches a new ball) and kick_ball (fires an already-held ball without changing the count) instead of a manual balls-in-play counter. See Multiball and Ball Lifecycle Events.
  • Modes live inside modules. If you used global flags to switch rule sets, model those as modes — see Organizing Modes into Modules.
  • Validate before you run. cade validate catches missing references and type mismatches up front, so you find wiring mistakes without launching a game.