Skip to content

Iron Man (Stern)

Scripting and 3D art focused development using the VLM (VPX Lightmapper) toolkit workflow. Iron Man was VPW's first toolkit table, featuring extensive Blender baking, advanced insert integration, and comprehensive physics tuning across 77+ versions over 13 months.

Build Notes

VPX Table Extension for Ramps

When ramps extend beyond the back of the table (common on Stern games):

  • The VPX table dimensions must be enlarged to accommodate
  • Either extend the playfield image or use a playfield mesh
  • If using a playfield mesh, the VPX table still needs to be bigger
  • A flat playfield with no kicker holes can use the simplest mesh possible
  • Benefit of mesh playfield on flat table: can use DisableLighting for rendered tables
  • Glass height also needs adjustment -- original had it at 170 which blocked balls

VPX Dimension Manager Accuracy

VPX Dimension Manager uses 47 as multiplier instead of the correct 47.059, causing dimensions to be off by ~1-2 VP units. For Iron Man (20.25x45 inches), correct value is 952.94x2117.65 but VPX rounds to 951.75. Practically insignificant since tables are built from photographs anyway, but worth double-checking early.

3D Scanning Toys

Workflow for accurate toy models via 3D scanning:

  1. Source the exact action figure used in the real machine (check eBay for specific model)
  2. Scan with a 3D scanner -- initial scan produced 2.5M triangles
  3. Reduce poly count for VPX -- Iron Monger reduced from 2.5M to 8K triangles with lightmap baking, visually indistinguishable
  4. Upload to VPUniverse as community resource

Always check toy models for unnecessary geometry (threads, internal details) that can be stripped.

Plastic Redraw Techniques

When playfield plastic scans are blurry or missing (common for pieces under ramps):

  • Search online for better photos before redrawing
  • Use good scan patterns from other plastics as templates for the bad ones
  • Upscale source images for redraw to give more precision room
  • Use photos from Pinside sellers/modders as high-quality references
  • Instruction card textures can be sourced from eBay listing photos
  • Keep full-size PNGs for Blender even if VPX uses compressed versions

VLM Toolkit Workflow

Lightmapper Process

The VPX Lightmapper (VLM) toolkit workflow for rendered tables:

  1. Import VPX table into Blender using the toolkit addon
  2. Set up lighting collections: GI, flashers, inserts, room lighting
  3. Bake lightmaps at target resolution (512 for testing, 4096 for release)
  4. Generate nestmaps (texture atlases packing all lightmaps)
  5. Export back to VPX with auto-generated script helper

Bake times: 1.4 hours at 2K, ~9 hours at 4K (512 samples took ~3.5 hours)

Key learnings:

  • Objects in bake collections must be meshes (convert curves first)
  • Empty objects in bake folders cause loop errors
  • Object names too long get truncated, breaking flasher references
  • Use Render Height of 4092 instead of 4096 to leave 4px padding and prevent playfield splitting
  • Always clear cache before rendering to avoid stale results

Bake Collection Structure

VLM requires two mandatory parent collections: VLM.Bake (defines what gets baked) and VLM.Lights (defines lighting conditions).

Within bake collections:

  • Opaque non-movable parts go in a single group collection
  • Transparent parts (clear ramps) need separate layer collections with depth bias decreasing from 0 layer by layer
  • Inserts that bake to the playfield need a 'Bake To' property pointing to a playfield duplicate (Playfield.BT) in a Setup collection

Layer Separator for Transparent Parts

Layer separators are planes placed between transparent parts and what's behind them to enable alpha transparency rendering. The separator uses a premade toolkit material.

Critical: normals must face the correct direction -- blue face toward the transparent part (pointing away from the camera/viewer), red face outward.

Each transparent collection must have its 'bake mask' property set to the corresponding layer separator. Overlapping transparent objects must be split into separate non-overlapping layers (typically 2-3 layers needed).

Transparent plastic lid rendering: Layer separator must be correctly positioned below the plastic -- if inside the plastic, it causes dark rendering. Check "hide" on plastic and screw objects so they don't cast shadows on objects below.

Nestmap Packing

How VLM nestmap packing works:

  • The toolkit creates as many nestmap textures as needed (Iron Man used 14 at 4K)
  • In "Fit PF" camera mode, the playfield height matches the render height setting
  • A 4K render produces a playfield that is 4096 pixels tall within a larger texture
  • VPX may downscale images when it runs out of memory -- check Image Manager for warnings
  • Open tables with x64 VPX version if images are missing

The toolkit's automatic LOD (level of detail) can make small toys look poor -- manually bake important character models separately. Objects that overlap (like Wolverine's claws on chest) need traditional baking or splitting.

Insert Light Naming Convention

VLM insert lights follow a specific naming convention. Each VPX light has 4 components: l (light), p (primitive), poff (off primitive), h (bloom). Numbers must be consistent across all components (e.g., l8, p8, p8off, l8h).

Light type prefixes: lamps use L prefix, flashers use F prefix (toolkit adds 100 to the number, so F10 becomes light 110). Choice depends on how the ROM drives the light: solenoid-driven = flasher, lamp callback = lamp.

Dual insert/flasher lights: Some inserts have two bulbs behind them: one lamp and one flasher. The lamp uses its normal number (e.g., L55), while the flasher uses a diverted solenoid number (e.g., L130 from solenoid 30 via setlamp 130). Create separate light pairs for each mode. Iron Man has three such dual-light pairs: L55/L130, L21/L122, L10/L123.

Object Visibility Settings

Two visibility modes for VLM baking:

Hide -- object is baked (receives correct lighting) but doesn't affect surroundings (no shadows cast on other objects). Used for moving elements like flipper bats and pop-up toys (Iron Monger, X-Men Nightcrawler).

Indirect Only -- object influences the scene lighting (reflections, shadows) but is not exported to VPX. Used for front glass, reference geometry, and objects that should affect baking but not appear as VPX primitives.

Pop-Up Toy Baking Pattern

For pop-up toys that rise from the playfield (Iron Monger / X-Men Nightcrawler pattern):

  1. Split into two collections -- top (lid/cap at playfield level) and bottom (body in raised position)
  2. The cap is baked at playfield level with all its lightmaps
  3. The body is positioned at upper height with the 'hide' checkbox enabled so it doesn't shadow the cap/playfield
  4. If the lid is transparent, add a layer separator below it and declare it as the bake mask
  5. Join lid and any decals (Ctrl+J) into a single object before marking as 'hide'

Split vs Group Collections

VLM collections can be set as group (all parts merged into single bake output) or split (each part exported separately).

Use group for: GI lights, pop-up toy bodies, any element that moves as one unit (e.g., Wolverine rotating)

Use split for: movable items that animate independently (switch wires, targets, gates, spinners), flashers, inserts

Modifier Handling

The VLM toolkit automatically applies Blender modifiers during export unless the modifier name contains 'NoExp'. Typical setup: Solidify modifier (applied for export) + Bevel modifier with 'NoExp' in name (rendered and baked but not exported to VPX mesh). This keeps the exported mesh simpler while the bake benefits from the visual refinement.

Python script to auto-mark small bevels as NoExp:

import bpy
for obj in bpy.data.objects:
    for modifier in obj.modifiers:
        if 'NoExp' not in modifier.name and (modifier.type == 'BEVEL' and modifier.width < 0.1):
            modifier.name = f'{modifier.name} - NoExp'

Backface Removal Angle

The VLM toolkit has a backface removal option during export:

  • Set to 90+ degrees: no removal (all faces exported)
  • Set to 0: removes all non-front-facing faces
  • Intermediate values provide threshold-based removal

Works by computing dot product between camera view direction and face normal. Incorrect normals can cause valid faces to be wrongly removed.

Spinner and Flipper Baking

For moveable objects (spinners, flippers) in the VLM toolkit:

  • Must check "Use Obj Pos" in the toolkit settings for rotation to work correctly
  • Spinners need unwrapping before baking; width/height can be any ratio matching the unwrap
  • After each bake, spinners may need manual correction: copy angle from RotX to ObjRotX, track on RotX
  • "Fixed view glossy" node tree needed for thin metallic objects like wire gates

Automatic Script Update System

The VLM toolkit can automatically update the VPX table script after each bake/export:

  1. Copy/paste lines from the Script Helper file into the table script WITH the ' VLM. end-of-line comments
  2. On subsequent exports, specify the latest VPX file as reference
  3. The toolkit finds tagged lines and updates lightmap arrays automatically
  4. This handles lightmap names changing between renders
  5. Arrays are preferred over VPX collections because collections need updating after each render

Example auto-managed line:

Dim IM_001_LM: IM_001_LM=Array(IMBot_LM_GI, IMBot_LM_Lit_Room, ...) ' VLM.Array;LM;IM_001_LM

Bake Cache and Re-rendering

VLM stores rendered results in a 'bakes' folder in the same directory as the .blend file. To force re-render: delete the bakes folder, then run batch all again. "Batch all" runs all steps sequentially (meshes, renders, nestmaps, export). Adding/changing any light requires a full re-render as lighting affects everything.

Must be in Object Mode before pressing the render/batch button. VLM baking fails if Blender is in Edit Mode.

Glass Transparency Node Group

The VLM toolkit includes a 'Glass Transparency' node group that handles darkening from light passing through glass material (like the pinball machine's front glass). Plug it between the shader output and material output in Blender. Apply to plastics and flasher dome materials.

Weighted Normals Modifier

The Weighted Normals modifier in Blender improves shading on hard-surface models (metal plates, machine parts) by weighting face normals based on face area. Combined with Solidify and Bevel (with NoExp) modifiers, it creates realistic-looking metal parts.

Scripting

Iron Monger Animation

Complex animated toy (Iron Monger rising/lowering) with toolkit lightmaps:

  • Two bake collections needed: IMTop (lid down) and IMTopUpped (lid raised)
  • Fade between lightmap sets as toy moves using IMOpacity variable
  • Wire both positions to Lampz callbacks so flashers affect both states:
Lampz.Callback(129) = "UpdateLightMap IMTop_LM_flashers_l129, IMOpacity, "
Lampz.Callback(129) = "UpdateLightMap IMTopUpped_LM_flashers_l129, 100.0 - IMOpacity, "
  • Z-fighting fix: swap visibility of opaque primitives at mid-point of animation rather than fading both simultaneously
  • Animation timer should be 16ms (not 1ms) to reduce CPU load
  • GI level must be cross-referenced from Lampz in animation code; don't set opacity directly as Lampz will overwrite it

GI Fading with Lampz

Common GI implementation errors on toolkit tables:

  • Do NOT use for-loops to change each light state directly -- fading is handled by Lampz callbacks
  • Only the Lampz light state change and relay sound are needed in GI subs
  • The gilvl variable should store the latest fading level from Lampz callbacks (can be a decimal like 0.34535), not set as binary 0/1
  • For toolkit projects, create a custom UpdateLightmap sub for each GI-affected collection
  • Lampz callbacks provide per-channel fading level that can be referenced anywhere via stored global variable

Material Fading with GI

Pattern for fading material color with GI level changes using Lampz callbacks:

Sub UpdateMaterialFromGI(materialName, rgbOn, rgbOff, giLevel)
    ' Interpolate between on/off colors based on GI fading level
    ' Tie to Lampz assignment for GI channel
End Sub

Insert text material fades from rgb(30,30,30) (GI off) to rgb(255,255,255) (GI on). The callback fires on every GI channel update event, providing smooth analog fading rather than binary on/off.

3D Insert Integration

Combining Flupper's 3D inserts with VLM toolkit-rendered tables:

  • Mark toolkit inserts as "indirect only" in Blender so they generate reflections but aren't baked into the playfield
  • Cut insert shapes out of the playfield texture using a black/white mask (must be pure #ffffff for alpha)
  • The insert text layer sits above the playfield as a separate primitive
  • Insert primitives need: Z offset of -0.1, correct DisableLighting settings (reversed for off-state)
  • Insert text brightness must be tied to GI using material color fading
  • Ball reflections from inserts need separate hidden lights at Z=-10 to avoid changing insert appearance

Ball Reflections for Inserts

Proper ball reflection setup for VPX inserts:

  • Use VPX light Color property only (not ColorFull) for ball reflections
  • Set ColorFull to black unless the light needs to be visible to the eye
  • For LED inserts: use more saturated/pure RGB values (closer to full red/blue/etc.)
  • For incandescent: use warmer, more gray tones (more green than blue)
  • Example: red insert reflection = rgb(250,30,15) not rgb(255,0,0) -- center goes white
  • VPX has an 8-reflection limit per ball position -- use Scale Mesh to adjust effective range
  • Process: Start in F6 mode, use alt+arrows to zoom, throw in a ball, adjust colors iteratively

Ball Brightness as Function of GI

Ball color/brightness should dynamically match the table's lighting state. Implementation calculates brightness as a function of GI opacity and distance to nearest GI light. Without this, the ball appears unnaturally bright when GI goes dark. Tuning: minimum brightness offset of ~55-100 prevents ball going completely black in dark scenes; value depends on room lighting preference.

Lampz Fade Speed: LED vs Incandescent

Lampz fade speed values differ significantly between bulb types:

  • LEDs: FadeSpeedUp = 1/2, FadeSpeedDown = 1/8 (fast, near-instant)
  • Incandescent: FadeSpeedUp = 1/40, FadeSpeedDown = 1/120 (slow, gradual)
  • When Lampz uses a -1 interval timer, denominators are in milliseconds
  • Iron Man Vault Edition uses LED GI, so needs fast fade values
  • Mismatched fade speeds cause inserts to appear constantly lit rather than flickering

SAM ROM Fast Flips

For Stern SAM-era ROMs, fast flips use InitVpmFFlipsSAM instead of UseSolenoids=2:

  • Fast flip support must be hard-coded per ROM version in VPinMAME
  • Newer ROM versions may not have fast flip support added yet (use older ROM as fallback)
  • im_185ve ROM works with fast flips; im_186ve initially did not
  • The memory addresses are literally injected into the emulation
  • Without fast flips, flipper delay is noticeable and makes the table feel harder

ROM selection for release:

  • im_185ve: Works with fast flips, well-tested
  • im_186ve: Newer, has bug fixes (SHIELD award, difficulty settings, sound attenuation) but may have fast flip issues
  • Always upload ROMs to VPUniverse before release to prevent user confusion

KickBall Sub for Impulse Plunger

Pattern to replace VPX kickers with more reliable impulse plungers (from rothbauerw):

Function PI()
    PI = 4*Atn(1)
End Function

Sub KickBall(kball, kangle, kvel, kvelz, kzlift)
    dim rangle
    rangle = PI * (kangle - 90) / 180
    kball.z = kball.z + kzlift
    kball.velz = kvelz
    kball.velx = cos(rangle)*kvel
    kball.vely = sin(rangle)*kvel
End Sub

Usage: Set KickerBall via sw_Hit event, then call KickBall KickerBall, 180, 55, 0, 0. Can also toss ball upward (useful when Iron Monger rises under ball).

VLM Flipper Rotation Code

VLM flipper lightmap rotation requires calculating angle from current flipper position:

Dim vlmLFlipperAngle: vlmLFlipperAngle = -180 + (120 - LeftFlipper.currentangle)
Dim vlmRFlipperAngle: vlmRFlipperAngle = -180 - (120 + RightFlipper.currentangle)
Flippers_001_BM_Dark_Room.RotZ = vlmLFlipperAngle
Flippers_001_LM_Lit_Room.RotZ = vlmLFlipperAngle

Each flipper has BM (bake map) and LM (light map) primitives for dark room and lit room, plus per-flasher lightmaps. All must be rotated together in the FrameTimer.

VR Room Automatic Detection

Pattern for automatically detecting VR mode and setting room choice:

  • Set VRRoom = VRRoomChoice based on RenderingMode at game start
  • Players don't need to edit script to set VR room -- it auto-detects
  • Works with lit room and dark room options

VPX Primitive Animation via Timer

VPX walls don't have UnHit events like triggers. To animate primitives on hit (e.g., Iron Monger shaking):

  • Cannot use TransZ in both _Hit and _UnHit subs for walls
  • Must use an animation timer (like sling animations) to return to original position
  • Start timer on hit, animate return over several frames

LUT/Color Saturation Options

LUTs (Look-Up Tables) for color adjustment should be presented as "Color Saturation" in the options menu, not "LUT" (user-facing description vs implementation detail). When LUT is disabled, don't set to neutral LUT -- disable it entirely for better performance. Desaturation options from 0-100% in 10% steps provide good range. Default should be the neutral/1to1 option.

Physics

Playfield Friction

Playfield friction at 0.02 is far too low (causes ice-rink effect). Correct range is 0.15 to 0.25.

Physics Materials

The "FO" suffix on physics materials means "Fall Off" -- higher elasticity falloff means more damping. MetalFO is less bouncy the faster the ball hits compared to plain Metal. Useful for walls and targets where high-speed impacts should absorb more energy.

Toy Impact Absorption

For large toys that absorb ball energy on real machines (Iron Monger, TOTAN genie):

  • Use low elasticity on the collision object
  • Can also multiply activeball.velx and activeball.vely by a damping factor (e.g., 0.69) in the hit sub
  • Combination of low elasticity + velocity damping gives most realistic feel
  • Hit threshold tuning: default of 2 was too sensitive; 4-5 recommended to prevent registering grazing hits

Ramp Exit Spin Randomizer

The ramp exit randomizer controls maximum ball spin after exiting ramps. Reducing the multiplier maximum from 70 to 50 produces more natural-feeling ball behavior -- the ball still gets varied spin but without exaggerated rolling.

Physics Testing Checklist

Structured physics testing checklist:

  • Plunger lane: ball centering groove
  • Ramp ball jams at gates: check glass height and ball going airborne
  • Ball passing through rising toys: need blocker objects
  • Inlane wall friction: fast balls appearing to speed up (perennial VPX issue)
  • Target momentum absorption: toys like Iron Monger need falloff/elasticity tuning
  • Magnet grab strength: fast center balls vs slow approach
  • Ramp rejection angle: compare against tutorial videos (PAPA videos are ideal reference)
  • Stand-up target overhang: should overlap their holes slightly

Game Knowledge

Iron Man Insert Lights

Iron Man Vault Edition uses white LEDs (part 112-5033-08) for all insert and flasher bulbs. The color comes from the plastic insert material itself, not the light source. When the insert "blows out" (at high intensity), it appears white because the light overwhelms the insert's color filter.

Modern Stern Pinball Machines Don't Have Physical Knockers

Modern Stern pinball machines (including Iron Man) don't have physical knocker mechanisms -- the knocker effect is purely a ROM sound effect.

Troubleshooting

Z-Fighting Between Lightmap Layers

Z-fighting (visual flickering between overlapping surfaces) in toolkit-rendered tables:

  • Common cause: opaque primitive and lightmap primitive at same position
  • Fix 1: Scale the opaque primitive to 99.99% so it sits slightly behind the lightmap
  • Fix 2: For animated objects, swap visibility of opaque prims at mid-point of animation instead of fading both
  • An opaque prim with opacity=0 can still cause z-fighting -- must set visible=false
  • Nestmap alpha mask value of 180 (vs default) eliminates jagged insert edges but makes holes slightly larger -- may need to scale inserts by 1.01 to compensate

VPX Gate Physics

One-way gates allowing balls to pass backward:

  • Root cause: gate friction set to 0, plus gate height was accidentally set to 105 for all gates (should have been selected individually)
  • Zero friction on VPX gates "can do very odd physics things"
  • Fix: Set gate friction to 0.1+, verify each gate's height individually

When editing grouped collections in VPX, changes may apply to all objects instead of the selected one -- never EVER group collections.

VPX Collection Membership Bugs

Objects belonging to multiple VPX collections can cause unpredictable behavior:

  • Objects in more than one collection sometimes don't work correctly -- pattern is inconsistent
  • Shadow rendering can be affected by object ordering within collections
  • Related to VPX's internal parsing behavior

FlipperTricks Bug

Flipper nudge not working traced to a copy/paste bug: LFPress was used for right flipper key press calls instead of RFPress. The right flipper state was permanently stuck at 1. Debugging process:

  1. Right flipper angles needed negative values (standard for right flippers)
  2. LFState/RFState initialization was a red herring
  3. Real fix: Change LFPress to RFPress in the right flipper key press handler
  4. Uninitialized VBScript variables default to Empty, which can cause intermittent issues in state machines

LampFader Array Size

When VPX crashes on table load with LampFader/Lampz errors, check that the array size in the Lampz class matches the highest lamp number used. If lamps go up to 150 but arrays are dimensioned to 140, any lamp above 140 causes an array bounds error.

GI Not Updating

If VLM table parts appear dark/untextured despite correct baking, the GI may not be hooked into the Lampz system. The GI lightmaps need to be registered in Lampz callbacks to update properly.

VPX Element Name Length Limit

VPX has a maximum element name length of 31 characters. The VLM toolkit will trim names that exceed this limit, which can cause name collisions. Long Blender collection names get concatenated with light names during export.

Fix: use short collection/object names in Blender (e.g., "IM" instead of "IronMonger", "flight" instead of "flasherlight").

Names starting with underscore _ crash VPX.

Nestmap Texture Stutter

First-time stutter when hitting bumpers/flashers is caused by lazy GPU texture loading of large nestmap textures. Fix implemented as "FlasherPreloader" -- briefly activates flasher callbacks to level 25 (not 255, avoiding visible flash) during table_init to force GPU to load all nestmap textures.

Better solution: create small hidden objects (inside apron) for each nestmap, visible by default, then toggle off after a delay -- ensures all nestmaps are loaded with zero visual artifacts.

VPX 32-bit Memory Limitations

VPX running in 32-bit mode has severe memory limitations for VLM toolkit tables. When memory is exhausted, textures get silently downscaled or go missing. In 10.7+, a warning dialog was added when table fails to load fully due to low memory.

Saving a table after this occurs will save the degraded textures permanently. The 64-bit version is needed for complex VLM tables.

VRThings Variable Scope Bug

VRThings variable was declared inside a sub (local scope) instead of at module level, causing errors when other subs tried to reference it. Manifested as errors when entering certain VR settings menus (particularly GI settings).

Fix: add Dim VRThings at module level or inside each sub that references it (specifically the UpdateGI sub).

Computer Sleep Interrupting Renders

Long VLM renders (hours) can be interrupted by Windows sleep/power saving. Fix: adjust power saving options, or use Windows PowerToys "Awake" utility (taskbar toggle to keep computer from sleeping). Critical for overnight 4K renders.

Blender Console Error from BlenderKit

Errors in the Blender console window about "connection denied" during VLM baking are often from the BlenderKit add-on (material library), not the VLM toolkit. These errors are harmless to the VLM bake process and can be ignored.

Blender Camera Clipping

If objects disappear in Blender's orthographic view, the issue is camera clipping distance. Fix: Adjust the clip start/end values in the viewport properties panel (N-panel > View).

Tools & Resources

Blender Outliner Navigation

Useful Blender outliner navigation shortcuts:

  • Press Home key over outliner to jump to current selection
  • Use filter text box above outliner to search
  • Disable 'object content' from outliner for a cleaner view
  • Use -/+ keys over outliner to collapse/expand everything
  • Use H / Shift+H / Alt+H to show/hide objects in viewport
  • Dot key (numpad) focuses viewport on selected object

Blender on a laptop without numpad is significantly harder -- an external keyboard is strongly recommended.

Best Practices

VLM Table Version Control

VLM table version control challenges: new VLM renders overwrite old primitives/textures, breaking script hookups done by others.

Workflow:

  • Keep the VPX file in the same folder as the .blend file
  • The toolkit uses the linked VPX as the base for export
  • Script changes should be kept in the auto-generated section (marked ' VLM) which updates automatically
  • Custom script work (physics, subs) goes outside VLM-managed sections
  • Always backup the .blend file before major changes

Iron Man was VPW's first toolkit table and suffered from version control growing pains -- the team learned to coordinate bake/script work more carefully for subsequent tables.

Development Pipeline

Full development pipeline for a Stern recreation (Iron Man, 77+ versions over 13 months):

  1. Layout phase: Playfield scan, dimension verification, basic physics objects
  2. Art sourcing: Plastic scans/redraws, reference photos (Pinside, eBay), 3D toy scanning
  3. VPX construction: Script (ROM hookup, example table base), physics tuning, inserts, flashers, GI
  4. Blender import: Ramp modeling, material setup, playfield mesh
  5. Iterative rendering: Low-res test bakes (512-2K), identify issues, fix, re-render
  6. Polish: 3D insert integration, ball reflections, iron monger animation, SSF sounds
  7. Final render: 4K bake (~9 hours), comprehensive testing, release preparation

Contributors: sixtoe, tomate80, apophis79, iaakki, niwak, bord1947, embee1979, its.me.dazz, flux5009, fluffhead35, borgdog