Behind the build: 2025's BFCM globe in a pinball machine ๐ŸŒŽ๐Ÿ•น๏ธ

Real-time merchant sales rendered on our globe, Easter eggs galore, and a pinball machine because... why not?

โ€ข
Shopify Engineering
โ€ข18 min readโ€ขintermediateโ€ข
--
โ€ขView Original

Overview

Shopify's 2025 Black Friday Cyber Monday (BFCM) live globe was reimagined as an interactive pinball machine running at 120fps in a browser. Built with Three.js and React Three Fiber, the project features real-time order visualization via server-sent events, 2D physics simulation using Rapier2D, custom shader-based lighting for 85+ lights, AR/VR support, and was displayed on the Las Vegas Exosphere for the third consecutive year.

What You'll Learn

1

How to optimize a 3D browser game by using 2D physics with raycasting for elevation simulation

2

How to implement dynamic lighting for dozens of lights using texture masks and shader-based light indexing

3

Why switching from 3D to 2D physics engines can dramatically improve performance on mobile devices

4

How to stream real-time data to browser visualizations using Server-Sent Events

5

How to design modular game systems that handle complex edge cases like multi-ball scenarios

Prerequisites & Requirements

  • Understanding of 3D rendering concepts and WebGL basics
  • Familiarity with Three.js and React Three Fiber (R3F)
  • Basic understanding of physics engines and collision detection
  • Understanding of GLSL shaders and GPU rendering pipelines(optional)
  • Experience with Blender for light baking and 3D asset creation(optional)

Key Questions Answered

How can you simulate 3D pinball ramps using a 2D physics engine?
By creating a system of sensors that activate and deactivate colliders as the ball moves. When the ball touches a ramp entrance, all table colliders are disabled except the ramp's colliders. At the ramp exit, the opposite happens. Raycasting to the floor handles elevation changes. This approach reduced physics step time from 2ms to 0.2ms while maintaining realistic ball behavior.
Why did Shopify switch from Rapier 3D to Rapier 2D for their pinball physics?
As the scene grew more complex, the 3D physics simulation became too computationally expensive for older mobile devices, especially during multiball events with up to 6 balls. Switching to Rapier 2D with continuous collision detection reduced the physics step from 2ms to 0.2ms on average, leaving plenty of performance headroom for rendering effects like confetti.
How do you render dozens of dynamic lights efficiently in a WebGL pinball game?
Instead of using forward-rendering with point lights (impossible for 85 lights), they used two floor textures: a base color and an emissive lights texture. A separate mask texture encoded each light's ID in the R channel. A shader reads the mask to determine which light each pixel belongs to, then checks a uniform float array of light values to conditionally enable or disable that light in the emissive map.
How does the Shopify BFCM globe show real-time order data?
Server-Sent Events (SSE) stream order data to the browser in near real-time. Every arc on the globe represents a real merchant order, drawing an as-the-crow-flies path from shop to buyer. Apache FlinkSQL pipelines process the firehose of events to define arcs and compute sales and buyer metrics. The data is visible in the browser's network tab.
How do you build a dot matrix display shader for a browser-based pinball game?
Individual spritesheets are loaded and merged into one large spritesheet. Each frame, the CPU determines which part of the spritesheet to render and uploads texture offsets to the GPU. The frame size is 128x32 pixels, matching old-school DMDs. A shader selects the correct sprite region and applies a dot effect for the iconic matrix look. Bloom post-processing adds a warm glow effect.
How do you add AR and VR support to a Three.js pinball game?
Using the @react-three/xr library, AR support was added with approximately 100 lines of code. This allows users to load the pinball table in their preferred VR/AR headset and play it in their physical space. The implementation leverages the composability of the React Three Fiber ecosystem to integrate XR capabilities with minimal additional code.
What are the key design challenges when building a virtual pinball table?
The team discovered that flow and rhythm matter more than novelty. Early experiments with underground boards and sideways gravity were cut because they didn't feel fun. Claustrophobic center areas caused frustration rather than flow. The biggest improvement was opening the central lane into the pop bumper zone. Modular minigame systems solved the problem of swappable content while avoiding permanent commitments to board layout during development.
How do you handle edge cases in a pinball minigame swap system?
When a minigame is completed, the system must verify no balls are in the minigame area before swapping. With up to 6 balls in multiball mode, all ball positions must be checked. A metal fence rises in front of the minigame area during swaps to prevent balls from entering. The modular design allows minigames to be interrupted by milestones and resumed in any order.

Key Statistics & Figures

Frame rate
120fps
Target frame rate for the pinball game running in a browser
Physics step time (3D)
2ms
Physics step duration before switching to Rapier 2D
Physics step time (2D)
0.2ms
Average physics step duration after switching to Rapier 2D
Dynamic lights on table
85-86
Total number of individually controllable lights on the pinball table
Dot matrix display resolution
128x32 pixels
Frame size for the DMD shader, matching old-school pinball displays
AR/VR integration code
~100 lines
Lines of code needed to wire up AR support using @react-three/xr
Development hours (lead engineer)
400 hours
Hours clocked by Senior Engineer Daniel Beauchamp before launch
Maximum balls in play
6
Maximum number of simultaneous balls with CCD enabled during multiball
Mini games count
5 + 2 special
Five regular mini games plus two special ones triggered by live data
Years on Exosphere
3
Third consecutive year Shopify displayed the BFCM globe on the Las Vegas Sphere

Technologies & Tools

Some links below are affiliate links. We may earn a commission if you make a purchase.

3d Rendering
Three.js
Core render engine for the entire pinball scene and globe visualization
Frontend Framework
React Three Fiber
React renderer for Three.js providing composability for building game systems
Physics Engine
Rapier2d
2D physics simulation for ball collisions, flippers, bumpers, and all table elements
Xr Framework
@react-three/Xr
AR/VR support enabling headset-based pinball play with ~100 lines of integration code
State Management
Zustand
State management for the pinball game application
Real-time Data Streaming
Server-sent Events
Streaming live order data from Shopify merchants to the globe visualization
Stream Processing
Apache Flinksql
Processing firehose of order events to define arcs and compute sales and buyer metrics
3d Modeling
Blender
Light baking and 3D asset creation for the pinball scene
GPU Programming
Glsl Shaders
Custom shaders for dot matrix display, dynamic lighting mask system, and visual effects
Visual Effects
React-postprocessing
Post-processing stack with bloom effect for the warm DMD glow
Monitoring
Grafana
Powering real-time infrastructure stats displayed on the in-game computer monitor
Rendering Optimization
Meshbasicmaterial
Used for most scene materials since lighting was baked, improving rendering performance

Key Actionable Insights

1
Consider using 2D physics with raycasting for 3D-looking games instead of full 3D physics simulation. Shopify achieved a 10x performance improvement (2ms to 0.2ms per physics step) by switching from Rapier 3D to Rapier 2D, using sensor-based collider activation/deactivation to simulate ramps and elevation changes.
This is especially important when targeting mobile devices or when multiball/multi-object scenarios demand high-frequency collision detection with CCD enabled.
2
Use texture-based light indexing instead of forward-rendered point lights when you need dozens of dynamic lights. By encoding light IDs in a mask texture's R channel and driving visibility through a uniform float array in a shader, you can control 85+ lights without the performance cost of individual point lights.
This approach works particularly well when combined with baked lighting in Blender using MeshBasicMaterials, keeping the base rendering cost low while dynamic elements are handled efficiently by shaders.
3
Bake your scene lighting in Blender and use MeshBasicMaterials in Three.js for static elements. This eliminates the need for real-time light calculations on most surfaces, dramatically improving rendering performance and leaving GPU headroom for dynamic effects.
This technique is well-suited for scenes where most lighting is fixed and only specific elements (like pinball table lights) need dynamic control.
4
Build modular, swappable game systems rather than committing to permanent elements early in development. Shopify's modular minigame system, where elements rise through the floor, allowed easy iteration without requiring changes to the 3D model, light baking, materials, or game logic for every swap.
Modularity also enabled flexible game flow where milestones could interrupt any minigame and resume in any order, decoupling content from physical board position.
5
When designing interactive experiences, prioritize flow and rhythm over novelty. The Shopify team cut several creative ideas (underground boards, sideways gravity, WarioWare-style minigames) because they disrupted the gameplay feel. Opening up claustrophobic areas into flowing paths fundamentally improved the experience.
This aligns with the pinball design philosophy of 'precision in the face of chaos' โ€” the best additions were ones that maintained the core gameplay loop rather than disrupting it with gimmicks.
6
Use Server-Sent Events (SSE) for streaming real-time data to browser visualizations rather than WebSockets when the data flow is primarily server-to-client. Shopify streams live order data, sales metrics, and infrastructure stats through SSE, which is simpler to implement and debug (visible in the browser network tab).
Paired with Apache FlinkSQL for stream processing, this architecture handles planetary-scale event volumes during peak traffic like BFCM weekend.

Common Pitfalls

1
Using a 3D physics engine for what is essentially a 2D game. The team initially used Rapier 3D thinking it would simplify ramp handling, but found it was too computationally expensive for mobile devices, especially during multiball events. The extra CPU cycles for 3D physics simulation were not worth the marginal realism gain.
The solution was switching to Rapier 2D with a sensor-based collider activation system and raycasting for elevation, achieving 10x better physics performance.
2
Designing claustrophobic center areas on the pinball table that block ball flow. The team's initial arrangement of ramp entrances and walls in the middle area felt like a normal real table layout, but it created frustration because flipper shots naturally converge there. The overwhelming feeling was rejection rather than flow.
Opening the center into a lane leading to the pop bumper zone fundamentally changed the table feel and created the flowing gameplay they wanted.
3
Over-investing in novel mechanics that don't improve the core experience. The team spent weeks on an underground second board with WarioWare-style minigames before cutting it. The underground area felt wrong at full table size, and camera movements were problematic on mobile and VR platforms.
The lesson was 'sometimes you just gotta make a normal thing really well' โ€” focusing on polishing standard pinball mechanics produced a better result than novelty features.
4
Not accounting for multi-ball edge cases in swappable game elements. When a minigame completes and needs to swap, you must verify all balls (up to 6) have exited the swap area. Without physical barriers, balls can enter during the transition, causing physics glitches and broken game state.
The team solved this by raising a metal fence in front of the minigame area during swaps and checking all ball positions before initiating the transition.
5
Trying to design intricate split ramps in a 2D physics environment. The original design had ramps splitting into and out of each other, which is possible on real tables but extremely difficult to program without true 3D gravity pulling balls downward into the table surface.
Simplifying ramp design and focusing on one easier and one harder ramp produced a more natural and enjoyable gameplay flow.

Related Concepts

Webgl Rendering Optimization
Browser-based Game Development
Real-time Data Visualization
Physics Engine Selection And Optimization
Shader Programming For Dynamic Effects
AR/VR Web Experiences With Webxr
Stream Processing At Scale
Baked Lighting Vs Real-time Lighting
Game Design Iteration And Playtesting
Continuous Collision Detection (ccd)
Server-sent Events Vs Websockets
Spritesheet Animation Systems
Modular Game Architecture
Post-processing Effects In Three.js