Skip to content

VPW Example Table Walkthrough

The VPW Example Table is the canonical reference implementation showing VPW build conventions. It's a bag of tricks, not a framework - designed for copying features into your own projects.

Purpose & Philosophy

What it is: - Demonstration of standard VPW features in one place - Copy-and-paste reference for specific techniques - Best practices implementation examples

What it's NOT: - Not a game framework (like Orbital or JPSalas) - Not meant to be used as complete starting point for game logic - Not a substitute for understanding the underlying systems

Core Features (v1.0+)

Physics & Flippers

  • nFozzy flippers and physics corrections
  • Slingshot angle correction (4-7 degree range recommended)
  • Kicker randomization
  • TargetBouncer for realistic target hits
  • Rubberizer for post/sleeve dampening

Lighting System

  • Lampz light fading
  • 3D inserts (2-prim version recommended)
  • Flupper dome flashers with bloom
  • GI lamp control architecture
  • Modulated signal support (PWM-style)

Audio

  • Fleep sound package integration
  • Ramp rolling SFX (ZRRL system)
  • Ball rolling sounds with volume parameter (no _amp9 suffix)
  • Mechanical tilt implementation

Visual Effects

  • Dynamic ball shadows with Z-scaling on ramps
  • RTX shadow primitives with soft edges
  • Rendered playfield shadows
  • Roth drop targets with shadows

VR Support

  • VR cabinet and room
  • FlexDMD scoreboard with intro

Original Table Features (v1.4+)

  • Queue system for multiball/jackpot/bonus sequences
  • FlexDMD scene initialization and management
  • Physical trough (balls created once, never destroyed)

Key Implementation Details

2-Prim vs 3-Prim Inserts

Why 2-prim is default: - 3-prim inserts extremely difficult to tune correctly - Requires precise material opacity adjustment (can even be reversed between On/Off) - Depth bias complications - 2-prim version tunable with any material/depth combination

Lampz Control Light Method (VPX 10.7+)

For VPX built-in LightSequencer compatibility: - Add invisible "control light" as first element in MassAssign arrays - Control all lights through these control lights - All other objects in array fade properly - Invisible lights must be positioned within playfield area - Requires GetInPlayStateBool property (10.7+)

Lampz Timer Configuration

Critical: Use fixed interval, NOT frame rate (-1) - Frame rate linking causes different fading speeds per player (165Hz vs 30fps) - On some GPUs causes FPS cycling (30-160fps with 1-second intervals) - Fixed interval resolves both consistency and performance issues

GI Architecture

Incorrect approach: Changing lamp states for entire GI collection directly - Breaks Lampz fading logic

Correct approach: Single control lamp controls collection via Lampz - Preserves fading behavior - Maintains proper state management

Physical Trough Best Practice

Balls created once at initialization, never destroyed: - Forces realistic table behavior - Makes ball tracking much easier - Shadow ramp-type tracking requires this approach - SolRelease kicks next ball, chain reaction prepares for next

Note: ROM compatibility requires control logic outside SolRelease sub

gBOT (Global Ball-On-Table) vs GetBalls

Use gBOT array instead of GetBalls calls: - Physical trough creates balls once at init - More realistic behavior - Required for shadow tracking - If using traditional scripting (destroying balls), replace gBOT with BOT and use GetBalls

Dynamic Ball Shadow Implementation

Primitive shadows recommended (not flashers): - Primitives can be scaled on-fly - Flashers cannot scale dynamically - Z-scaling on ramps:

objBallShadow(s).size_x = 5 * ((gBOT(s).Z+BallSize)/80)
objBallShadow(s).size_y = 4.5 * ((gBOT(s).Z+BallSize)/80)
UpdateMaterial objBallShadow(s).material,1,0,0,0,0,0,AmbientBSFactor*(30/(gBOT(s).Z)),RGB(0,0,0),0,0,False,True,0,0,0,0

Optimization: Don't write size/UpdateMaterial every loop iteration - Detect when ball returns to playfield - Only write original values once - Leave only position updates in main loop

Flupper Dome Flasher Setup

Auto-placement code:

If objbase(nr).roty > 135 then
    objflasher(nr).y = objbase(nr).y + 50
    objflasher(nr).height = objbase(nr).z + 20
Else
    objflasher(nr).y = objbase(nr).y + 20
    objflasher(nr).height = objbase(nr).z + 50
End If
objflasher(nr).x = objbase(nr).x

Light object placement:

objlight(nr).x = objbase(nr).x
objlight(nr).y = objbase(nr).y
objlight(nr).bulbhaloheight = objbase(nr).z - 10

Flasher Bloom: - FlasherBloom placed at table glass height/angle - Tied to Flupper dome code - Position with additive blend OFF, then re-enable - Used in Bad Cats, Austin Powers, NBAF, Radical

Ball Rolling Sounds (v1.14+)

Use loudest sound effect with volume parameter: - No multiple pre-recorded amplitude levels - _amp9 suffix removed from all sound names - Simplifies sound management - Volume controlled programmatically

Export multiple sounds from VPX: Select all desired sounds, click export - exports all selected

Slingshot Angle Correction

Standard for VPW tables: - 4-7 degree range is correct (original 10 degrees too high) - Based on video research - Don't forget velocity correction calls from _slingshot subs - Requires dsin/dcos functions

Kicker Implementation

Always add small randomization: - Not needed if Game Difficulty > 20 - Caution with Game Difficulty variable - found in unexpected VPX source locations

TargetBouncer

Critical: Requires idx parameter:

' Wrong:
Sub TargetBounce_Hit
    TargetBouncer activeball, 2
End Sub

' Correct:
Sub TargetBounce_Hit(idx)
    TargetBouncer activeball, 2
End Sub

Rubber Dampener Scope

RubbersD applies to posts and sleeves only, NOT rubber bands: - Sleeves use different profile than posts - nFozzy/rothbauerw design

ROM Integration

SoundCommandListener: Intercept ROM sound commands for custom handling

SetLamp for SolCallback:

Sub SetLamp(aNr, aOn)
    Lampz.state(aNr) = abs(aOn)
End Sub

Bridges SolCallback array with Lampz fading system

Solenoid subs architecture: - Fire kicker regardless of ball presence - Only contain enabled/disabled parameter logic - No game control logic inside - Control logic determining when to call should exist outside - For original tables: add BIPL check BEFORE calling SolRelease, not inside

Original Table Features (v1.4+)

Queue System

Examples implemented: - Multiball launch with FlexDMD animation - Jackpot on ramp shot - Bonus X on bumper inlanes - Drop target bonus on saucer - Queue-controlled saucer ejection with flasher warning

FlexDMD scenes: Initialized into array at Flex_Init using ShowScene wrapper - Prevents errors from re-creating scenes each time shown - Animated GIFs do NOT restart from beginning each show

Note: Not ROM-compatible - ROM tables have queuing in ROM logic

Version History & Migration

VPX 10.6 to 10.7.2 Migration

Decision factors: - 10.7.2 superior for players (better layers, webp image savings) - 10.7.x editor lost authoring features (camera/debugger preview, multi-select viewing, object list refresh, dragging) - More layers for organizing techniques - Example table is copy-paste reference, not interactive debugging tool - Caveat: Once saved in 10.7, cannot revert to 10.6

Major Version Updates

  • v1.12: Dynamic shadows converted to arrays (rothbauerw implementation from Tee'd Off)
  • v1.14: Ball rolling sounds updated, _amp9 suffix removed
  • v1.21-1.24: Slingshot angle correction added
  • v1.26: ObjTargetLevel for flasher domes, modulated signal support
  • v1.32: Ball shadow Z-scaling on ramps
  • v1.4: Queue system for original tables
  • v1.5.3: Rendered playfield shadows, updated RTX shadow image

Common Issues & Fixes

FlexDMD VR Exit Error

if not flexdmd is nothing or vrroom=0 then
Prevents error from VR terminating FlexDMD before VPX cleanup

DTArrayID/STArrayID Bug

Exit Function must be INSIDE the If block:

Function DTArrayID(switch)
    Dim i
    For i = 0 To UBound(DTArray)
        If DTArray(i)(3) = switch Then
            DTArrayID = i
            Exit Function  ' Must be inside If
        End If
    Next
End Function

Removing Bumpers

  1. Comment out initialization lines
  2. Delete table objects
  3. Update For ind = 1 to X loop count

VPX File Corruption from Full SSD

  • VPX creates 12KB dummy file when drive full
  • Reports success but doesn't save
  • Keep adequate free space
  • Verify file size after saving critical work
  • VPX autosave unreliable (saves twice then stops)

Fleep Sound Dependencies

Error "Variable is undefined: 'min'" when calling RandomSoundBallBouncePlayfieldSoft/Hard: - Missing utility functions from ZMAT section - Functions may show purple but still need copying

Best Practices

Code Formatting (VPW Recommendation)

  • Use tabs (not spaces) for indentation
  • Dim and assign on separate lines (enables VPX hover comments)
  • Section headers with unique 4-letter search codes
  • Table of Contents at top of script
  • Comment headers between major libraries
  • Commented code: one tab after apostrophe

Version Control

  • Always verify VPX version before saving shared tables
  • VPX doesn't warn about version changes
  • Check save format matches project standard

Saving & Verification

  • Always verify file size after save
  • Full SSD can cause silent corruption
  • Keep backups of working versions
  • Use WinMerge (https://winmerge.org) for comparing script changes

Tools & Comparison

WinMerge for script comparison: - Compare folders and files - Visual diff presentation - Detects undocumented changes - Essential for multi-author tables