Skip to content

Blood Machines (PUP)

VPW's original table based on Seth Ickerman's Blood Machines film, developed from scratch over 2021-2022. A landmark original VP table featuring FlexDMD integration, PUP pack support, IP-licensed movie assets (including ~70GB of production files from the film directors), Carpenter Brut soundtrack, and a 15-ball wizard mode multiball. The project pioneered many techniques for original table development including non-ROM game architecture, pentagon hatch mechanics, and RGB GI baking. Key contributors include iaakki, apophis79, oqqsan, astronasty, tomate80, sixtoe, lumigado, and many others.

Build Notes

IP Licensing Workflow

The team contacted director Seth Ickerman directly via social media. He granted permission enthusiastically and provided access to ~70GB of original 3D production assets via SFTP (spaceship models at 11M triangles, 4K movie frames, equirectangular environment images, sound effects, poster art). Key lesson: independent film directors are often approachable and supportive of fan projects, especially when the work is high quality and non-commercial. Carpenter Brut soundtrack was licensed with an agreement to link to their Bandcamp store.

VPX-to-Blender Workflow for Original Tables

The correct workflow: (1) Build all shots, ramps, physics, collidables, and mechanics in VPX first. (2) Export the entire table as a single OBJ file to Blender. (3) In Blender, adjust materials, lighting, and bake textures. (4) Import baked primitives back to VPX one by one. (5) Hide original visible collidables and overlay with baked primitives set as toys (no collisions). Making tables in Blender first causes alignment and physics issues -- always start in VPX.

Whitewood-First Design Philosophy

Build the layout and flow before visuals. Get shots, ramps, and mechs working before making 3D models. Score variables should be defined at the top of the script from the start. Don't code gameplay until the whitewood is done.

Non-PinMAME Architecture

As an original design, Blood Machines does not use PinMAME ROM emulation. All game logic is written in VBScript within the table file: score tracking, mode state machines, display rendering via FlexDMD, sound triggering, and light shows. The Orbital framework provides a foundation for common pinball mechanics.

Insert Placement Principles

For lower playfield: draw art first, then place inserts around it. For upper playfield: place inserts first (need to be in front of ramps), then draw art around them. Minimize inserts in lower playfield to give art room to breathe. Tracking info can go on DMD instead of playfield inserts. "Under paint" lights are possible -- light visible through painted playfield artwork.

Difficulty Tuning

Extensive difficulty tuning during beta phase. Key parameters: flipper strength (too strong = easy loops, too weak = cannot reach upper playfield), slingshot kick strength, bumper randomness, drain angle geometry. Beta testers provided feedback on average ball times -- target was 2-3 minutes per ball.

Scripting

vpmTimer 20-Event Limit

vpmTimer can only have 20 events pending simultaneously. After that, it silently eats additional events without executing them. This caused a VUK kick failure where a ball got stuck. Fix: replace vpmTimer usage with dedicated VPX object timers using UserValue as a counter.

' Pattern: VPX object timer with UserValue counter
Sub VUK2_Timer
    VUK2.UserValue = VUK2.UserValue + 1
    Select Case VUK2.UserValue
        Case 1: Flash3
        Case 4: Flash1
        Case 20: ' kick ball
        Case 21: VUK2.TimerEnabled = False
    End Select
End Sub

Best Practice

Use TimerEnabled = True/False (not 0/1) as VBScript can have issues with numeric boolean assignments. Call a dedicated Sub directly instead of vpmTimer for ball kicks.

vpmtimer.addtimer for Delayed Execution

vpmtimer.addtimer 1300, "MiMag.MagnetOn = 1'" executes the quoted command after the specified milliseconds. The apostrophe before the closing double quote is required (comments out an argument VPX tries to append). Useful for delayed sound playback and one-shot delays.

VBScript Duplicate Sub Names

VBScript allows duplicate Sub names without warning -- silently uses one and ignores the other. This caused hard-to-diagnose bugs during development. Always verify sub name uniqueness.

SwitchRecentlyHit Pattern for Orbit Detection

For reliable orbit counting during multiballs, check if a switch was "recently hit" within a time threshold instead of checking "last switch hit" (unreliable with multiple balls). SwitchHitRecentlyTime constant set to 1000ms. Timers should reset once a full loop is detected.

VBScript Light State Management

Technique for sharing one insert light between missions and multiball jackpots using bitmasked variables per light. Different blink patterns indicate which mode is active. Setting state=2 when already state=2 does nothing (no stutter).

Multiplayer State Management

Pattern for saving/restoring player state across turns: PlayerLights(4,20) array -- one entry per light per player. Save all light states and game variables on ball drain, restore on next ball. Limit rewards per game (e.g., extra balls max once per mystery).

Multiball Progress Prevention During Active Multiballs

Do not allow progress toward a multiball during an active multiball. Use a bMultiballMode boolean set explicitly during MB start/end subs (not auto-detected from ball count, which breaks when missions add balls). When true, prevent locking balls and collecting lit shots, but still allow lighting (blinking) shots.

Lampz does not work with VPX light state=2 (blinking). GetInPlayState returns 2 for blinking state which Lampz cannot process. Workaround: use timer-based blink patterns instead.

FlexDMD Setup and Patterns

FlexDMD uses .fnt/.png bitmap font pairs created with BMFont tool from TTF. Scale GIFs to 128x32 for DMD resolution. GIFs don't stop on table pause -- make last frame long (20 sec) to simulate stop. Lock/unlock render thread required when updating DMD. Keep DMD assets in external folder, not embedded in table file (32-bit VPX has RAM limits).

FlexDMD in VR -- Apron Display Conversion

FlexDMD on a flasher with "Use Script DMD" shows the same content on ALL DMD-enabled flashers. For VR, convert secondary displays from FlexDMD flashers to primitives with digit images (0-9), allowing independent display content.

Music Loop System

VPX PlayMusic() has no seek parameter. Workaround: create Windows Media Player COM objects in VBScript with volume control and seek capability. Music loops from soundtrack defined with start/end points in seconds. Alternative for very short loops: rip segments and use PlaySound with loopCount.

Table Pause Handling for FlexDMD

When VPX is paused, FlexDMD GIFs and action groups continue playing. Fix: lock the render thread on Table_Paused and unlock on Table_UnPaused.

3D & Art

Ship Model -- 4-State Texture Baking

Ship model at 29k triangles (reduced from 11M movie original). Four texture bakes needed: GI on/off crossed with cockpit lit/unlit. For RGB GI support, the GI render is used as an alpha mask on a separate primitive, with color controlled via VPX material updates at runtime.

Solidify Modifier for Depth-Bias-Free Layering

Technique for stacking 3 primitives of the same model at slightly different sizes to achieve smooth GI fading:

  1. In Blender, apply solidify modifier to create a slightly larger shell mesh
  2. Delete the inner mesh, keeping only the expanded outer shell
  3. UV mapping is preserved automatically
  4. Repeat for a third size

This eliminates depth-bias z-fighting issues entirely. Code to manage fading is approximately 20 lines.

Baked Flasher Reflections for All Cabinet Surfaces

Comprehensive flasher baking workflow: set all panel/PF materials to black with some reflectivity in Blender, enable only specific bulbs for each bake, render using Cycles, export as greyscale WebP images (use VPX flasher color to tint at runtime). File sizes remarkably small: all 4K flasher images total approximately 830KB using WebP.

Darkened PF Technique

The right method produces a dark PF that maintains crispness. For insert separation, crop inserts from the already-darkened PF so colors are consistent. Finalize insert art colors before darkening, as they become very difficult to fix later.

Pupillary Latency Lighting Effect

Simulates how human eyes adjust to brightness changes -- when GI turns off suddenly, the playfield appears darker than it actually is for a moment, then inserts appear brighter. Implementation uses slow-fading flashers at glass level controlled by Lampz.

360 VR Room

Created from an equirectangular image from the movie's production assets (nebula/space scene). Original 13000x6500px PNG compressed to only 1.7MB WebP. The VR room globe can be non-static, enabling rotation effects.

Emissive Materials for Neon Effects

VPX emissive materials (self-illuminating textures) create convincing neon without dynamic light overhead. For animated neon, swap between emissive and dark material states via timer.

Blender Normals Fix for Rendering Artifacts

Side blade primitive showing a visible line/cutout that moved with camera angle. Fix: In Blender, Edit mode -> Select All -> Mesh -> Normals -> "Set from faces."

WebP Compression Efficiency

  • 2K PNG: 700KB vs WebP: 27.5KB (96% reduction)
  • 13000x6500px equirectangular image: 1.7MB in WebP
  • All 4K resolution flasher images combined: only 830KB

Caveat: PaintShop Pro-converted WebP files lose transparency -- need to manually edit alpha to black.

Troubleshooting

VPX Magnet Behavior at Low FPS

VPX magnets behave differently when the game drops below 30fps -- they may start losing grabbed balls. In Blood Machines, the magnet can hold up to 14 balls during wizard mode. At low frame rates, the physics simulation steps become too coarse for the magnet to maintain its grip.

VPX Cannot Have Moving Collidable Objects

Hard VPX limitation: you cannot have a moving collidable object (except built-in flippers). To simulate rotating discs, the ball must be controlled via timer code with mathematical equations. Most tables cheat by destroying the real ball and replacing it with a ball-shaped primitive model during animation sequences.

Additive Flasher Stacking Limitation

Additive flashers cannot be stacked properly in VPX -- when overlapping, they "burn" (oversaturate). Workaround: use primitives instead of flashers for one of the reflection layers.

7.1 Audio Causing VPX Stalls

VPX can stall/freeze when using 7.1 surround sound, particularly the "7.1 Enhanced" option. Symptoms: game pauses as if alt-tabbed, pressing flipper button unfreezes it. Try regular 7.1 mode instead of Enhanced.

B2S "Run as EXE" Causing FlexDMD Focus Loss

When B2S is set to "Run as EXE" mode, it can cause FlexDMD tables to lose focus (playfield goes unfocused/blurry). Fix: disable "Run as EXE" in B2S settings.

POV Inclination Causes Ball Fall-Through

Changing POV inclination angle causes balls to fall through the playfield. Fix: add invisible blocker primitives underneath problem areas with HasHitEvent = False and Collidable = True. Recommended under ramp exits, wireform transitions, and VUK entry points.

Drop Wall vs Non-Collidable Wall for Hatches

Using a drop wall for the under-playfield hatch caused "Narnia balls" (balls teleporting). Fix: toggle collidable = false instead of dropping/raising the wall.

Game Knowledge

Rules Architecture

  • 7 missions (story-based, completable objectives with timer)
  • 3 character multiballs (Mima, Corey, Tracy)
  • 2 wizard modes: Mission Wizard (complete all missions) + Space Woman (complete all 3 MBs, 15-ball multiball)
  • Timed playfield multiplier via pink target wall (2X-5X, 30 seconds per tier)
  • Laser cannon loaded via hidden passage or loop shot, auto-fires after ~10 seconds
  • Under-playfield portal room accessible during certain modes

Sound Design Philosophy

Cardinal rule: "every switch or action should have a sound." Callouts are the most important element for player guidance. DMD info is secondary -- the player is not always looking at it. DOF shaker motor feedback also important. Add 200-300ms delay before callouts so they do not overlap with hit sounds.

Attract Mode Design

Attract mode should be mostly quiet with only occasional sounds. Full music playing in attract mode forces players to shut down the table during breaks.

Grace Period Design

Grace periods after mode end give players a brief window to score a final shot. For Blood Machines wizard mode: extra ball continues wizard, but draining during wizard ends it.

Resources

  • FlexDMD: https://github.com/vbousquet/flexdmd
  • Freezy DMD Extensions: https://github.com/freezy/dmd-extensions
  • vo.codes -- AI voice generation for prototyping callouts
  • WinMerge -- Essential for merging VPX script changes between team members
  • gtxjoe's Shot Tester -- Debug tool for testing shot paths