Migrating from Visual Pinball
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 assignment | A variable "int" / variable "bool" block |
Light.State = 1 / = 0 | A light driven by a bool variable, toggled in an event_handler |
PlaySound "fx", …, AudioPan, AudioFade | An audio block with spatial = true — panning is computed from device position |
DropTarget.isDropped = 0 + a reset timer | A target_group device with a reset_delay setting |
Manual BIP (balls-in-play) counter | A ball device plus eject_ball / kick_ball actions |
Score arithmetic in a Sub | A score block with a points expression |
| Combo flags + timers across several subs | A 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
- Install Cade and walk through Getting Started so you can validate and run a
.cadefile. - Declare your devices. Translate each VPX switch, coil, light, flipper, bumper, and target into a
deviceblock, mapping VPX objects to Cade hardware addresses (S01,C01, …). - Replace the subs. Turn each
Sub …_Hitinto anevent_handlerorscorerule. Move combo/flag logic intosignalblocks. - Move audio over. Declare your clips once and attach them with
audioblocks; usespatial = trueinstead of hand-computed pan/fade. See Audio & Sound. - 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) andkick_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 validatecatches missing references and type mismatches up front, so you find wiring mistakes without launching a game.