Everything a new user (or an LLM helping you) needs to know.
Sensor Games is a small Node/Express site for playing real-time multiplayer games that use a phone's motion sensors (accelerometer, gyroscope/orientation). One person hosts a room on a big screen or their own phone; everyone else joins the room from their own phone browser. Gameplay data flows two ways:
/api/rooms) and continuous raw sensor streaming (/api/devices), described below.A target roll angle appears. Tilt your phone to match it, then tap to lock in. Closest angle wins; speed breaks ties.
Each player is assigned a random instrument. Their live accelerometer motion becomes a voice in the host's audio mix.
A minimal starter/reference game. The play screen plots the player's own accelerometer and gyroscope values live (before anything is sent), while continuously streaming the same data to the server. The host screen polls the server and renders a live scrolling chart per player. Use this as a template for building new sensor-driven games.
Everyone starts at the host's origin on a shared canvas. Each player's phone streams raw accelerometer data; the host integrates it into a 2D walking velocity/position per player (with damping so it settles instead of drifting) and draws a colored trail behind each dot. The camera smoothly pans and zooms out to keep the host origin and every walker in view.
/) and pick a game, or go straight to /rooms?game=<game-id>.POST /api/rooms./rooms?game=<game-id>, see the room in the live list (polled every 5s from GET /api/rooms), and tap it to join.play.html?peer=<hostPeerId>&room=<roomId>.DELETE /api/rooms/:roomId) when the host closes the tab or ends the game.DeviceMotionEvent /
DeviceOrientationEvent (and their permission prompts) only work in a secure context â
https:// or http://localhost. Testing over a plain LAN IP
(e.g. http://192.168.1.20:3000) will silently skip the permission prompt and no sensor
data will flow. Use a tunnel such as ngrok
(ngrok http 3000) to get an HTTPS URL for phone testing, or deploy the app (e.g. to
Render) which serves everything over HTTPS
already. All API and PeerJS calls use relative paths / location.hostname, so no code
changes are needed â the same site works unmodified at any host, including
https://fidget-camp-workshop.onrender.com.
Independent of any particular game's PeerJS messages, every play screen continuously streams raw sensor readings to the server once a room is joined and sensor permission is granted. This lets a host â or any other tool â read everyone's live sensor data over plain HTTP, without needing a PeerJS connection.
| Method | Path | Description |
|---|---|---|
| POST | /api/devices/:room |
Upsert one device's latest sensor reading for a room. |
| GET | /api/devices/:room |
Array of every device's latest reading in that room. |
| GET | /api/devices/all |
Object of { roomId: [devices...] } for every room. |
POST body shape:
{
"deviceId": "peer-id-or-any-stable-id",
"name": "Player name",
"orientation": { "alpha": 0, "beta": 0, "gamma": 0 },
"motion": { "x": 0, "y": 0, "z": 0 },
"rotationRate": { "alpha": 0, "beta": 0, "gamma": 0 }
}
Devices that stop posting for 15 seconds are automatically dropped (player left / closed tab).
The shared helper public/js/device-stream.js wraps this:
// On a play.html page, after sensor permission is granted:
const handle = startDeviceStreaming({
roomId, deviceId: peer.id, name: playerName,
onSample: (latest) => { /* plot latest.motion / latest.orientation locally */ }
});
handle.stop(); // when leaving
// On a host.html page:
const poll = pollRoomDevices(roomId, (deviceList) => { /* render everyone's data */ });
poll.stop();
// Reusable scrolling canvas chart used by Sensor Skeleton:
const plot = createSensorPlot(canvasEl, [
{ key: 'x', color: '#f87171' }, { key: 'y', color: '#4ade80' }, { key: 'z', color: '#60a5fa' }
]);
plot.push({ x, y, z });
| Method | Path | Description |
|---|---|---|
| GET | /api/rooms?game=<id> |
List open rooms, optionally filtered by game. |
| POST | /api/rooms |
Create a room: { name, game, hostPeerId } â { roomId }. |
| PATCH | /api/rooms/:roomId |
Update playerCount and refresh the room's heartbeat. |
| DELETE | /api/rooms/:roomId |
Close a room (called on host disconnect / end game). |
Rooms are held in memory and expire automatically if not heartbeated (PATCH) within 60 seconds.
server.js Express app, room API, device sensor API, PeerJS server (/peerjs)
public/
index.html Game picker / homepage
rooms.html Room lobby (host/join) for a given ?game=
docs.html This page
js/device-stream.js Shared sensor streaming + polling + canvas plotting helpers
css/style.css Shared dark theme styles
games/<game-id>/
host.html Host screen: creates room, runs game logic, shows players
play.html Player screen: joins room, reads sensors, plays the game
public/games/<game-id>/host.html and play.html (copy Sensor Skeleton as a starting point).rooms.html's GAME_META map (name, hostPath, playPath).index.html.POST /api/rooms with a unique game id; players discover it via GET /api/rooms?game=..../api/devices endpoints + device-stream.js helpers for raw sensor streaming/plotting.