Inspiration

We grew up playing Bop-It — the toy that barked commands and punished you for hesitating. There's something primal about a physical object ordering you around in front of a crowd. We wanted to bring that energy into a party game built from scratch, but with a twist: instead of a dedicated plastic toy, the controller is a secret payload, and instead of fixed buttons, the box senses how you're handling it.

The premise wrote itself. What if a fragile package could tell when you fumbled it — and judged you for it in front of everyone?

What it does

Courier Crisis is a cyber-physical party game for 3–10 players. The "controller" is a 3D printed enclosure packed with an ESP32, an MPU6050 IMU, capacitive touch pads, a microphone, a rotary potentiometer, and an LCD screen. Players pass it around a circle while a TV-projected web interface barks out random physical challenges. The box knows exactly how you handle it.

Each round is 90 seconds. Challenges fire one at a time, split between solo tasks and two-player pass modes:

Pass Modes (appear roughly every 4–5 challenges):

  • Secure Transfer — hand the package to the next player without tilting it past ±10°. A live tilt readout on screen shows every wobble.
  • Air Drop — shake the controller hard enough to trigger a simulated toss. An on-screen box animates along a trajectory arc to a landing zone when the shake threshold is met.
  • Dead Man Switch — pass the controller while keeping at least one finger on a capacitive touch pad at all times. Lose contact for more than 100 ms and the attempt resets.

Single-Player Challenges:

  • Attitude Lock — a ghost box appears on screen in a random orientation. Match it with the real box within ±5° and hold steady for 2.5 seconds. Concentric rings shift from red to green as alignment improves.
  • Kinetic Storage — shake the controller to charge up kinetic energy. A seismograph-style graph tracks acceleration in real time. Keep shake confidence above 75% long enough to bank 4 seconds of sustained shake.
  • Stealth Mode — the microphone is listening. Maintain absolute silence for 8 seconds. Any sound above the threshold resets the progress ring and the timer. The room goes dead quiet — or it doesn't.
  • Combination Decrypting — a three-number combination is randomly generated. Rotate the physical dial to match each target within ±3, holding briefly to lock each number before advancing to the next.

Pass a challenge and the score ticks up. Fail and there's a brief cooldown before the next one fires. The clock is the only pressure. A full-screen TV display shows a live 3D box rotating in sync with the real one, its color bleeding from gold to red as the team takes damage. An 11 Labs-generated announcer calls out every challenge so players don't need to read the screen.

How we built it

The pipeline has three layers that had to work together in real time.

On the box: An ESP32 samples the MPU6050 at ~100 Hz, reads capacitive touch pads, a microphone, and a rotary potentiometer, then streams all sensor data over WebSocket to the backend as JSON at ~100 Hz.

On the server: A FastAPI + python-socketio backend receives the stream and runs a game state machine. Server-side atan2 math on the accelerometer data computes live tilt angle. Gesture events — shakes, tosses, hard catches — are detected via acceleration magnitude thresholds on the raw IMU data. Touch continuity, sound level, and dial position are each evaluated by dedicated challenge handlers. The challenge engine supports two core mechanics: hold challenges auto-succeed at expiry if the condition stayed within bounds (Secure Transfer, Attitude Lock, Stealth Mode, Dead Man Switch), and action challenges auto-fail at expiry if the target event was never detected (Air Drop, Kinetic Storage, Combination Decrypting). A challenge sequencer enforces distribution rules — no immediate repeats, no back-to-back pass modes, and a sensor cooldown so two IMU-heavy challenges never fire consecutively. A 90-second round timer and 3-2-1 countdown sequence drive the full game loop. State changes emit Socket.IO events to every connected browser.

In the browser: A single index.html handles everything the audience sees. Three.js renders a 3D box that rotates in real time from the live IMU stream, with material color lerping from safe gold to danger red based on accumulated damage. Attitude Lock adds a semi-transparent ghost box and concentric alignment rings. Kinetic Storage renders a live seismograph graph. Combination Decrypting mirrors the physical dial on a large circular graphic with a tolerance arc. Per-challenge timer bars drain in the challenge's accent color, and standardized transition screens give the player a 3-second heads-up before each new challenge. Full-screen flash overlays signal success and failure. The 11 labs announcer reads each challenge aloud so no one needs to stare at the screen while holding the box.

During development, a Streamlit simulator replaces the physical ESP32 — sliders for every sensor axis and gesture injection buttons let us iterate on game feel without hardware in the loop.

Challenges we ran into

Latency budget. Getting IMU data from the box to a visible UI reaction in under 10 ms required keeping the full pipeline in-process. Early prototypes used HTTP polling; switching to WebSocket + Socket.IO on the same FastAPI server was the right call but required restructuring the server architecture mid-sprint.

Tilt jitter. Raw accelerometer data near a threshold causes the challenge state to flicker — a box sitting at exactly 10° reads 9.8° one frame and 10.2° the next. Without a low-pass filter, tilt-based challenges were failing on noise rather than player behavior. We shipped a partial fix with conservative threshold margins, but a proper rolling average is still on the backlog.

Gesture detection without a model. Distinguishing a toss from a hard catch using only magnitude thresholds on raw IMU data is harder than it sounds — both are sudden acceleration spikes. We tuned per-gesture thresholds and timing windows to make them feel distinct, but it required more iteration than expected.

Capacitive touch debounce. The Dead Man Switch challenge was either too forgiving or too punishing depending on the debounce window. Too short and normal grip adjustments triggered false failures; too long and the challenge lost its tension. We landed on 100 ms after extensive playtesting.

Microphone calibration. Stealth Mode needed a baseline calibration step at challenge start because ambient noise levels varied wildly between rooms. Without it, a loud room made the challenge impossible and a quiet room made it trivial.

Game feel under real party conditions. The first playtest revealed that 90 seconds felt too long when challenges resolved quickly. Tuning challenge durations, detection thresholds, and the cooldown window to hit the right tension-to-chaos ratio took more iteration than the networking code.

Accomplishments that we're proud of

The end-to-end pipeline works. IMU data leaves the ESP32, travels over WebSocket, runs through the game state machine, and lands as a visible UI reaction in under 10 ms on a real TV. That's the core promise and it holds up.

The 3D box in the browser rotating in sync with the physical box — in real time, with no perceptible lag — is the single best demo moment. When a first-time player picks up the box and watches the screen mirror their hands, the game explains itself without a word.

Seven distinct challenge types shipped, spanning four different sensors — IMU, capacitive touch, microphone, and rotary dial. Each one feels mechanically different to play despite running through the same state machine and UI framework.

The Dead Man Switch is the crowd favorite. Watching two players try to coordinate a handoff while keeping a finger on a copper pad — and the room erupting when contact drops — is exactly the energy we were designing for.

We also shipped a fully playable game loop without any hardware dependencies during development, thanks to the Streamlit simulator. Every mechanic was designed, implemented, and tuned against simulated sensor data before a physical box was ever connected.

What we learned

WebSocket over local Wi-Fi is the right protocol for this kind of cyber-physical game. HTTP adds too much overhead; Bluetooth range and pairing friction ruled it out. WebSocket gave us a persistent, low-overhead channel that works across every device on the same network with no configuration.

Gesture detection from raw IMU data rewards specificity. Threshold math works, but each gesture needs its own carefully chosen combination of magnitude window, duration, and cooldown to feel intentional rather than accidental. There's no substitute for physical testing with real people.

Multi-sensor challenges multiply design complexity. Each new sensor type — touch, microphone, dial — brought its own calibration quirks, debounce needs, and failure modes. The challenge engine had to be flexible enough to evaluate fundamentally different input types through a single state machine interface.

Game feel is a first-class engineering concern. The challenge durations, detection thresholds, and cooldown timer are not just game design parameters — they determine whether the state machine produces tension or tedium. They needed as much iteration as the networking code.

What's next for Courier Crisis

Radiation Shielding challenge. A photoresistor is already specced in the design doc — covering the light sensor to simulate shielding a radioactive package. The hardware is cheap; wiring it into the challenge engine is the next sprint.

Simultaneous multi-constraint challenges. Right now every challenge tests exactly one condition. "Shake it while keeping it flat" is the obvious next tier — it requires evaluating multiple sensors in parallel and is mechanically much harder to survive.

Streak multiplier. Scoring is currently flat +1 per challenge passed. A multiplier that builds on consecutive successes and resets on failure would reward skilled play and create comeback moments.

A proper low-pass filter. A rolling average or complementary filter on the tilt calculation would eliminate threshold jitter and make tilt-based challenges feel fair rather than random near the edges.

Built With

Share this project:

Updates