VPW Example & Resource Table¶
The VPW Example Table is the canonical reference implementation for VPW techniques. It demonstrates every major VPW system in working form, intended as a "bag of tricks" for reference and copy-paste -- not as a starter template for new tables.
Available on VPUniverse. Source tracked at: github.com/francisdb/vpw-example-table-extracted
Demonstrated Features¶
The Example Table contains working implementations of:
- Dynamic Ball Shadows -- Array-based shadow tracking with height-proportional scaling
- nFozzy Flipper Physics -- Trajectory correction, polarity curves, velocity curves (all eras)
- nFozzy Rubber Separation -- dPosts/dSleeves collections with physics materials
- TargetBouncer -- Realistic vertical bounce on standup targets and posts
- FlipperCradleCollision -- Ball-to-ball dampening on held flippers during multiball
- Inlane Live Catch -- Extended CheckLiveCatch with base dampening
- Slingshot Angle Correction -- 4-7 degree deflection correction
- Fleep Sound Effects -- Complete mechanical sound package
- Ramp Rolling Sounds -- Height-based rolling audio
- Flupper Dome Flashers -- With blooms and auto-positioning
- Flupper Bumpers -- 3D bumper caps
- 3D Inserts -- Both 2-prim and 3-prim systems
- Roth Drop Targets -- With shadows and animation
- Physical Trough System -- Never-destroy-balls pattern
- VR Cabinet & Room -- Full VR support
- FlexDMD Scoreboard -- Scriptable DMD
- Standup Target Code -- With TargetBouncer integration
Temporal note: Lampz was removed in favor of VPX native light handling starting v1.5.15 (March 2024). The light_animate event sub system replaced Lampz for controlling 3D insert primitives.
Key Conventions¶
Start New Tables from Blank, Not Example Table¶
Apophis79's recommendation: start new tables from a completely blank table with no extras, then add features incrementally. The Example Table is overwhelming as a starting point and leads to not understanding what's in the final table. A separate "Basic" example table was created in 2026 with just essential code (nFozzy, Fleep) and a generic layout as a proper starting point.
Physical Trough -- Never Destroy Balls (gBOT Pattern)¶
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 realistic behavior -- real machines don't create/destroy balls.
If you want traditional ball destruction, replace all gBOT references with BOT and use GetBalls where needed. Shadow tracking code that relies on ball IDs will NOT work if balls are destroyed and recreated (IDs change).
Standalone compatibility: gBOT causes segfaults in standalone VPX. Use GetBalls instead for standalone compatibility. This is the cause of 95% of "ball hits flipper -> segfault" crashes in standalone.
2-Prim vs 3-Prim Inserts¶
3-prim inserts produce better results but are extremely difficult to tune -- material opacities, depth bias, and DL values all interact in non-obvious ways. For general adoption, 2-prim inserts are recommended because they can be tuned to look good with whatever material and DL combination. 3-prim inserts should only be attempted by experienced builders.
Physics Sets Must Not Be Used with nFozzy¶
VPX "Global Physics Sets" silently override nFozzy's carefully tuned values. The editor does NOT grey out overridden values, making the conflict invisible. Never use global physics sets with nFozzy physics.
Flipper Physics Reference¶
Trigger Sizing (2024 Standard)¶
Flipper triggers should be 27 VP units from the flippers (increased from the original 23-unit standard). The previous 23 units was less than a ball radius (25 units), meaning velocity correction from cradle was silently broken.
To size correctly: temporarily set end angle 3 degrees beyond actual (e.g., 67 instead of 70), create the trigger, then restore the flipper end angle. The 3-degree extension accounts for built-in momentum overswing.
Polarity and Velocity Curves by Era¶
The AddPt curves are NOT universal -- they vary by era (70s, 80s, 90s, etc.) to match flipper coil power. Select the curve set matching the table's era. When shot angles feel wrong, try swapping to a different era's polarity curve.
Flipper Strength by Era¶
| Era | Coil Type | Strength |
|---|---|---|
| System 11 (mid-80s) | Weaker solenoids | 2300-2600 |
| Early WPC (1991) | Medium red coil FL-11630 | 2400 |
| Mid-WPC (1992+) | Strong blue coil FL-11629 | 2600 |
| Late WPC/Modern | Varies | 2600-3300 |
Key Constants¶
- EOSTorque: 0.375 -- tuned extensively, do not change when adjusting main strength
- EOSReturn: 0.4 -- affects backhand strength and flick pass difficulty
- FlipperMass (SOSRampup): 2.5 -- VPW standard since early development. Do not reduce.
- BaseDampen: 0.55 -- for inlane live catch at flipper base
- LiveDistanceMin: 5 -- extends live catch detection to flipper base
Slingshot Correction¶
VPX slingshots apply force perpendicular to the wall surface, but real slings deflect at an angle. The correction adds 4-7 degrees of rotational force (the original 10-degree value was too high). Correction calls go in _Slingshot event subs, and require dsin/dcos helper functions.
TargetBouncer¶
Adds realistic vertical bounce to standup target hits. Key constants:
- TargetBouncerEnabled = 1
- TargetBouncerFactor = 0.7
The TargetBounce_Hit sub must include the (idx) parameter:
Warning: Do not apply TargetBouncer to targets or posts in close proximity to pop bumpers. Repeated velocity additions between bumper kicks and target bounces can compound, launching the ball through geometry.
FlexDMD Architecture¶
Scenes should be initialized once into an array during Flex_Init and displayed using a ShowScene wrapper. Recreating scenes each time throws errors.
For auto-detection of FlexDMD: wrap the CreateObject call in error handling rather than checking filesystem paths. Filesystem checks fail on standalone (Android/Linux/Mac).
' Correct detection pattern:
On Error Resume Next
Set FlexDMD = CreateObject("FlexDMD.FlexDMD")
On Error Goto 0
If FlexDMD Is Nothing Then UseFlexDMD = False
Dynamic Ball Shadows¶
Optimized array-based shadow code with height-proportional behavior:
objBallShadow(s).size_x = 5 * ((gBOT(s).Z + BallSize) / 80)
objBallShadow(s).size_y = 4.5 * ((gBOT(s).Z + BallSize) / 80)
When ball is elevated (on ramp), shadow grows larger and fades. Important: detect Z threshold crossing and only update once per transition -- avoid writing size and UpdateMaterial every loop iteration.
VPX 10.8+ Insert Control (light_animate)¶
VPX 10.8+ supports light_animate event subs that fire whenever a light's IntensityScale changes. This replaces Lampz for controlling 3D insert primitive BlendDisableLighting. For non-ROM tables, set light.state = 1 or 0 and the VPX "Incandescent" fader handles the rest.
Benefits: simpler script, better performance (native implementation), PWM fading support, and lightmap integration. Lampz is no longer needed for either ROM or original tables.
DisableStaticPreRendering (VPX 10.8.1+)¶
VPX 10.8.1 changed DisableStaticPreRendering to track enable/disable count rather than being a simple toggle. Tables must set it True once when entering the options menu and False once when exiting. The old approach of setting it every time an option changes breaks in 10.8.1.
Version History Highlights¶
- v1.0-v1.4 -- nFozzy physics, Fleep sounds, Lampz, inserts, shadows
- v1.5.15 (March 2024) -- Lampz removed in favor of native VPX light handling
- v1.6+ -- FlipperCradleCollision, updated trigger sizing (27 units), inlane live catch
- v1.7+ -- Basic example table variant for clean starting point