Skip to content

Game Rules & Mechanics Reference

This guide covers the scripting patterns and design principles for implementing game rules, mechanisms, and scoring systems in VPX tables, drawn from both recreation and original table builds across VPW.

Mode State Machines

Start / Active / Complete Pattern

Modes follow a three-phase lifecycle. The start phase sets up the mode (lights, DMD display, ball save). The active phase runs the mode logic (shot tracking, scoring, timer). The complete phase handles success or failure (reset lights, award points, transition to next state).

For wizard modes specifically, key design principles from experienced rules designers (learned from Blood Machines Rules):

  1. Wizard modes should ALWAYS have a ball saver (at least 8-10 seconds)
  2. Multiball should not be able to start wizard -- play out the multiball first, then light wizard after returning to single ball
  3. Extra ball stacking must work properly (earn 1, earn another = have 2)
  4. Everything must reset after wizard fail or completion
  5. Turn off unrelated shot lights during wizard to avoid "Christmas tree" effect
  6. Do not tune exclusively for the top 0.1% -- ensure fun light shows and experiences for normal players

Wizard Mode Start Sequence Pattern

Pattern for starting wizard mode with a controlled "pause" sequence (learned from Blood Machines Rules): Use a physical ball trap (wall / drop wall) to hold the ball while running an intro sequence. Steps: (1) Ball enters trap shot, (2) GI turns off, (3) DMD displays intro text, (4) Character-specific lighting changes, (5) After ~5 second intro, wall drops and ball releases, (6) Ball saver activates. Key insight: disable flippers during intro if multiball is active so extra balls drain first, ensuring wizard starts with single ball.

Mode Reset on Tilt

When a player tilts, the normal drain logic is skipped, meaning active modes are not properly terminated. Force-reset all active modes during tilt handling regardless of current state. Additionally, flipper disable flags must be reset on tilt -- without this, a slam tilt could be triggered on the next ball simply by pressing the start button because the flipper-held flag from the previous ball was never cleared (learned from Goonies).

Multiball Logic

Ball Tracking with gBOT

VPW best practice: create all balls once at table initialization and never destroy them. Use a global ball array gBOT instead of GetBalls calls. This forces the table to behave more realistically -- real pinball machines do not create or destroy balls (learned from VPW Example Table).

The GetBalls call is the primary performance bottleneck in ball rolling subs, not the position/velocity checks. The gBOT pattern maintains a global array of ball references updated from trough/lock counts rather than calling GetBalls every frame (learned from Guns N' Roses).

Shadow tracking code that relies on ball IDs will NOT work if balls are destroyed and recreated (IDs change). Judge Dredd tracks 9 balls total (3 captive in reactor, 3 in Deadworld, 3 in play), making reliable ball tracking critical (learned from Judge Dredd).

Ball Save Implementation

For 15-ball multiball, ball saver returns max 10 balls. First 5 balls drain very fast. Apollo 13's 13-ball multiball has no ball saver since so many balls prevent quick draining. Consider limiting ball shadows to improve performance during high ball counts (learned from Blood Machines Rules).

Ball Lock Mechanics

When implementing ball locks, several approaches exist:

Physical trough replacement: Create balls at trough switch kicker positions using CreateSizedBallWithMass, set corresponding controller switches active, track balls in a BOT array, and strip out old trough code. The drain kicker must be positioned at the physical bottom of the trough. Check the manual for "Install X balls" to get the correct ball count (learned from Bram Stoker's Dracula).

Visual lock with real ball: Place an invisible kicker inside the lock mechanism. Create a real ball in the kicker when the ball is "locked." Ball rotates with the mechanism naturally. Kicker releases ball on ROM events. This eliminates the visual artifact of ball texture swapping (learned from Goldeneye).

Captive ball / Newton ball: Create a slightly oversized ball (57 wide) at the post position when raised, destroy or move it when lowered. The spoofed ball uses VPX ball collision physics to naturally transfer momentum. Used in Breakshot's center mechanism, AC/DC bell, and JWPS (learned from Breakshot).

Ball-in-Narnia Recovery

"Narnia balls" -- balls that fall through the playfield or get lost in impossible locations -- can be detected and recovered by looping through the BOT array and checking x/y/z positions. Lost balls are detectable by their rolling sound continuing after they are visually gone, or by z-position being far below 0 (learned from Batman DE).

Ball-Ball Collision Dampening

During multiball, balls colliding on a held flipper produce unrealistically high velocities because VPX does not model ball-to-ball friction. The FlipperCradleCollision sub applies a COR dampening factor (0.4) when one ball hits another while either is on a flipper held at end angle. Called from OnBallBallCollision. Validated against PAPA Godzilla gameplay footage (learned from Physics Debate).

Ball-ball collision sounds can fire excessively during multiball if not filtered. Track ball IDs and prevent the same collision pair from triggering sounds more than once per physics frame with a 50-100ms reset timer (learned from Cirqus Voltaire).

Scoring Systems

Multi-Player State Management

For original (non-ROM) tables supporting multiple players, all game state variables must be explicitly saved at ball drain and restored when the next player's turn begins. Save all per-player variables (score, mode progress, locked balls, bonus multipliers, ramp counts) into per-player arrays. Initialize all player arrays to default values at game start. Watch for counters that are not reset between games in special modes (learned from Goonies).

Score Display with Large Numbers

When implementing score displays that cross into billions, font alignment can drift if certain initialization code is skipped. Using the real game's fonts eliminates the need to reposition the score at each digit boundary -- the spacing works naturally (learned from Game of Thrones).

Building Complex Scoring from Real Machines

Building an original (non-ROM) table requires extensive rule research. For Game of Thrones, scoring rules are undocumented and arbitrary -- even disassembling Spike 1 machine code (possible because Stern left symbol tables in) was not enough to determine all scoring formulas, as awards are based on too many dynamic components (learned from Game of Thrones).

Methodology for verifying complex scoring against a real machine: set up camera viewing both table and DMD simultaneously, hit switches one by one outside of any mode to establish base scoring, then repeat for each mode individually. Go slow and methodical (learned from Iron Maiden).

Attract Mode Implementation

For original tables, attract mode uses a combination of light sequences and FlexDMD animations. The FlexDMD update timer must remain enabled throughout the entire game session -- if disabled after the intro sequence, the DMD breaks on subsequent games without a full table restart (learned from Goonies).

When ending attract mode, call lightCtrl.StopSyncWithVpxLights() to stop the attract sequence from overriding game-controlled light states. Without this call, attract mode light patterns continue during gameplay (learned from Light State Controller).

Any Data East table can be booted in VPX to watch attract sequences as reference. For the backglass during gameplay, astronasty's answer: "on or off -- it's binary" (learned from Die Hard Trilogy).

Custom Light Sequencer

Custom coordinate-based light sequencer uses light positions on the playfield to create spatial effects (sweeps, waves, falling blocks). Parameters encode center coordinates, radius, speed, color, and pattern type. The system calculates which lights fall within the specified region and applies the animation (learned from Goonies).

Flux's Light State Controller provides a more sophisticated system: multi-state light cycling, blink group management, Panlandia coordinate-based sequences, color palette fading, and lightmap color synchronization. GitHub: github.com/mpcarr/vpx-light-controller (learned from Light State Controller).

Tilt Handling

Script-Based Tilt (Original Tables)

Mechanical tilt bob implementation: MechanicalTilt keycode triggers CheckTilt. Add debouncing timer (Stern default: 1000ms ignore window after contact). Separate mechanical tilt handling from desktop nudge-key tilt for cab vs desktop play (learned from Game of Thrones).

Native 10.8.1 Tilt

VPX 10.8.1 includes a native tilt plumb simulator running in the core physics engine at 1kHz. It models a 3D Newtonian mass on a bar that can hit and bounce against a ring. The LiveUI overlay shows the dynamic plumb position when nudging. See Hardware & Cabinet for setup details.

Free Play / Credit Bypass

Quick way to enable free play on a table with a credit system: search for lines containing Credits=Credits-1 and comment them out. This prevents the script from decrementing credits (learned from Blood Machines Rules).

Mechanism Scripting Patterns

VUKs, Scoops, and Kickers

Kicker axis matters: for scoops/VUKs, use the Y-axis for kick direction, not Z-axis. Z-axis kicks the ball straight up; Y-axis kicks it forward onto the playfield (learned from Hook).

When using a kickball function with a ball variable, release the ball variable after kicking. If you do not, and there is a ball search or unexpected solenoid firing, weird behavior occurs (ball floating, stuck animations). Check if there is a ball assigned to the variable rather than checking switch status (learned from Jurassic Park).

When multiple balls enter a scoop/subway, they can stack and jam. Fix: before kicking, loop through all balls to detect stacking using InRect position check + z-height comparison. Identify the top ball (highest z), then reposition it above the subway before kicking (learned from Guns N' Roses).

Magnets and Ball Capture

For mechanical toys that grab and move the ball (like JM's data glove), use a 1ms timer that continuously moves the active ball to follow the toy's position. The old approach (destroy ball at capture, create new ball at release) causes visual pops and leaves no physical ball in play (learned from Johnny Mnemonic).

Diverters and Motors

For continuous motor sounds, use PlaySoundAtLevelStaticLoop which loops until explicitly stopped. Start when the motor solenoid activates, stop when it deactivates. Static loop sounds persist without re-triggering each cycle, avoiding audible gaps from repeated PlaySound calls (learned from Black Rose).

Use SolCallback to detect when a motor solenoid stops. The callback fires with Enabled=True when the solenoid activates and Enabled=False when it deactivates. This is cleaner than guessing motor stop position from mechanism values which drift (learned from Austin Powers).

Raising/Lowering Ramp Mechanism

Jokerz's center ramp uses a one-way motor on a cantilever. Model the motor as continuously running when the solenoid is enabled, with the ramp cycling through positions until the appropriate up/down switch is reached. For collision: use sequential collidable primitives that become collidable in sequence as the ramp moves. Make all ramps collidable cumulatively during raising, then disable all except the top after fully raised (learned from Jokerz).

VPX collidable primitives cannot be moved at runtime. For animated mechanisms that need collision (like Indiana Jones' idol carousel), use an array of VPX flipper objects arranged in a circle to approximate the collision boundary. Flippers are natively collidable AND movable. Script the flipper positions/angles to rotate together (learned from Indiana Jones).

Light Show Systems

Lampz and ModLampz (Legacy)

Lampz was the standard VPW light management system through early 2024. Key concepts:

  • MassAssign registers lights to lamp numbers, with the control light as the first element
  • FadeSpeedUp/FadeSpeedDown values control transition speed per light type
  • The Lampz timer must NOT be linked to frame rate (use fixed 10ms interval) to avoid different fading speeds at different FPS (learned from VPW Example Table)

Temporal note: Lampz was replaced by native VPX light handling starting with Example Table v1.5.15 (March 2024). VPX 10.8+ supports light_animate event subs that fire whenever a light's IntensityScale changes, replacing Lampz for controlling 3D insert primitive BlendDisableLighting. For non-ROM tables, simply set light.state = 1 or 0 and the VPX "Incandescent" fader handles the rest (learned from VPW Example Table).

Incandescent vs LED Fade Speeds

Different FadeSpeed values simulate LED and incandescent bulb response characteristics (learned from Iron Man):

LEDs (fast response): Fade up 1/2 (near instant), fade down 1/8 (slight persistence).

Incandescent bulbs (thermal lag): Fade up 1/40 (slow warm-up as filament heats), fade down 1/120 (very slow cool-down). The asymmetric up/down speeds reflect the physical reality that filaments heat faster than they cool.

VPX 10.8 introduced built-in light fading with incandescent bulb simulation: select "incandescent" fader type to get the correct nonlinear fading curve. Working values: 40ms up, 120ms down (learned from Johnny Mnemonic).

Flasher Fading

The standard VPW flasher fading pattern uses a per-flasher timer with exponential decay (learned from Congo):

FlashLevel = FlashLevel * 0.93 - 0.01
flashx3 = FlashLevel * FlashLevel * FlashLevel
Flasher.opacity = 110 * flashx3
Light.IntensityScale = 3 * flashx3

The cubic function creates a fast initial decay that slows near zero, matching incandescent cooling. Each flasher solenoid gets its own SolCallback sub that sets FlashLevel = 1 and starts the timer. Per-element variation avoids the "Christmas tree" look where all flashers fire identically (learned from Black Rose).

GI Color Mod System

Render all GI lights as pure white in Blender, then apply color modifications in VPX script. This enables unlimited color mod options without re-rendering: users can choose presets, each GI group can be independently colored, and room light level is adjustable (learned from Haunted House).

For tables with two GI strings of different colors, use a separate boost collection controlled by the equation IntensityScale = (1 - gi2lvl) * gi1lvl for smooth cross-dimming. Do not attempt to override ModLampz.Lvl values at runtime -- the lampz system locks internal values (learned from Black Rose).

Original Table Design Considerations

Design Workflow

Original table design follows an iterative workflow: paper sketches establishing shot layout, then basic VPX prototype with walls and kickers to test shot geometry, then iterative refinement. Prove the layout is fun with placeholder art before investing in 3D modeling (learned from Blood Machines).

Recommended build order for original VPW tables: (1a) Rules and inserts with iterations, (1b) Meanwhile update physics and fleep, (2a) Artwork, (2b) Meanwhile start programming rules, (3) Blender (last, depends on finalized artwork and layout) (learned from Die Hard Trilogy).

Difficulty Balancing

Design tension: making modes achievable for casual players vs maintaining challenge for skilled players. Sixtoe's advice: "don't hide all the fun stuff away where no-one will ever see it" (learned from Blood Machines Rules).

Difficulty level in table options changes slope (if min/max are different) and scatter randomness. It does NOT change ball mass (learned from Physics Debate).

Player Testing with Real Machine Reference

Video reference is essential: compare gameplay videos (PAPA/IFPA tournament videos preferred -- consistent camera angles, good lighting) for ball behavior. Real machine owners on the build team can provide high-resolution playfield scans, exact measurements, shot difficulty verification, and reference photos of specific mechanisms (learned from Diner, Iron Maiden).

DIP Switch Initialization

ROM DIP switches do not have VPX-side defaults, so first-time users get whatever the ROM defaults to. Two approaches (learned from Haunted House):

  1. Script-based: SetDefaultDips function writes desired DIP values on first table load. Drawback: requires table restart.
  2. Include .nv file: Ship the table with a pre-configured .nv file. Works immediately but only applies to one ROM set.

For Gottlieb tables, difficulty adjustments are described as "conservative" (harder) and "liberal" (easier) post positions in the manual. In VPX, implement as F12 menu options that swap post positions (learned from Countdown).

VBScript Gotchas for Game Logic

Boolean Pitfall

VBScript boolean pitfall: if a variable is set to 1 (integer) instead of True (boolean -1), then Not variable returns -2 instead of 0. When using service menu values that store 0/1 integers, compare with <> 0 rather than using Not (learned from Game of Thrones):

' Bad:
If Not cabinetmode Then ...  ' fails when cabinetmode = 1
' Good:
If cabinetmode = 0 Then ...

Duplicate Sub Names

VPX does not always throw an error for duplicate sub names. Having two subs with the same name (e.g., two LeftFlipper_Collide(parm) subs for sound and nFozzy physics) causes the first to be silently overridden. Fix: merge into a single sub or rename the duplicate (learned from Austin Powers).

Destroyed Ball References

When balls are destroyed, VBScript's IsNull() does NOT detect the invalid reference. Accessing properties on a destroyed ball causes a runtime error. Solutions: set ball reference variables to Nothing immediately after DestroyBall and check with Is Nothing, or use a boolean alive flag (learned from Blood Machines).