Durable Triva
A 60-second competitive trivia game where Temporal Replay 2026 badges race to complete real Rust Temporal Activities.
A 60-second competitive trivia game where Temporal Replay 2026 badges race to complete real Rust Temporal Activities. The badges are Workers, the questions are Activities, and the game survives when a player deliberately crashes one.
A Rust/Axum controller runs the Workflow Worker and a 16:9 scoreboard on a laptop. Temporal Cloud coordinates questions, retries unfinished work, and preserves the round through Worker failures. Physical badges and simulated badges compete in the same Workflow.
Temporal Concepts Demonstrated#
| Concept | What It Does Here | Where to Look |
|---|---|---|
| Workflow | One GameWorkflow owns the timer, question deck, scores, power-ups, and result |
web/src/workflow.rs |
| Activities | Every question is a real trivia.answer_question Activity completed by a badge |
firmware/src/main.rs |
| Heartbeats and Retries | A simulated crash stops heartbeats so Temporal gives unfinished work to another Worker | firmware/src/main.rs |
| Queries | The scoreboard and the badges read live state without changing Workflow history | web/src/workflow.rs |
| Updates | Operator power-ups durably change the running Workflow | web/src/workflow.rs |
| Visibility and Memo | Completed rounds remain discoverable without a game-state database | web/src/main.rs |
Architecture#
The Workflow is the game state. Each badge owns at most one outstanding Activity, and a heartbeat timeout returns that work to the queue. The laptop can restart and reconstruct the board from Temporal history without a separate game-state database.
Shared Temporal configuration#
Every component — the controller, the simulators and the badge firmware — reads one dotenv file at the repository root. Copy the example and fill in your Temporal Cloud namespace and API key:
cp .env.temporal.example .env
TEMPORAL_ADDRESS=your-namespace.tmprl.cloud:7233
TEMPORAL_NAMESPACE=your-namespace.your-account
TEMPORAL_API_KEY=your-api-key
.env is ignored by Git. Set TEMPORAL_ENV_FILE to read the file from
somewhere else; an explicit path must exist. The controller and simulators also
accept a temporal.toml profile and TEMPORAL_* environment variables, in the
SDK's documented precedence order.
Quick Start#
You need Rust, macOS or Linux, and a Temporal Cloud namespace with an API key. Badge hardware is optional for the simulated path.
-
Complete the shared Temporal configuration above.
-
Start the TV controller:
./run-web.sh -
Start ten simulated badge Workers in a second terminal:
./simulate-badges.sh 10
Open http://127.0.0.1:3000, mirror it to the TV, and select Start Round.
To use physical hardware, continue with the badge firmware guide.
Running the demo on hardware#
Flash the default image. ./build-firmware.sh with no arguments builds it
and prints verified no HIL test protocol in the image; the hil feature adds
a USB command channel that can inject answers and crashes, and it has no place
on a badge you hand to someone.
./build-firmware.sh
./flash-badge.sh /dev/cu.usbmodemXXXXXXX
Then, per demo:
-
Start the controller with
./run-web.shand open http://127.0.0.1:3000. -
Wait for the attract screen to list every badge you intend to play
before selecting Start Round. The round is sized from Temporal's poller
list at the moment it starts, so a badge that is still booting is not dealt
a question and sits out the whole round. The roster refreshes every two
seconds and is also available at
GET /api/badges. -
A badge takes roughly ten seconds from power-on to
Polling trivia queue, walkingBooting -> ConnectingWifi -> SyncingTime -> ConnectingCloudon its OLED, so a reboot is visible rather than silent.
If a badge reboots mid-round the Workflow is designed for it: the Activity heartbeat lapses, the question is reassigned to another badge, and the recovered badge rejoins and is dealt new work. That path is the demo, not a failure of it.
Common checks#
Run the host-side checks -- fmt, clippy and the full test suite for web,
shared, badge-screen and badge-input -- from the repository root:
./check-host.sh
git diff --check
Use the script rather than a bare cargo test. .cargo/config.toml pins
build.target to xtensa-esp32s3-espidf so the firmware builds without extra
flags, which means a plain cargo test or cargo clippy from the root tries
to build the host crates for the badge and fails. check-host.sh supplies the
host target and the stable toolchain explicitly. Extra arguments are passed
through to cargo test, so ./check-host.sh winner filters as usual.
Firmware build and hardware verification live in ./build-firmware.sh.
Component-specific commands are in the firmware guide
and web guide.
Documentation#
| Guide | Covers |
|---|---|
| Contributor guide | Runtime boundaries, required commands, and the rules that have already cost us a round |
| Badge firmware | ESP Rust toolchain, Wi-Fi, build, flash, controls, sleep, haptics, and physical verification |
| Web controller | TV setup, running a round, operator controls, Worker recovery, tests, and Temporal Visibility |
| Game specification | Scoring, scheduling, retries, power-ups, UI states, and accepted design decisions |
| Engineering journal | Chronological implementation notes, failures, validation, and unresolved work |
Project Layout#
firmware/— Rust/ESP-IDF Activity Worker for the Replay 2026 badge.-
web/— Rust Workflow Worker, Axum controller, TV UI, and the badge simulator. shared/— serialized game contract shared by every Worker.-
badge-screen/— hardware-independent 128×64 badge screen renderer and previews. badge-input/— hardware-independent button gesture state machine.
Both the controller and firmware use Temporal Rust SDK 0.7.0. See the
component guides for build and test commands.
