Baby Pac-Man PinMAME
A complete technical reference for the modified PinMAME build that runs the Baby Pac-Man ROM and drives a real physical playfield through two Ultimarc USB controller boards.
01 Project Overview
Baby Pac-Man (Bally, 1982) is a hybrid arcade/pinball machine — the upper half is a video game screen running the Pac-Man maze, and the lower half is a real pinball playfield with flippers, drop targets, saucers, and solenoids. This project replaces the original MPU board with a modern PC running the game ROM under PinMAME, while driving the original physical hardware through two USB boards.
Two Roles, Two Boards
I-PAC Ultimate I/O
Drives the entire lamp matrix — all 56 playfield lamps across 30 SCR positions × 2 AC phases. Also accepts up to 48 switch inputs (joystick, buttons, playfield switches).
PAC-Drive
Drives flippers, saucers, drop target reset, and the outhole kicker. Simple on/off HID device, up to 48V with external supply.
Both boards connect via USB and are accessed through a single Windows DLL, pacdrive32.dll, which is loaded at runtime by the custom pacdrive.c hardware abstraction layer added to PinMAME.
02 System Architecture
The full signal path from ROM execution to physical hardware, running at 60 Hz:
Inputs (reverse direction)
UpdateOutputs() caches the previous state of every lamp group (OutputLEDsPrevious[]) and the solenoid word (OutputsSolenoidPrevious). USB commands are only sent when a value actually changes, keeping USB traffic minimal.
03 Key Source Files
pacdrive32.dll, manages the 112-element output array, and sends USB commands to both boards. This is the main file added to PinMAME for this project.update_hW() and update_hw_byte() — the bridge that converts the emulated lamp matrix and solenoid state into calls to pacdrive.c. Called once per VBLANK.byVP_lampStrobe), solenoid decoding (pia1b_w), and the VBLANK interrupt handler.BYVP_babypac_PORT — hardcoded default keyboard key assignments for joystick, flipper, start, coin, and tilt. Also defines all BYVP_SW* switch constants.FLIP_SWNO(0,1) which sets the right-flipper switch number.LoadPacDrive() during machine initialization when the -ultimateio flag is present.ultimateio field in the pmoptions struct.04 Startup & Configuration
rem babypac.bat
call setvol 80
pinmame babypac -ultimateio -joystick -dmd_only -scanlines -skip_gameinfo
| Flag | Effect |
|---|---|
| -ultimateio | Enables the hardware I/O path — calls LoadPacDrive() on startup |
| -joystick | Enables gamepad/joystick input (I-PAC can report as HID gamepad) |
| -dmd_only | Suppresses the desktop emulator window; video goes to the DMD |
| -scanlines | Applies CRT scanline filter to the video output |
| -skip_gameinfo | Bypasses the splash/info screen at startup |
Initialization Sequence
On exit, UnloadPacDrive() calls InitOutputs() (turns everything off), frees memory, then FreeLibrary(hio).
PacInitialize() is called once at startup and enumerates all connected Ultimarc USB devices. If either the I-PAC Ultimate I/O or the PAC-Drive is unplugged when PinMAME starts, LoadPacDrive() returns failure and neither board receives any commands for the entire session — lamps and solenoids will both be dead even if one board is present. Always connect both boards before running babypac.bat.
LoadPacDrive() fails (DLL missing or no device found), PinMAME prints "Unable to communicate with Ultimate IO." and continues running — the game still works as a pure emulator, just without hardware output. The exit(1) call is commented out in mame.c:354.
05 I-PAC Ultimate I/O Board
The I-PAC Ultimate I/O handles all 96 lamp outputs and accepts up to 48 switch inputs. It appears as a standard USB HID device — no special Windows driver needed.
USB Identity
- Vendor ID0xD209 (Ultimarc)
- Product ID0x0410
- USB speedFull Speed USB 2.0
- DriverNone needed (HID)
I/O Specs
- LED outputs96 channels
- Brightness256 levels per channel
- Switch inputs48 dedicated pins
- Power in5V DC, 2.1mm barrel
- Per-LED current20mA max
How the LED command works
Every frame, UpdateOutputs() advances a per-lamp brightness (0–255) toward its ROM target, then pushes all 96 outputs to the board in one bulk USB transfer. Driving real brightness — not just on/off — is what produces the incandescent-style fade. See Incandescent Lamp Fade for the full model and the user-tunable controls.
// pacdrive.c: UpdateOutputs() — lamp section (Approach C)
for (int i = 0; i < 96; i++) {
int target = Outputs[i] ? 255 : 0; // ROM on / off
// ease LedIntensity[i] toward target (exp / s / lin),
// then map through the perceptual gamma table:
pushBuf[i] = GammaLUT[LedIntensity[i]];
}
Pac64SetLEDIntensities(hw_id_ultimateio, pushBuf); // all 96 in one call, ~30 Hz
The board's 256-level PWM per channel is what makes the fade possible. (Earlier builds sent pure on/off group states with Pac64SetLEDStates; the current build sends per-LED brightness with Pac64SetLEDIntensities.) The hw_id_ultimateio constant is 0 — the first device enumerated by the DLL.
Input side
The I-PAC's 48 inputs are wired to every physical switch on the cabinet and playfield. Using WinIPAC V2, each input is assigned a keyboard key or gamepad button. When a switch closes, the I-PAC sends that key to Windows, which PinMAME reads and routes to the switch matrix. See the Switch & Input Mapping section for the full table.
06 PAC-Drive Board
The PAC-Drive is a simple 16-channel output board used to fire the pinball solenoids — flippers, saucers, drop target reset, and the outhole kicker. Each channel is either on or off; the entire state is sent as a single 16-bit word.
USB Identity
- Vendor ID0xD209 (Ultimarc)
- Product ID0x1500
- Protocol4-byte HID report (type 0x03)
- DriverNone needed
Output Specs
- Channels16 (on/off)
- Max current500mA per channel
- Voltage (USB)5V
- Voltage (ext)Up to 48V
- Multiple boardsUp to 4 on one PC
How the solenoid command works
// pacdrive.c: UpdateOutputs() — solenoid section
unsigned short binary_short = 0;
binary_short |= Outputs[outputNum + 0] << 0; // bit 0 → PAC-Drive channel 1
// ... through ...
binary_short |= Outputs[outputNum + 15] << 15; // bit 15 → PAC-Drive channel 16
if (OutputsSolenoidPrevious != binary_short) {
OutputsSolenoidPrevious = binary_short;
PacSetLEDStates(hw_id_pacdrive, binary_short);
}
The hw_id_pacdrive constant is 1 — it's the second device enumerated by the DLL after the Ultimate I/O.
Board Layout — Screw Terminals
The PAC-Drive uses screw terminals along both edges. Output channels are numbered 1–16. There is also a GND terminal. The board is powered entirely by USB — no external power connector for logic. When connecting an external high-voltage supply through the driver PCB, the external supply's ground must be tied back to the USB ground (see Build & Wiring Guide).
| Terminal | Net | Notes |
|---|---|---|
| 1–16 | Output channels | Open-drain. Pulled LOW when software sets the corresponding bit. Pulled HIGH by the driver board's pull-up resistor (RN4) when idle. |
| GND | Ground | USB ground. Must be tied to common ground bus shared with I-PAC and Meanwell supply. |
Ordering the Correct Version
Ultimarc sells several PAC-Drive variants. For this project you need the "ID #1 Special" version (also described as "ID #1 – outputs default OFF"). This version:
- Has its hardware board ID burned as 1, so the DLL assigns it
hw_id = 1 - Defaults all outputs to OFF at power-up — essential for safety, so solenoids don't energize before PinMAME initializes
The standard PAC-Drive (ID #0) would conflict with the I-PAC Ultimate I/O, which also uses hw_id = 0. Do not substitute the standard version.
hw_id = 0 and the PAC-Drive "ID #1 Special" always appears as hw_id = 1, regardless of which USB port they are plugged into or which is enumerated first.
Wiring PAC-Drive to the Custom Driver PCB
Run individual signal wires from the PAC-Drive's numbered screw terminals to the JP1–JP4 header pins on the BabyPacDriver-v4 PCB. Also bring over a GND connection to JP9 pins 1 & 2, and a 5V source to JP9 pin 3 (see JP9 Power Connector table in Custom Driver PCB).
| PAC-Drive terminal | → Driver board | Solenoid |
|---|---|---|
| 1 | JP1 pin 1 | Left Flipper |
| 2 | JP1 pin 2 | Right Flipper |
| 3 | JP1 pin 3 | Out Hole Kicker |
| 4 | JP2 pin 1 | Left Maze Saucer |
| 5 | JP2 pin 2 | Right Maze Saucer |
| 6 | JP2 pin 3 | Drop Target Reset |
| 7 | JP3 pin 1 | Drop Target #1 (Left) |
| 8 | JP3 pin 2 | Drop Target #3 (Center) |
| 9 | JP3 pin 3 | Drop Target #5 (Right) |
| 10–16 | JP4 / unassigned | Unused — available for expansion |
07 Custom Solenoid Driver PCB
The files in Solenoid Driver board schematics/ contain the design for a custom PCB (Eagle schematic + board layout + BOM) that sits between the PAC-Drive's 5V logic outputs and Baby Pac-Man's original high-voltage solenoid coils.
This PCB provides:
- Voltage level translation from PAC-Drive 5V logic to pinball coil voltage (24–50V)
- Per-channel flyback diode protection to absorb solenoid back-EMF
- Isolation between the USB logic side and the high-voltage coil side
- 12 driver channels (Q1–Q12), 9 of which are currently assigned to solenoids
Signal Path Through the PCB
Signals travel left to right through the board:
When idle: PAC-Drive output not asserted → RN4 holds signal HIGH → SN74HCT04N input HIGH → output LOW → MOSFET gate LOW (also held by RN3 pull-down) → MOSFET off.
The inversion is intentional: it allows the PAC-Drive's active-LOW outputs to drive the IRLZ34N gates correctly through a standard push-pull CMOS inverter.
MOSFET Pinout — IRLZ34N (TO-220)
Each driver channel uses an IRLZ34N logic-level N-channel MOSFET in TO-220 package. With the flat face toward you and leads pointing down:
Flat face → ┌──────────────┐
│ IRLZ34N │
│ │
└──┬───┬───┬──┘
│ │ │
Pin1 Pin2 Pin3
Gate Drain Source
(tab)
↑ ↑
Left Right
| Pin | Position | Net | Connects to |
|---|---|---|---|
| 1 | Left | Gate | Gate resistor (RN1 or RN2 output) — receives signal from SN74HCT04N inverter |
| 2 (tab) | Middle / heatsink flange | Drain | Output terminal (JP5–JP8) — connects to solenoid coil |
| 3 | Right | Source | GND — common ground bus |
Power Connector — JP9
JP9 is a 3-pin screw terminal that supplies 5 V logic power to the inverter ICs (U1, U2). It must be connected before any signals are driven.
| JP9 Pin | Net | Voltage | Connect to |
|---|---|---|---|
| Pin 3 | VCC | +5 V | 5 V rail — PAC-Drive USB 5V output or regulated 5V supply |
| Pin 2 | GND | 0 V | Common ground shared with PAC-Drive |
| Pin 1 | GND | 0 V | Common ground (second GND pin) |
5V Power Source for JP9
JP9 VCC can be supplied from any of the following. All options require the 5V source's ground to be part of the common ground bus:
- USB 5V tap — a USB cable or hub port provides clean regulated 5V, shares USB ground automatically
- 5V from the same supply powering the PC — a 5V header or USB output from the PC's power distribution
- Separate 5V regulated supply — tie its GND to the common bus
The 5V does not need to come from the PAC-Drive itself — the PAC-Drive's screw terminals expose output channels and GND, but those output lines are open-drain (not a 5V source output). Use any 5V supply that shares the common ground.
Bench Testing the Driver Board
Use this procedure to verify each MOSFET channel before connecting any real solenoids. You need: a 5V supply, a multimeter, a 470 Ω resistor, and an LED.
-ultimateio. Trigger the corresponding game event (e.g., press the coin switch to start a game and look for the out-hole kicker to fire). Observe the Setting Solenoid HwID: 1 to value X printf output in the console to confirm software is sending the command.PAC-Drive Signal Wiring — JP1–JP4
Run individual signal wires from the PAC-Drive's output connector to the 3-pin headers on the driver board. The table below shows the complete path from each header pin through to the solenoid output terminal. The Solenoid Assignment Table cross-references these hardware outputs to the software output numbers and game mechanisms.
| Input connector | Pin | PAC-Drive output | Inverter input | MOSFET | Output terminal |
|---|---|---|---|---|---|
| JP1 | 1 | 1 | U1 – 1A | Q1 | JP5 pin 1 |
| 2 | 2 | U1 – 2A | Q2 | JP5 pin 2 | |
| 3 | 3 | U1 – 3A | Q3 | JP5 pin 3 | |
| JP2 | 1 | 4 | U1 – 4A | Q4 | JP6 pin 1 |
| 2 | 5 | U1 – 5A | Q5 | JP6 pin 2 | |
| 3 | 6 | U1 – 6A | Q6 | JP6 pin 3 | |
| JP3 | 1 | 7 | U2 – 1A | Q7 | JP7 pin 1 |
| 2 | 8 | U2 – 2A | Q8 | JP7 pin 2 | |
| 3 | 9 | U2 – 3A | Q9 | JP7 pin 3 | |
| JP4 | 1 | 10 | U2 – 4A | Q10 | JP8 pin 1 |
| 2 | 11 | U2 – 5A | Q11 | JP8 pin 2 | |
| 3 | 12 | U2 – 6A | Q12 | JP8 pin 3 |
08 pacdrive32.dll API
The DLL is Ultimarc's closed-source Windows library. It is loaded dynamically at startup using LoadLibrary(), and seven function pointers are resolved by name (pacdrive.h):
// pacdrive.h — function pointer typedefs
typedef int (WINAPI *PACINITIALIZE) (void);
typedef void (WINAPI *PACSHUTDOWN) (void);
typedef int (WINAPI *PAC64SETLEDSTATES) (int id, int group, char data);
typedef int (WINAPI *PAC64SETLEDSTATE) (int id, int group, int port, int state);
typedef int (WINAPI *PACSETLEDSTATES) (int id, short data);
typedef int (WINAPI *PAC64SETLEDFADETIME) (int id, int fadeTime);
typedef int (WINAPI *PAC64SETLEDINTENSITIES) (int id, unsigned char *data);
| Function | Parameters | Used for |
|---|---|---|
| PacInitialize() | none | Enumerate all connected Ultimarc USB devices. Returns 0 on failure. |
| PacShutdown() | none | Clean shutdown. Called on exit (via atexit). |
| Pac64SetLEDStates(id, group, data) | id=0, group 1–12, data 0–255 |
Set one group of 8 lamp LEDs on the Ultimate I/O. Each bit = one LED. |
| Pac64SetLEDState(id, group, port, state) | id=0, group, port 0–7, state 0/1 |
Set a single LED on/off — state is a boolean, not a brightness. Not used in the main loop. |
| Pac64SetLEDIntensities(id, data) | id=0, data = 96 bytes, 0–255 each |
The lamp path. Set all 96 LED brightnesses in one bulk transfer. Drives the incandescent fade — see §22. |
| Pac64SetLEDFadeTime(id, fadeTime) | id=0, fadeTime |
The board's own hardware fade (legacy "Approach A"). Left off — the software fade is finer. |
| PacSetLEDStates(id, data) | id=1, data 0–65535 |
Set all 16 solenoid outputs on the PAC-Drive in one call. Each bit = one channel. |
Device IDs are positional: hw_id_ultimateio = 0 (first device), hw_id_pacdrive = 1 (second device). If you add a second PAC-Drive board, it would be id=2.
09 Output Array Layout
All output state is held in a single flat array Outputs[112] (1 byte per output, value 0 or 1). UpdateOutputs() reads this array and issues USB commands to the appropriate board.
| Array range | Count | Destination board | Physical meaning |
|---|---|---|---|
| Outputs[0..95] | 96 | I-PAC Ultimate I/O | Lamp matrix — 12 groups × 8 bits. Groups 1–6 = Phase A (Strobe 1), Groups 7–12 = Phase B (Strobe 2) |
| Outputs[96..111] | 16 | PAC-Drive | Solenoid outputs — flippers, saucers, drop targets, out hole. Only 9 currently assigned. |
10 Lamp Matrix Flow
Physical hardware
Baby Pac-Man uses 30 SCR (Silicon Controlled Rectifier) drivers labeled Q01–Q30 on the A3 Driver Board (Q10 and Q21 are unpopulated). Each SCR fires on one of two AC phases:
- Phase A — positive half of the AC cycle (Strobe 1 in the emulator)
- Phase B — negative half of the AC cycle (Strobe 2 in the emulator)
This gives 60 possible lamp positions (30 SCRs × 2 phases), of which 56 are wired. The remaining 4 positions (Q10A, Q10B, Q21A, Q21B) are available for custom additions.
Emulator representation
Accumulates into coreGlobals.tmpLampMatrix[]
8 bytes = 64 bits (lampMatrix[0..3] = Phase A, [4..7] = Phase B)
Each byte → 8 individual Outputs[] entries
One call per group that changed
// core.c: update_hW() — lamp section
int outputNum = 1;
for (int i = 0; i < 8; i++) {
update_hw_byte(outputNum, coreGlobals.lampMatrix[i]);
outputNum += 8; // advances 8 outputs per byte
}
// update_hw_byte() — unpacks one byte into 8 output slots
void update_hw_byte(int outputStart, unsigned char value) {
PacDriveSetOutput(outputStart + 0, value >> 0 & 0x01);
// ... through ...
PacDriveSetOutput(outputStart + 7, value >> 7 & 0x01);
}
11 Solenoid Flow
From ROM to coil
Bits 0–2: select one of 7 momentary solenoids (pulsed when p1_cb2=0)
Bits 4–7 (inverted): continuous solenoids 8–11 (flipper relay, etc.)
Packs solenoids, solenoids2 (flipper), and pulsed states into one 64-bit word
12 Switch & Input Mapping
There are two independent layers. Getting a switch into the game requires both to be configured correctly.
Layer 1 — Physical pin → key (WinIPAC V2)
Each of the I-PAC's 48 physical input pins is assigned a keyboard key or gamepad button using the WinIPAC V2 utility. This is stored in the I-PAC's own flash memory. PinMAME never sees the physical wiring — it only sees keystrokes.
Layer 2 — Key → switch matrix position (PinMAME)
PinMAME maps each key to a position in coreGlobals.swMatrix[], which the emulated 6800 CPU reads when it scans the switch matrix. There are two sub-layers:
a) Hardcoded defaults — byvidpin.h:53
These are compiled in and apply on a fresh install. They cover the main cabinet controls:
| Default Key | Function | swMatrix destination | Port bit |
|---|---|---|---|
| Left Arrow | Joystick Left | swMatrix[0] bit 5 | 0x0020 |
| Right Arrow | Joystick Right | swMatrix[0] bit 4 | 0x0010 |
| Up Arrow | Joystick Up | swMatrix[0] bit 7 | 0x0080 |
| Down Arrow | Joystick Down | swMatrix[0] bit 6 | 0x0040 |
| Left Shift | Left Flipper | solenoids2 (not swMatrix) | CORE_LLFLIPKEY |
| Right Shift | Right Flipper | core_setSw(1, …) | CORE_LRFLIPKEY |
| 1 | Start Player 1 | swMatrix[1] bit 5 | IPT_START1 |
| 2 | Start Player 2 | swMatrix[1] bit 2 | IPT_START2 |
| 5 | Coin 1 | swMatrix[2] bits 0–1 | IPT_COIN1 |
| 6 | Coin 2 | swMatrix[2] bits 0–1 | IPT_COIN2 |
| Delete | Ball Tilt | swMatrix[2] bit 6 | 0x1000 |
| Home | Slam Tilt | swMatrix[2] bit 7 | 0x2000 |
| 7 | Self Test | swMatrix[0] bit 0 | 0x0001 |
| 8 | Video Diagnostic | swMatrix[0] bit 3 | 0x0008 |
| 9 | CPU Diagnostic | swMatrix[0] bit 1 | 0x0002 |
| 0 | Sound Diagnostic | swMatrix[0] bit 2 | 0x0004 |
b) User-configured mappings — cfg/babypac.cfg
All playfield switches (drop targets, saucers, rollovers, out hole, tilt bob, etc.) are configured through PinMAME's in-game Tab menu → "Input (this game)". Those assignments are stored in the binary cfg/babypac.cfg file. The file format is MAME's proprietary MAMECFG binary format and isn't human-readable, but the Tab menu provides a full visual editor.
13 Flipper Wiring & Signal Path
Flippers have a special path that bypasses the normal switch matrix and directly populates coreGlobals.solenoids2.
Signal flow
Game definition: FLIP_SWNO(0, 1)
From byvidgames.c:16: INITGAMEVP(babypac, ..., FLIP_SWNO(0,1), ...)
The macro FLIP_SWNO(l, r) encodes which playfield switch numbers the game ROM reads for flipper feedback:
- Left flipper (l=0): Switch number 0 = no playfield switch. The solenoid fires via
solenoids2only — the ROM does not get switch feedback for the left flipper. - Right flipper (r=1): Switch number 1.
core_setSw(1, ...)is called when Right Shift is pressed, so the ROM's switch scan does see the right flipper.
14 Full Lamp Reference
All 56 active lamps, verified against the official Bally A3 Driver Board schematics and lamp chart. This is the Version 2 mapping — see the corrections section below for errors found in the original mapping document.
Architecture
The lamp matrix uses 30 SCR (Silicon Controlled Rectifier) drivers labeled Q01–Q30 on the A3 Driver Board. Q10 and Q21 are unpopulated. Each SCR controls two lamps via AC phase multiplexing:
Phase A — Strobe 1
Fires during the positive half of the AC waveform. Maps to lampMatrix[0..3] in the emulator.
Phase B — Strobe 2
Fires during the negative half of the AC waveform. Maps to lampMatrix[4..7] in the emulator.
30 SCRs × 2 phases = 60 possible positions. 56 are wired; 4 positions (Q10A, Q10B, Q21A, Q21B) are unused and available for custom modifications.
Complete Mapping by SCR Position
This is the primary wiring reference — use this when tracing wires from the A3 board to the playfield.
| SCR | Phase A — Strobe 1 (positive half-cycle) | Phase B — Strobe 2 (negative half-cycle) |
|---|---|---|
| Q01 | Fruits "R" | Fruits "I" |
| Q02 | Tunnel "U" | Tunnel "N" (Second) |
| Q03 | Fruits "S" | Inside Lane Left |
| Q04 | Spinner Arrow Left | Tunnel "L" |
| Q05 | Row #1 "M" | Row #1 "A" (First) |
| Q06 | Saucer Arrow Left | Row #1 "N" |
| Q07 | Row #2 "A" (First) | Row #2 "M" |
| Q08 | Outhole Left Side Flasher | Tunnel Outlane |
| Q09 | Arrow 4K | Arrow 8K |
| Q10 | UNUSED | UNUSED |
| Q11 | Row #3 "M" | Row #3 "A" (First) |
| Q12 | Fruit Outlane | Row #4 "N" |
| Q13 | Saucer Arrow Right | Row #2 "N" |
| Q14 | Shoot Again | Row #3 "N" |
| Q15 | Tunnel "T" | Tunnel "N" (First) |
| Q16 | Energizer #1 | Row #1 "A" (Second) |
| Q17 | Arrow 2K | Arrow 6K |
| Q18 | Row #4 "M" | Row #4 "A" (First) |
| Q19 | Fruits "T" | Inside Lane Right |
| Q20 | Row #2 "P" | Row #2 "C" |
| Q21 | UNUSED | UNUSED |
| Q22 | Fruits "F" | Fruits "U" |
| Q23 | Tunnel "E" | Spinner Arrow Right |
| Q24 | Energizer #2 | Row #2 "A" (Second) |
| Q25 | Row #3 "A" (Second) | Energizer #3 |
| Q26 | Row #4 "C" | Row #4 "P" |
| Q27 | Row #1 "P" | Row #1 "C" |
| Q28 | Extra Ball Arrow | Outhole Right Side Flasher |
| Q29 | Row #3 "C" | Row #3 "P" |
| Q30 | Energizer #4 | Row #4 "A" (Second) |
I-PAC Ultimate I/O Pin Assignments
Physical connector pin → lamp mapping for the I-PAC Ultimate I/O board, as wired on this build. Strobe 1 (pins 1–32) and Strobe 2 (pins 33–65) are the two output connector groups.
lampadr=15, which is filtered out by an if (lampadr != 0x0f) guard in byVP_lampStrobe(). Pin 65 falls outside the 8-iteration lamp loop. These outputs are listed as DEAD in the table. The real lamps that appear to be at those pin positions actually fire at the correct pins shown in the "Organized by Function" section.
| Strobe 1 — Pins 1–32 | Strobe 2 — Pins 33–65 | |||
|---|---|---|---|---|
| 01 | Fruits "U" | 33 | Fruits "F" | |
| 02 | Right Inside Lane | 34 | Fruits "T" | |
| 03 | Tunnel "N" (1st) | 35 | Tunnel "T" | |
| 04 | Right Spinner Arrow | 36 | Tunnel "E" | |
| 05 | Row #1 "C" | 37 | Row #1 "P" | |
| 06 | Energizer #1 | 38 | Row #1 "A" (2nd) | |
| 07 | Row #2 "C" | 39 | Row #2 "P" | |
| 08 | Energizer #2 | 40 | Row #2 "A" (2nd) | |
| 09 | Row #3 "C" | 41 | Row #3 "P" | |
| 10 | Energizer #3 | 42 | Row #3 "A" (2nd) | |
| 11 | Row #4 "C" | 43 | Row #4 "P" | |
| 12 | Energizer #4 | 44 | Row #4 "A" (2nd) | |
| 13 | Arrow 6K | 45 | Arrow 2K | |
| 14 | Outhole Right Side Flasher | 46 | Extra Ball Arrow | |
| 15 | unused — no lamp (confirmed dark) | 47 | unused — no lamp (confirmed dark) | |
| 16 | DEAD — lampadr=15, never driven | 48 | DEAD — lampadr=15, never driven | |
| 17 | Fruits "I" | 49 | Fruits "R" | |
| 18 | Left Inside Lane | 50 | Fruits "S" | |
| 19 | Tunnel "N" (2nd) | 51 | Tunnel "U" | |
| 20 | Left Spinner Arrow | 52 | Tunnel "L" | |
| 21 | Row #1 "M" | 53 | Row #1 "A" (1st) | |
| 22 | Left Saucer Arrow | 54 | Row #1 "N" | |
| 23 | Row #2 "M" | 55 | Row #2 "A" (1st) | |
| 24 | Right Saucer Arrow | 56 | Row #2 "N" | |
| 25 | Row #3 "M" | 57 | Row #3 "A" (1st) | |
| 26 | Shoot Again | 58 | Row #3 "N" | |
| 27 | Row #4 "M" | 59 | Row #4 "A" (1st) | |
| 28 | Fruit Outlane | 60 | Row #4 "N" | |
| 29 | Arrow 8K | 61 | Arrow 4K | |
| 30 | Outhole Left Side Flasher | 62 | Tunnel Outlane | |
| 31 | unused — no lamp (confirmed dark) | 63 | unused — no lamp (confirmed dark) | |
| 32 | DEAD — lampadr=15, never driven | 64 | DEAD — lampadr=15, never driven | |
| — | (no pair) | 65 | DEAD — beyond 8-iteration lamp loop | |
Organized by Function
Same data, grouped by lamp purpose — useful for understanding game logic and testing lamp groups.
Energizers (4)
FRUITS Letters (6)
TUNNEL Letters (6)
PAC-MAN Row 1 (6)
PAC-MAN Row 2 (6)
PAC-MAN Row 3 (6)
PAC-MAN Row 4 (6)
Scoring Arrows (8)
Special Indicators (5)
Flashers (3)
Unused (4 positions)
Technical Hardware Details
A3 Driver Board
- BoardA3 Driver Board
- Transistor2N5060 (SCR)
- Phase controlAC zero-crossing, 120 Hz
- Total SCRs30 (Q01–Q30)
- Active lamps56 of 60 positions
Connector Assignments
- A3J1SCRs Q01–Q18 (odd pins)
- A3J2SCRs Q08–Q28 (various)
- A3J3SCRs Q15–Q30 (various)
- Wire colorsRed, Blu, Grn, Yel, Orn, Brn, Gry, Blk, Wht
Wiring Notes
- Each lamp connects to one SCR driver through the phase control circuit
- Phase A lamps light during the positive half of the AC cycle
- Phase B lamps light during the negative half of the AC cycle
- The PinMAME emulator maps Phase A → Strobe 1 (
lampMatrix[0..3]) and Phase B → Strobe 2 (lampMatrix[4..7]) - Unused positions Q10 and Q21 (both phases) are available for custom lamp additions
- Enable hardware output with the
-ultimateioflag — 96 LED outputs cover all 30 SCRs × 2 phases
Corrections from the Original Mapping Document
The original BabyPacman-LampMapping.txt (v1) contained several errors, corrected in v2 against the official Bally schematics:
BabyPacman-LampMapping-v2.txt's "Complete Mapping by SCR Position" table has Q27 Phase A and Phase B reversed. The correct assignment (confirmed by I-PAC pin data) is: Q27 Phase A = Row #1 "P", Q27 Phase B = Row #1 "C". The "Organized by Function" section in v2 is correct. The SCR table on this page has been corrected.
- Scoring arrows: 6K = pin 13, 8K = pin 29, 2K = pin 45, 4K = pin 61
- Extra Ball Arrow = pin 46, Shoot Again = pin 26
- Outhole side flashers = pins 14 & 30 (both fire together)
- Structurally dead (lampadr = 15, never driven): pins 16, 32, 48, 64; pin 65 is beyond the 8-iteration lamp loop
- Drivable but unwired — no lamp present: pins 15, 31, 47, 63
Verification Status
This mapping is authoritative and suitable for production hardware builds.
15 Solenoid Assignment Table
Software assignments defined in src/wpc/core.c:1733–1747 inside update_hW(). The PAC-Drive output number and driver board columns show the physical hardware path — see Custom Driver PCB for full wiring details.
| Output # | Outputs[] index | PAC-Drive bit | PAC-Drive output | Solenoid / Mechanism | allSol bit | Driver board output |
|---|---|---|---|---|---|---|
| 97 | Outputs[96] | bit 0 | output 1 | Left Flipper | bit 46 | JP5 pin 1 |
| 98 | Outputs[97] | bit 1 | output 2 | Right Flipper | bit 44 | JP5 pin 2 |
| 99 | Outputs[98] | bit 2 | output 3 | Out Hole Kicker | bit 0 | JP5 pin 3 |
| 100 | Outputs[99] | bit 3 | output 4 | Left Maze Saucer | bit 8 | JP6 pin 1 |
| 101 | Outputs[100] | bit 4 | output 5 | Right Maze Saucer | bit 9 | JP6 pin 2 |
| 102 | Outputs[101] | bit 5 | output 6 | Drop Target Reset | bit 1 | JP6 pin 3 |
| 103 | Outputs[102] | bit 6 | output 7 | Drop Target #1 (Left) | bit 2 | JP7 pin 1 |
| 104 | Outputs[103] | bit 7 | output 8 | Drop Target #3 (Center) | bit 4 | JP7 pin 2 |
| 105 | Outputs[104] | bit 8 | output 9 | Drop Target #5 (Right) | bit 6 | JP7 pin 3 |
| 106–112 | Outputs[105..111] | bits 9–15 | outputs 10–16 | Unused — available for expansion | — | JP8 pins 1–3 + unrouted |
16 Switch Matrix Quick Reference
Column 0 (swMatrix[0]) is the "cabinet" column — special switches handled directly by the SWITCH_UPDATE function. Columns 1+ are the playfield matrix scanned by the CPU.
| Constant | Value | swMatrix col | Function | Default key |
|---|---|---|---|---|
| BYVP_SWSELFTEST | -7 | [0] bit 0 | Self Test | 7 |
| BYVP_SWCPUDIAG | -6 | [0] bit 1 | CPU Diagnostic (triggers NMI) | 9 |
| BYVP_SWSOUNDDIAG | -5 | [0] bit 2 | Sound Diagnostic | 0 |
| BYVP_SWVIDEODIAG | -4 | [0] bit 3 | Video Diagnostic (triggers Video NMI) | 8 |
| BYVP_SWJOYRIGHT | -3 | [0] bit 4 | Joystick Right | → |
| BYVP_SWJOYLEFT | -2 | [0] bit 5 | Joystick Left | ← |
| BYVP_SWJOYDOWN | -1 | [0] bit 6 | Joystick Down | ↓ |
| BYVP_SWJOYUP | 0 | [0] bit 7 | Joystick Up | ↑ |
| — | sw #1 | [1] bit 0 | Right Flipper switch feedback | Right Shift |
| IPT_START2 | bit 0x0100 | [1] bit 2 | Start Player 2 | 2 |
| IPT_START1 | bit 0x0200 | [1] bit 5 | Start Player 1 | 1 |
| IPT_COIN2 | bit 0x0400 | [2] bit 0 | Coin 2 | 6 |
| IPT_COIN1 | bit 0x0800 | [2] bit 1 | Coin 1 | 5 |
| Ball Tilt | bit 0x1000 | [2] bit 6 | Ball Tilt | Del |
| Slam Tilt | bit 0x2000 | [2] bit 7 | Slam Tilt | Home |
17 How to Modify Assignments
| What you want to change | Where to change it | Requires recompile? |
|---|---|---|
| Which physical I-PAC pin sends which key/button | WinIPAC V2 utility (programs I-PAC flash memory) | No |
| Which key activates which playfield switch in PinMAME | Press Tab in PinMAME → "Input (this game)". Saved to cfg/babypac.cfg. |
No |
| Default keys for joystick / flipper / start / coin / tilt | Edit BYVP_babypac_PORT in src/wpc/byvidpin.h |
Yes |
| Which solenoid fires on which allSol bit | Edit PacDriveSetOutput() calls in update_hW() in src/wpc/core.c:1733 |
Yes |
| Add a new solenoid to an unused PAC-Drive channel | Add a PacDriveSetOutput(106..112, ...) line to update_hW() |
Yes |
| Enable debug logging of lamp/solenoid USB commands | Uncomment the printf lines at pacdrive.c:156 and pacdrive.c:188 |
Yes |
| Game DIP switch settings (lives, free play, tunnels, etc.) | Press Tab in PinMAME → "DIP Switches". Or edit cfg/babypac.cfg via Tab menu. |
No |
| Launch flags (volume, display mode, etc.) | Edit babypac.bat |
No |
pinmame.exe in the project root. All modified source files are under MinGW/PinMAME/src/.
18 Build & Wiring Guide
This section describes how to assemble the complete hardware system from scratch. It is intended for others who want to replicate this build on a real Baby Pac-Man cabinet.
Parts Required
| Item | Notes |
|---|---|
| Baby Pac-Man cabinet or playfield | Original Bally 1982. Needs flippers, drop targets, saucers, out hole, original A3 driver board for lamp SCRs. |
| Windows PC | Runs PinMAME. Any modern PC works. Must have USB ports for both Ultimarc boards. Used for video output and audio. |
| I-PAC Ultimate I/O | Ultimarc. VID 0xD209, PID 0x0410. Handles all 96 lamp outputs and all switch inputs. No Windows driver needed — standard USB HID. |
| PAC-Drive "ID #1 Special" | Ultimarc. VID 0xD209, PID 0x1500. The "ID #1 Special" variant is required — do NOT substitute the standard version. Outputs default OFF at power-up. |
| BabyPacDriver-v4 custom PCB | Eagle design files in Solenoid Driver board schematics/. Order from any PCB fab. Requires IRLZ34N MOSFETs (×12), SN74HCT04N hex inverters (×2), resistor networks (RN1–RN4), and 0.1 µF bypass caps (C1, C2). |
| DC solenoid supply | Typically 24–48V DC at adequate current (depends on coil ratings). A Meanwell or similar enclosed supply works well. Must share common ground with USB boards. |
| 5V supply for driver board logic | Can be a USB 5V tap, USB hub port, or any regulated 5V source. Must share common ground with all other supplies. |
| pacdrive32.dll | Closed-source Windows DLL from Ultimarc. Place in same directory as pinmame.exe. Available from the Ultimarc website. |
| MinGW toolchain | Required to rebuild PinMAME from source. Run ..\bin\mingw32-make from the MinGW/PinMAME/src/ directory. |
Common Ground — Critical Wiring Requirement
All grounds in the system must be tied to a single common ground bus. Without this, solenoids may not fire, signals will be unreliable, and you risk damaging the hardware.
- Meanwell power supply negative (−) terminal
- PAC-Drive board GND screw terminal
- I-PAC Ultimate I/O GND (shared via USB; ensure USB cable shield is grounded)
- Custom driver board JP9 pins 1 & 2
- 5V logic supply GND (if separate from USB)
Step-by-Step Wiring Sequence
babypac.bat. Both must be present at startup or neither board will receive commands.Flipper Coil Requirements
Because the PAC-Drive is strictly on/off (no PWM), flipper coils must be the original-style dual-winding type with a mechanical End-of-Stroke (EOS) switch:
- The coil has a power winding (low resistance, high current) and a hold winding (high resistance, low current)
- The EOS switch is wired in series with the power winding. When the flipper reaches full stroke, the EOS switch opens, removing the power winding from the circuit
- Only the hold winding remains energized — at lower current that won't overheat the coil
- This is the design Bally used originally. No software changes are needed
19 Troubleshooting
Lamps and solenoids are both completely dead
PacInitialize() may return 0, LoadPacDrive() fails, and no USB commands are sent to either board for the entire session. Quit PinMAME, connect both boards, and relaunch.
Flippers don't fire
- Open WinIPAC V2 and confirm the I-PAC input terminals for the left and right flipper buttons are assigned to Left Shift and Right Shift respectively. This is the most common cause — if the keys are unmapped or mapped to something else, PinMAME never sees the button press and the PAC-Drive outputs never fire.
- Verify the flipper coil power windings are connected to PAC-Drive terminals 1 (left) and 2 (right) via the driver board. In this build, flippers are driven through the PAC-Drive — not directly wired as in the original Bally machine.
- Note: the original Baby Pac-Man A3 driver board used a direct-wired circuit (button → K1 relay → coil power winding) with no MPU solenoid driver in the flipper path. This build replaces that with PAC-Drive outputs 97/98. If you wire the coils directly instead, outputs 97/98 become irrelevant for coil firing but the I-PAC key mapping is still needed for game logic.
Lamps work but solenoids don't fire
- Check that PAC-Drive terminal 1 is wired to driver board JP1 pin 1 (and so on for all channels)
- Verify common ground — Meanwell GND tied to PAC-Drive GND and driver board JP9 GND
- Enable the solenoid debug printf by uncommenting it in
pacdrive.c:188. If you seeSetting Solenoid HwID: 1 to value Xin the console, the software side is working and the problem is hardware - Run the driver board bench test (Stage 4: LED dummy load) to isolate whether a specific MOSFET channel is switching
- Verify
hw_id_pacdrive = 1inpacdrive.c. If it's 0, solenoid commands are being sent to the I-PAC instead of the PAC-Drive
Solenoids fire but lamps don't work
- Enable lamp debug printf in
pacdrive.c:156. You should seeSetting LED HwID: 0 Group: X to value Ychanging on each frame - Verify
hw_id_ultimateio = 0inpacdrive.c - Check that
-ultimateioflag is present inbabypac.bat
PacInitialize() returns 0
- Both boards missing or not enumerated yet — ensure both are physically connected
pacdrive32.dllmissing or wrong version — must be in the same directory aspinmame.exe- Wrong PAC-Drive version — must be "ID #1 Special", not the standard version
A specific solenoid fires on the wrong game event
The solenoid-to-bit mapping is defined in src/wpc/core.c:1733 inside update_hW(). Each PacDriveSetOutput(N, ...) call maps a specific bit of allSol to a PAC-Drive channel. See the Solenoid Assignment Table for the full mapping. Edit the relevant line in core.c and rebuild.
A lamp pin appears dead (always 0)
If a pin reads 0 no matter what the game does, check whether it is one of the five structurally dead pins: 16, 32, 48, 64, or 65. These are permanently 0 due to code-level filtering — they cannot be activated without modifying byVP_lampStrobe() and update_hW() in core.c. See the pin table in Full Lamp Reference for details on which real lamps those numbers were mistakenly associated with.
Enabling debug output
Two printf statements in pacdrive.c are commented out in normal builds to avoid console spam. Uncomment them to trace hardware commands in real time:
| Location | Output when enabled |
|---|---|
| pacdrive.c:158 | Prints every I-PAC LED group update: Setting LED HwID: 0 Group: N to value V |
| pacdrive.c:190 | Prints every solenoid state change: Setting Solenoid HwID: 1 to value V |
Rebuild after uncommenting. Commands only print when the state actually changes, so a solenoid that fires and releases will generate exactly two lines.
20 K1 Flipper Enable Relay
What K1 is
The original Baby Pac-Man machine has a relay on the A3 driver board called K1 — Flipper Enable. In the Bally self-test numbering it is solenoid #08 (continuous solenoid). The relay coil is powered from a dedicated supply rail labeled "FLIPPER RELAY +43" (J3 pin 8 on power supply schematic W-1284), distinct from the general solenoid bus at J3 pin 12 ("SOLENOID BUS +43"). The relay's normally-open contacts are wired in series with the +43V line feeding both flipper coils. When the ROM de-energizes K1, +43V is cut to the flippers even if the flipper buttons are held — this is how the machine disables the flippers during the video-game half of the game and during attract mode.
How K1 maps to software
In byvidpin.c, PIA 1 port B carries the continuous solenoids (K1–K4) as bits 6–9 of coreGlobals.solenoids. K1 lands at allSol bit 7. In core.c, there is already commented-out code at line 1740 that previously attempted to route K1 to PAC-Drive output 100:
// PacDriveSetOutput(100, allSol >> 7 & 0x01); // Flipper enable relayOutput 100 was later assigned to Left Maze Saucer. The K1 line has since been moved to output 106 (PAC-Drive terminal 10) and is active in this build.
The problem in this build
Software side implemented (2026-07): core.c now drives PacDriveSetOutput(106, allSol >> 7 & 0x01), so PAC-Drive terminal 10 tracks the K1 enable signal every VBLANK. The physical relay is not yet wired — until it is, that signal has no effect and the flippers still fire on any button press regardless of game phase (they are driven directly by PacDriveSetOutput(97/98, ...) from button state, bypassing the ROM gate). Wire the relay per Option 1 below to make K1 physically cut flipper power during the video-game half and attract mode.
Options for controlling flipper enable
Option 1 — Hardware K1 relay (full original behavior)
Wire a real relay (or the original K1 relay if salvaged) to PAC-Drive terminal 10, with its normally-open contacts in series with the +43V supply to both flipper coils. Then enable the software output:
Step 1 — Drive the K1 output in src/wpc/core.c — already done in this build (in update_hW(), right after the flipper outputs):
PacDriveSetOutput(106, allSol >> 7 & 0x01); // K1 Flipper Enable RelayStep 2 — Hardware wiring:
- PAC-Drive terminal 10 → driver board JP4 pin 1 (signal input)
- Driver board JP4 output terminal → relay coil (+)
- Relay coil (−) → common ground
- Relay normally-open contact 1 → FLIPPER RELAY +43V supply
- Relay normally-open contact 2 → both flipper coil high-power winding supply wires
- Add a flyback diode across the relay coil (cathode to +, anode to −)
With this wired, when K1 is energized (ROM playing pinball phase), +43V reaches the flippers. When K1 is de-energized (video game or attract), the relay opens and no amount of button pressing will fire the coils.
Note on relay selection: The relay coil must be compatible with the driver board's MOSFET output (12V or 24V coil recommended, depending on your supply). The original Bally K1 relay ran on +43V — if you use the original relay, feed the FLIPPER RELAY +43V rail to the coil side as well, not through the MOSFET output. In that case wire the MOSFET output to a low-voltage relay that in turn switches the +43V to the coil.
Option 2 — Software gate (no extra hardware)
Keep K1 unwired, but add a software check in core.c that suppresses flipper output when K1 is not active. Replace lines 1733–1734 in update_hW():
// Before (current code): PacDriveSetOutput(97, allSol >> 46 & 0x01); // left flipper PacDriveSetOutput(98, allSol >> 44 & 0x01); // right flipper // After (with software gate): if (allSol >> 7 & 0x01) { PacDriveSetOutput(97, allSol >> 46 & 0x01); PacDriveSetOutput(98, allSol >> 44 & 0x01); } else { PacDriveSetOutput(97, 0); PacDriveSetOutput(98, 0); }
This respects the ROM's K1 enable signal in software without requiring any additional hardware. The coils simply do not receive drive current when the ROM has K1 off. Rebuild and test after this change.
Option 3 — Live with it (current approach)
Do nothing. K1 remains unwired, the software output remains commented out, and the flippers fire freely whenever the buttons are pressed. This is the current state of the build as of 2026-05-29. Because the ball is always in the out-hole during the video-game segment, this has no practical effect on gameplay.
core.c:1740. To pursue Option 1 or 2, revisit this section and follow the steps above.
21 Flipper Power PWM Adapter (optional / experimental — not used in the final build)
tools/flipper_pwm/ and this section are kept for reference. If you do build it, the flipper channels must be spliced through the Arduino; to revert, restore the direct HCT04→gate connection.
The PAC-Drive and the custom driver board are on/off only — there is no PWM, so flipper strength cannot be tuned in software. With the dual-winding EOS coils correctly wired the flippers are safe, but they can still feel too strong because the power winding fires at full 43 V during the stroke. This optional add-on inserts a small microcontroller between the driver board's logic output and the flipper MOSFET gates to PWM the gate drive, giving an adjustable fire strength via a single potentiometer — with no changes to PinMAME or the driver board's design.
How it works (and why it suits the EOS coil)
The AQ-25-500/34-4500 coil shares the MOSFET drain between both windings: the power winding runs through the EOS switch, the hold winding connects directly. The adapter exploits this:
- During the stroke (EOS closed) the power winding dominates — the adapter PWMs the gate at a pot-set duty, softening the snap.
- At end of stroke EOS opens and only the hold winding remains — the adapter switches the gate to 100 % solid-on for a firm, silent hold (no PWM whine, and the hold winding is rated continuous).
So the pot tunes fire strength while the hold stays firm. A naïve "PWM everything" approach would weaken the hold winding and make it droop and buzz — this timed fire-then-hold avoids that.
Controller board
This build uses a USB-C "Nano" board built around the ATmega328P (chip marked ATMEL MEGA328P U-KR) — a classic 328P, programmed over an onboard USB-serial bridge (no native USB). The PF1/PE0 silkscreen pins are 32U4 port names left over on this clone layout; they are vestigial and unused here. The functional pins (D2, D3, D9, D10, A0, +5V, GND) are all standard 328P pins.
stk500 "not in sync" errors, because the IDE tries 115200 baud while this clone's bootloader runs at 57600). Uploads go through the board's USB-serial chip (CH340/CP210x) — install that driver on the uploading computer if needed. (A genuine Pro Micro instead uses "Arduino Leonardo" — see the Pro Micro sketch.)
Parts required
| Item | Notes |
|---|---|
| ATmega328P "Nano" board (or Pro Micro) | 5 V / 16 MHz. The photographed USB-C ATmega328P board (marked MEGA328P), or any Arduino Nano / Pro Micro / Leonardo. |
| 10 kΩ linear potentiometer | Sets fire strength for both flippers. Panel-mount so it can be adjusted without opening the board. |
| 2 × 10 kΩ resistor | Gate-to-GND pulldowns — mandatory. One on each flipper MOSFET gate. |
| 2 × 100–220 Ω resistor | Series gate resistors. The driver board likely already has one per channel — reuse it if so. |
Pin connections (this board's silkscreen labels)
| Board pin | Connect to | Purpose |
|---|---|---|
| D2 | Left flipper HCT04 output (channel 97) | Fire-command input, left |
| D3 | Right flipper HCT04 output (channel 98) | Fire-command input, right |
| D9 | Left flipper IRLZ34N gate | PWM gate drive, left (Timer1) |
| D10 | Right flipper IRLZ34N gate | PWM gate drive, right (Timer1) |
| A0 | Potentiometer wiper | Fire-strength set |
| +5V | Driver board 5 V (JP9 pin 3) | Logic power |
| GND | Common ground bus (JP9 pin 1/2) | Shared reference — required |
Potentiometer outer legs go to +5V and GND.
Where it splices in
Wiring sequence
Bring-up & test (coils safe first)
- Pull the 43 V flipper fuses (or disconnect the coils) so nothing can fire on first power-up.
- Load the sketch. With a meter on a gate vs GND: idle ≈ 0 V; hold a flipper button → a PWM average that rises with the pot, settling to ≈ 5 V after the ~80 ms fire window.
- If a flipper behaves inverted (fires when not pressed), set
ENABLE_ACTIVE_HIGH = falseand reflash. - Restore fuses/coils. Start with the pot low, fire-test, and turn up until the strength feels right. If a flipper struggles to complete its stroke at low settings, raise
FIRE_MINorFIRE_MSslightly.
tools/flipper_pwm/flipper_pwm_nano/flipper_pwm_nano.ino (this ATmega328P "Nano" board) and tools/flipper_pwm/flipper_pwm_promicro.ino (Pro Micro). The code is identical — only the header notes and the Arduino IDE board target differ. The coil's built-in flyback diodes double as the PWM freewheel path, so no extra diodes are needed.
22 Incandescent Lamp Fade
The original Baby Pac-Man playfield lit with incandescent bulbs — they glow up and cool down instead of snapping on and off. LEDs switch instantly, which reads as harsh. This build simulates the bulb behavior in software, on top of the Ultimate I/O's 256-level PWM, and every part of it is tunable from babypac.bat without recompiling.
babypac.bat lines. It runs the same integer math as the build.
How it works
Each lamp keeps a brightness value 0–255. Every 60 Hz frame, UpdateOutputs() in pacdrive.c eases that value toward the ROM's target (255 = on, 0 = off), then the whole 96-output board is pushed with a single Pac64SetLEDIntensities() bulk transfer. Because that bulk command needs ~20 ms before the next one, the push runs every other frame (~30 Hz) — still smooth, and the update cost is fixed no matter how many lamps are fading at once.
The three curve shapes
Fade-in and fade-out each pick an easing curve independently:
exp— fast off the mark, then tapers (ease-out). Snappy start, long lingering tail. The default.s— smoothstep: slow at both ends, fastest in the middle. Soft launch and soft landing.lin— constant speed the whole way. A straight ramp.
Perceptual gamma
LED PWM is linear in duty cycle, but the eye is not. A gamma curve (default 2.2) is applied to the output so the fade reads as smooth and even and the dim glow lingers naturally. Toward 1.0 gives the raw, punchier linear look; higher than 2.2 is punchier still with a longer low tail. Gamma is set independently for rise and fall.
Configuration — babypac.bat
All values are read once at launch and clamped to sane ranges. Speeds are 1–255 (higher = faster). The plain BABYPAC_FADE_CURVE / BABYPAC_FADE_GAMMA set both directions; the _RISE / _FALL variants override.
| Environment variable | Controls | Default |
|---|---|---|
| BABYPAC_FADE_RISE | Fade-in speed (1–255, higher = faster) | 32 |
| BABYPAC_FADE_FALL | Fade-out speed (1–255, higher = faster) | 24 |
| BABYPAC_FADE_CURVE_RISE | Fade-in curve: exp · s · lin | exp |
| BABYPAC_FADE_CURVE_FALL | Fade-out curve: exp · s · lin | exp |
| BABYPAC_FADE_GAMMA_RISE | Fade-in perceptual gamma (1.0 = off) | 2.2 |
| BABYPAC_FADE_GAMMA_FALL | Fade-out perceptual gamma (1.0 = off) | 2.2 |
| BABYPAC_FADE_CURVE | Shorthand — sets both rise & fall curve | — |
| BABYPAC_FADE_GAMMA | Shorthand — sets both rise & fall gamma | — |
| BABYPAC_LED_FADE | Board hardware fade (legacy "Approach A") | 0 (off) |
A "real bulb" starting point — a quick exponential warm-up in, a slow constant fade-out with a long perceptual tail:
rem babypac.bat
call setvol 80
set BABYPAC_FADE_CURVE_RISE=exp
set BABYPAC_FADE_CURVE_FALL=lin
set BABYPAC_FADE_GAMMA_RISE=1.6
set BABYPAC_FADE_GAMMA_FALL=2.6
set BABYPAC_FADE_RISE=40
set BABYPAC_FADE_FALL=18
pinmame babypac -ultimateio -joystick -dmd_only -scanlines -skip_gameinfo
On startup the console prints the active values — Lamp fade: RISE speed=… curve=… gamma=… | FALL speed=… curve=… gamma=… — so you can confirm what took effect.
23 Input HW → Switch Function Map
Running with -ultimateio, this build does not use the normal named switch inputs. Instead core.c reads 64 generic inputs — "Input HW1" … "Input HW64" — directly into the switch matrix. You rebind those "Input HW" entries in Input (general) — they override the named "Slam Tilt", "Coin", etc. entries in Input (this game), which is why editing those has no effect. This table says what each "Input HW" controls.
swMatrix column (N−1)÷8, bit (N−1) mod 8. Column 0 (HW1–8) is the cabinet column; playfield strobes ST0–ST5 are columns 1–6 → HW9–16, 17–24, 25–32, 33–40, 41–48, 49–56. HW57–64 are unused. Equivalently, for a playfield switch at strobe STc, return row r: Input HW = 9 + c×8 + r.
src/inptport.c with real Baby Pac-Man names — e.g. BabyPac Slam, BabyPac #1 Drop Tgt (Lt), BabyPac Rt Spinner — so they read clearly in Input (general) instead of "Input HW24" etc. (HW1–4, the on-board diagnostic switches, don't respond to a mapped key in this build and are left generic.)
Relabeled inputs (from PinMAME Tab Menu re-mappings.csv)
The 28 mapped inputs, and the exact label each one now carries in the Input (general) menu in this build (the "Menu label" column is what was applied to src/inptport.c):
| Input HW | Switch / function | Menu label (this build) |
|---|---|---|
| Input HW5 | Joystick Right | BabyPac Joy Right |
| Input HW6 | Joystick Left | BabyPac Joy Left |
| Input HW7 | Joystick Down | BabyPac Joy Down |
| Input HW8 | Joystick Up | BabyPac Joy Up |
| Input HW9 | Right flipper EOS | BabyPac Rt flip EOS |
| Input HW11 | Start Game for 2 | BabyPac Start 2 |
| Input HW13 | Rebounds (2) | BabyPac Rebounds (2) |
| Input HW14 | Start for Game 1 | BabyPac Start 1 |
| Input HW15 | Right Spinner | BabyPac Rt Spinner |
| Input HW16 | Left Spinner | BabyPac Lt Spinner |
| Input HW17 | Coin 2 (right) | BabyPac Coin 2 |
| Input HW18 | Coin 1 (Left) | BabyPac Coin 1 |
| Input HW23 | Tilt (2) | BabyPac Tilt (2) |
| Input HW24 | Slam | BabyPac Slam |
| Input HW25 | Top Loop Lane (Right) | BabyPac Top Loop (Rt) |
| Input HW28 | Top Loop Lane (Left) | BabyPac Top Loop (Lt) |
| Input HW29 | TUNNEL Outlane | BabyPac TUNNEL Outlane |
| Input HW30 | FRUITS Outlane | BabyPac FRUITS Outlane |
| Input HW31 | Right Inside Outlane | BabyPac Rt Inside Outlane |
| Input HW32 | Left Inside Outlane | BabyPac Lt Inside Outlane |
| Input HW33 | #5 Drop Target (Right) | BabyPac #5 Drop Tgt (Rt) |
| Input HW34 | #4 Drop Target | BabyPac #4 Drop Target |
| Input HW35 | #3 Drop Target (Center) | BabyPac #3 Drop Tgt (Ctr) |
| Input HW36 | #2 Drop Target | BabyPac #2 Drop Target |
| Input HW37 | #1 Drop Target (Left) | BabyPac #1 Drop Tgt (Lt) |
| Input HW38 | Outhole Saucer | BabyPac Outhole Saucer |
| Input HW39 | Right Maze Saucer | BabyPac Rt Saucer |
| Input HW40 | Left Maze Saucer | BabyPac Lt Saucer |
Cabinet & control switches (exact)
These are defined in src/wpc/byvidpin.h / byvidpin.c, so the mapping is definitive. The "source default key" is the compiled-in default; in this build the actual key comes from the I-PAC programming and cfg/babypac.cfg.
| Input HW | swMatrix | Function | Source default key |
|---|---|---|---|
| Input HW1 | [0] bit 0 | Self Test | 7 |
| Input HW2 | [0] bit 1 | CPU Diagnostic (NMI) | 9 |
| Input HW3 | [0] bit 2 | Sound Diagnostic | 0 |
| Input HW4 | [0] bit 3 | Video Diagnostic (Video NMI) | 8 |
| Input HW5 | [0] bit 4 | Joystick Right | → |
| Input HW6 | [0] bit 5 | Joystick Left | ← |
| Input HW7 | [0] bit 6 | Joystick Down | ↓ |
| Input HW8 | [0] bit 7 | Joystick Up | ↑ |
| Input HW9 | [1] bit 0 | Right Flipper switch (EOS feedback) | Right Shift |
| Input HW11 | [1] bit 2 | Start Player 2 | 2 |
| Input HW14 | [1] bit 5 | Start Player 1 | 1 |
| Input HW17 | [2] bit 0 | Coin 2 | 6 |
| Input HW18 | [2] bit 1 | Coin 1 | 5 |
| Input HW23 | [2] bit 6 | Ball Tilt | Del |
| Input HW24 | [2] bit 7 | Slam Tilt | Home |
Playfield switches
The playfield switches fill the strobe columns (roughly Input HW9–HW56), per the Baby Pac-Man switch matrix in the operator's manual (drawing W-1280). The complete set of named switches:
- Flippers: Left Flipper, Right Flipper (Right Flipper = Input HW9)
- Drop Targets: #1 (Left), #2, #3 (Center), #4, #5 (Right)
- Saucers: Left Maze Saucer, Right Maze Saucer, Outhole Saucer, Top-Left Saucer, Top-Right Saucer
- Spinners: Left Spinner, Right Spinner
- Top Center Loop: Left, Right
- Lanes / outlanes: Left Inside Outlane, Right Inside Outlane, Tunnel Outlane, Fruits Outlane
- Rebound: 10-Point Rebound
Input HW = 9 + strobe×8 + row (returns A4J2-8…A4J2-15 are rows 0…7). To change the physical switch's key, remember there are two layers: WinIPAC (switch → key) and this map (key → matrix).
Last updated: 2026-07-20