Mesh & radio
Trivia Over LoRa: a game show on a slow, lossy radio mesh
I built a trivia game show for a LoRa radio mesh, where every message has to fit in 200 bytes and might never arrive. Designing around airtime, lost packets and an audience I couldn't see turned out to be a lot like running a live show.
Since June I have been running a game show on a radio network where one message tops out at 200 bytes, takes up to about two and a half seconds to send, and may never arrive. My trivia bot asks a question every hour on a LoRa mesh. Players answer by tapping an emoji reaction, and at the end of each month the bot crowns a champion.
The radio wrote the brief
A LoRa mesh is a network of small radios that pass short text messages along for each other. It is slow: on the default long-range setting, throughput is about 0.98 kbps. I treated its limits the way I would treat a venue's power budget or the size of its loading door. You don't argue with them. You design the show to fit.
- 200 bytes per message. Every question in the bank is checked against the limit. The link to join the channel runs about 87 bytes, so it always goes out as its own message, where nothing can cut it in half.
- Airtime is shared. One full message takes roughly 1.6 to 2.5 seconds on air, and every second the bot talks is a second nobody else on the mesh can. The bot keeps at least two seconds between sends and never sends more than six in a minute.
- Delivery is a suggestion. Messages show up late, twice, or never. The bot reads with a 10-second overlap, drops duplicates, and ignores any reaction that lands after the answer window has closed.
The airtime math decided how often the game could speak. A question once an hour is about 0.07% of the hour on air. A question every 90 seconds, the pace of a normal quiz game, would be about 2.2%, roughly thirty times more. The EU's 1% duty-cycle ceiling, where it applies, is the usual yardstick. The hourly game sits far under it.

How a round plays
A question goes out with four numbered options. You answer by reacting to the message with 1️⃣, 2️⃣, 3️⃣ or 4️⃣. Typing the number works too. Only your first reaction counts, so there is no waiting to see what everyone else picked and then switching.
Scoring borrows from the old chat-room trivia bots, which rewarded speed. A right answer earns 10 points, plus up to 5 more for answering fast, plus 3 for being the first person right. A wrong answer costs nothing. I wanted the game friendly enough that guessing is always worth it.
There are two ways to play. Anyone can start a full game: 12 questions, 90 seconds each. Then there is the hourly question, which fires at an odd minute past the hour so it stays clear of the top-of-the-hour slot where scheduled mesh traffic piles up.
Two rules are about the audience more than the game.
The anti-grind guard. If three rounds in a row get one answer or none, the game stops itself and says it needs more than one player. Its job is to stop one person farming the leaderboard alone.
The monthly champion. At the end of each month the bot counts correct answers from both the full games and the hourly question, names the winner, archives the standings and resets the board. The whole ceremony is capped at three messages a month, and only two of those go to the main channel. The first one ran on August 1.
A playable mock, with made-up players and questions:
What broke
Every deploy wiped the scores. I caught it in July: the leaderboard went back to zero on every update. The fix was to keep the game's state in a folder on the host that survives a rebuild.
Too much math. More than four out of five questions in the bank were math. Arithmetic is the one category you can generate in bulk with a provably correct answer. Players don't want that much arithmetic, though. Now a cap holds games and the hourly question to about 18% math, and in July I added 130 curated non-math questions.
More than one right answer. On a radio mesh you can't argue with the host, so a question with two right answers is a broken question. A check at build time now fails on that shape of question unless a person has written down why there is exactly one answer. I seeded 80 of those explanations and fixed 6 questions.
The restart that replayed two weeks. After one restart the bot read about a thousand old messages, two weeks of commands, as if they were new. The server it reads from had ignored the "since" filter on the request. The fix doesn't trust the filter: after any downtime, the bot ignores anything older than five minutes.
Wrong answers were met with silence. Right answers were celebrated at the reveal. A wrong guess got nothing, which looks exactly like a guess that never arrived. On a lossy network those are different things, and players couldn't tell which one had happened. Now a wrong guess gets one short reply that says the guess was seen and never says what the right answer was. Right answers still stay quiet until the reveal, so nobody gives the answer away while others are still guessing.
Deployed is not committed. Two changes lived only on the Raspberry Pi running the bot for more than six weeks.
Running a show for a room you can't see
I spent years in live events, and the core instinct there is to read the room. You watch faces, hear the energy drop, and change the pace. A radio mesh takes all of that away. I can't see the players. I get reactions, late, some of the time.
So the game has to read the room for me, in the only signals it has. Three quiet rounds in a row means the room is empty, so stop. A guess with no reply makes a player feel unheard, so acknowledge every guess. The hourly question is the house music between sets. The monthly champion is the moment the whole crowd hears about, and you only get a few of those before people tune out, which is part of why it is capped.
The silence bug taught me the most. In a room, a host can nod at someone who guessed wrong. On a mesh, no nod is indistinguishable from a lost packet. Any feedback you don't send, the player assumes didn't happen.
Where I was the bottleneck
I reported the score wipe in July; until then it had happened on every deploy. The stranded changes were the kind of problem only a person goes looking for, because the bot was working and nothing looked wrong.
If you are about to try this
- Do the airtime math before you write the game loop. The math tells you how often you are allowed to speak, and that one number shapes everything else.
- Count bytes, not characters. Test every message you will ever send against the limit, and give links their own message.
- Assume every message is late, doubled or missing. Overlap your reads, drop duplicates, close the window hard, and ignore stale messages after a restart.
- Acknowledge every player action. Silence reads as loss.
Build it yourself
What it does: a trivia host for a LoRa text mesh, with tap-to-answer reactions, speed scoring, an hourly question, and a monthly champion. The parts:
- A LoRa mesh radio on a channel your players share, running firmware that supports emoji reactions on messages.
- A server that exposes the mesh's messages and reactions over a REST API and can send messages. The bot talks to that API and never touches the radio directly.
- A Raspberry Pi or any small always-on computer, running the bot in Docker. Mine is Python 3.12 with a single dependency for HTTP requests.
- A question bank as a file: hand-written questions plus generated ones with computed answers, each with four options and exactly one right answer.
How the pieces fit: the bot polls the API every few seconds, matches reactions to the question they answer, scores the first reaction from each player, and posts through a single sender that enforces the byte limit and the send pacing. Game state and scores live in a file in a folder outside the container. The work ran from June to mid-September.
If you run a mesh and want a game on it, or you have a better trick for answering on a network that drops messages, I am easy to reach.
NoteWritten October 2026, about work from June to September 2026.