Skip to content

Lethal Weapon 3

Lethal Weapon 3 (Data East 1992) is a VPW table build featuring a GI on/off texture swapping system with smooth playfield flasher overlay fading, VLM Blender Toolkit pipeline integration, Skitso-style 3D lighting, and extensive troubleshooting of VLM subdivision and nestmap issues.

Build Notes

Data East Cabinet Dimensions

Precise Data East cabinet dimensions are available from Wylte's Tommy measurements, stored in VPX Table Size Manager. Sideblades should reach the top of the side panels without gaps, and the glass cannot touch anything inside. Height should be set based on the tallest playfield object plus a small margin.

Siderails and Lockdown Bar

Data East machines (including LW3) have powder-coated black siderails and lockdown bar, unlike the chrome rails common on other manufacturers. When importing rail geometry from other tables, ensure the material is changed to black powder coat.

Fleep Sound Integration

Adding Fleep sounds requires scripting knowledge -- specifically knowing where to replace or delete existing sound-related routine calls. The Fleep package replaces default VPX mechanical sounds with realistic recordings. Fleep sounds should match the era and manufacturer of the table.

Data East 3D Insert Implementation

Data East tables require different scripting approaches for 3D inserts compared to other manufacturers. The .image property on primitives is the key mechanism for texture swapping. Insert lights must be positioned under the playfield (negative Z height, e.g., -2) for correct below-surface illumination. Light intensity needs significant reduction when Z is lowered (from 150 to 20-27 in one case). TFTC (Tales from the Crypt) serves as a better reference for Data East insert implementation than Hook.

PNG Compression for File Size

ImageOptim (Mac) or Pinga (Windows) can dramatically reduce VPX file sizes by compressing PNG textures with minimal quality loss. LW3 went from 189MB to 121MB (~36% reduction) with no noticeable quality difference on 4K displays. Extreme compression saved 57% on one test. Apply to all PNG textures before final release.

Scripting

Ball Shadow Height-Based Visibility

Ball shadows should be hidden when the ball is on ramps above the playfield. The Z axis in VPX goes negative when the ball is above playfield level. Correct shadow control: show shadow when ball Z is both greater than -20 AND less than 40 (covers playfield level and kicker holes, hides on ramps). Depth bias of -100 on ball shadow helps with z-fighting on the playfield.

Primitive Image Must Match Default GI State

When using GI texture swapping on primitives, the primitive's default Image property in VPX editor must be set to the GION texture (matching the table's starting state). If set to the wrong texture, primitives appear blank/white on table load until the first GI state change triggers the script to swap.

3D & Art

GI On/Off Texture Swapping System

For tables with baked lighting textures, GI on/off states require separate baked textures that swap via script. Create GION and GIOFF texture sets for each primitive group (playfield, cabinet, plastics, metals). Primitives are grouped in VPX collections for batch swapping. Non-playfield primitives (cab, plastics) use 4-step image swaps (0%, 33%, 66%, 100%) for smooth transitions. A GI timer (interval 25-40ms) controls the fade speed. File size grows significantly with multiple texture sets (~180MB with fading textures).

Playfield GI Fading with Flasher Overlay

Smooth playfield GI fading can be achieved using a VPX flasher placed at height 0.001 (not 0, which breaks) over the playfield. The flasher uses the GIOFF texture and its opacity is modulated by the GI timer from 0 to 100%. This avoids needing multiple intermediate playfield textures. The flasher's depth bias must be set carefully to avoid z-fighting with the playfield mesh and ball shadow.

Flasher Height

The flasher height of 0.001 works well; values with too many zeros (0.0001) cause visual artifacts in desktop view.

Insert Edge Artifacts in Texture Swapping

When swapping playfield textures for GI on/off, insert cutout edges in the OFF texture must match the ON texture exactly, or white/bright edge artifacts appear. Using stacked flashers (2x) with a light grey color (155) produces better insert text opacity than a single flasher. The PF background under inserts must be black in desktop mode to prevent bleed-through.

GI OFF Texture Cutouts

When creating GI OFF playfield textures, areas around inserts need careful cutouts to prevent visual artifacts. The cutout areas must be filled with black (not transparent) to prevent the underlying insert geometry from bleeding through. Insert borders in the OFF texture must exactly match the ON texture borders.

Skitso-Style Lighting Technique

Skitso's lighting approach: ambient lighting set very dark, majority of light comes from the table itself. Each bulb typically uses 2-3 VPX lights: (1) small bright light at height 28-30 with visible bulb and high transmit value, (2) larger dimmer light at height 1 shaped carefully for correct shadows, (3) optional third light at height 1 shaped to match space under plastics to simulate bouncing light. The modulate setting controls light "volume" (transparency) -- leave at 1 normally, use values like 0.9985 for subtle emphasis.

VLM Reflection Probe / Mirror Backwall

Mirror backwall using iaakki's method: flip normals in Blender for every primitive that should appear reflected. Each mirrored primitive uses the same UV map as the original. If UV maps change (e.g., after a rebake), the manual work must be redone. Mirror primitives with render probes still consume GPU resources even when invisible -- must be disabled in script (remove render probe from primitive), not just hidden.

VLM Bake Part vs Indirect Property

In VLM, unchecking "Bake Part" is equivalent to the old "indirect" property. Objects with Bake Part unchecked are hidden visually but still contribute to the scene (shadows, light bouncing). Note: "nosey VR guys" may still discover indirect objects if they look around the table in VR.

VLM GI Light Configuration for Ball Shadows

For VLM grouped GI, the collection should NOT be set to cast ball shadows. For split GI lights (individual lights), each should have "Cast Ball Shadow" checked. GI light names in Blender show up in lightmap names in VPX, so short names (e.g., "GI08" instead of "GI_008") reduce VPX element name length issues.

VPX Reflection Probe Normal Direction

When setting up reflection probes on primitives, the probe's normal direction must point in the correct direction for reflections to appear. Incorrect normals result in no visible reflection (primitive appears black). Testing all combinations (010, 001, 0-10) may be needed.

Troubleshooting

VLM Subdivision Modifier Causes Massive Poly Inflation

Missing NoExp suffix on Subdivision modifiers causes VLM mesh export to apply subdivisions, dramatically inflating polygon counts. LW3 went from 1.2M triangles (VLM.Bake) to 2.5M (VLM.Results) due to this. Fixing NoExp on bumper caps, posts, and other objects reduced batch time from 11 hours to 4 hours and file size from 268MB to 222MB.

Diagnostic: compare VLM.Bake vs VLM.Results tri counts -- a large jump indicates missing NoExp.

import bpy
collection_name = 'VLM.Bake'
if collection_name in bpy.data.collections:
    collection = bpy.data.collections[collection_name]
    for obj in collection.all_objects:
        if obj.type == 'MESH':
            for mod in obj.modifiers:
                if mod.type == 'SUBSURF' and mod.name == 'Subdivision':
                    mod.name = "Subdivision - NoExp"

VLM Nestmap Hanging on Large Parts Collection

VLM nestmap generation can hang when the Parts collection has too many connected faces. LW3's Parts collection had 262K verts / 455K faces vs JP's 71K/101K. Fix: split the Parts collection into separate collections (cab walls, light blockers, etc.) to reduce per-collection face counts. Tables imported from Octane workflows often have oversized Parts collections.

VLM Zero-Power Light Causes Lightmap Corruption

A VLM light with power set to 0 in the Room collection caused all lightmaps to render incorrectly (overexposed/blown out) while bakemaps looked fine. The fix was to disable or delete the zero-power light entirely. The Room collection gets its ambient lighting from the HDRI assigned via the World property.

VLM Flasher Lightmap Polygon Count Causes Stuttering

High-polygon wire ramps in the Parts collection cause flasher lightmaps to exceed 200K-500K polygons, causing stuttering when flashers activate. The issue is exponential: each flasher creates its own copy of the Parts lightmap mesh, so 5 flashers x 500K polys = 2.5M polys being alpha-blended simultaneously. Solutions: reduce wire ramp polygon count via merge-by-distance, or remodel with lower poly counts. Linked flashers (firing simultaneously) should share a single lightmap.

VLM HDR/LDR Nestmap Mixing

When render height is much smaller than nestmap texture size, VLM may nest lightmaps (HDR) together with bakemaps (LDR) in the same nestmap. Using 2K render height with 4K nestmap texture height, then scaling to 4K render / 8K nestmap for final release, is a recommended workflow.

VPX Frame Pacing vs VSync

Frame Pacing mode in VPX can cause stuttering on some tables while VSync mode runs smoothly on the same hardware. LW3 stuttered with Frame Pacing but not VSync, while other tables ran fine with Frame Pacing. GL version of VPX often doesn't work well with Frame Pacing at all. Try switching between modes before investigating table-specific causes.

Ball Appearance Too Dark

When the ball appears too dark, check: (1) table lighting environment settings, (2) Day/Night slider value (affects ball brightness significantly), (3) ball texture image, (4) VPX ball options in table settings. The Day/Night value being set too low is often the cause.

Resources

  • ImageOptim (Mac) / Pinga (Windows) for PNG compression
  • TFTC (Tales from the Crypt) as reference for Data East insert implementation
  • Wylte's Tommy measurements for Data East cabinet dimensions in VPX Table Size Manager