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
DisableLightingfor 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:
- Source the exact action figure used in the real machine (check eBay for specific model)
- Scan with a 3D scanner -- initial scan produced 2.5M triangles
- Reduce poly count for VPX -- Iron Monger reduced from 2.5M to 8K triangles with lightmap baking, visually indistinguishable
- 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:
- Import VPX table into Blender using the toolkit addon
- Set up lighting collections: GI, flashers, inserts, room lighting
- Bake lightmaps at target resolution (512 for testing, 4096 for release)
- Generate nestmaps (texture atlases packing all lightmaps)
- 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 Heightof 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):
- Split into two collections -- top (lid/cap at playfield level) and bottom (body in raised position)
- The cap is baked at playfield level with all its lightmaps
- The body is positioned at upper height with the 'hide' checkbox enabled so it doesn't shadow the cap/playfield
- If the lid is transparent, add a layer separator below it and declare it as the bake mask
- 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:
- Copy/paste lines from the Script Helper file into the table script WITH the
' VLM.end-of-line comments - On subsequent exports, specify the latest VPX file as reference
- The toolkit finds tagged lines and updates lightmap arrays automatically
- This handles lightmap names changing between renders
- Arrays are preferred over VPX collections because collections need updating after each render
Example auto-managed line:
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) andIMTopUpped(lid raised) - Fade between lightmap sets as toy moves using
IMOpacityvariable - 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
gilvlvariable 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
UpdateLightmapsub 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
#fffffffor alpha) - The insert text layer sits above the playfield as a separate primitive
- Insert primitives need: Z offset of -0.1, correct
DisableLightingsettings (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
Colorproperty only (notColorFull) for ball reflections - Set
ColorFullto 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)notrgb(255,0,0)-- center goes white - VPX has an 8-reflection limit per ball position -- use
Scale Meshto 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_185veROM works with fast flips;im_186veinitially 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-testedim_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 = VRRoomChoicebased onRenderingModeat 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
TransZin both_Hitand_UnHitsubs 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.velxandactiveball.velyby 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:
- Right flipper angles needed negative values (standard for right flippers)
LFState/RFStateinitialization was a red herring- Real fix: Change
LFPresstoRFPressin the right flipper key press handler - 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):
- Layout phase: Playfield scan, dimension verification, basic physics objects
- Art sourcing: Plastic scans/redraws, reference photos (Pinside, eBay), 3D toy scanning
- VPX construction: Script (ROM hookup, example table base), physics tuning, inserts, flashers, GI
- Blender import: Ramp modeling, material setup, playfield mesh
- Iterative rendering: Low-res test bakes (512-2K), identify issues, fix, re-render
- Polish: 3D insert integration, ball reflections, iron monger animation, SSF sounds
- Final render: 4K bake (~9 hours), comprehensive testing, release preparation
Contributors: sixtoe, tomate80, apophis79, iaakki, niwak, bord1947, embee1979, its.me.dazz, flux5009, fluffhead35, borgdog