Analog Town
Simulate fictional personas' thoughts on a broadcast event
Live Space: https://huggingface.co/spaces/build-small-hackathon/analog-town
Model:
Qwen/Qwen2.5-7B-Instruct(fallback:Qwen/Qwen2.5-14B-Instruct)
SDK: Gradio · CPU-basic · browser-side TTS, no GPU required
You drop a single piece of news into a fictional town — "the old grain silo will be demolished next month" — and the town reacts. Six residents each have their own values, fears, hopes, and grudges. Instead of chatting with them, you tune an analog radio dial through static and hear each resident's inner monologue in a different voice. The next day, you broadcast a follow-up. The town remembers. The map slowly drifts — angry, hopeful, suspicious. That is Analog Town: a perspective-rehearsal tool dressed up as a 1970s shortwave receiver, run by a 7B model with a typed state machine glued behind it.
Writers, tabletop RPG game masters, classroom debate moderators, and community planners all share the same workflow problem: when something new lands in a fictional community — a factory closing, a sacred grove being bisected, a stranger arriving — they have to mentally simulate every stakeholder's reaction to make the story (or the policy roleplay) feel honest.
The usual fallback is to open a chat window and ask a large model to roleplay each character one by one. That works, but it has three failure modes:
We wanted a tool that pushed in the opposite direction: small model, structured output, whimsical interface, time built in. A signal node, not a conversation.
Under the hood, Analog Town is not roleplay. Each character is a typed Pydantic state machine, and the model's only job is to compute one well-defined transition:
(Agent Profile + Previous Agent State + Broadcast Event + Day N)
→ Updated Agent State + Internal Monologue + Likely Actions
Every transition returns a strict JSON object (see schemas.py) with fields like:
event_summary_from_agent_view — how this character heard it (often a misreading)activated_memory — which private history bubbled upvalue_conflict — which core value the event collides withemotion_delta — signed changes to trust, anger, fear, hope, curiosity, social_energyupdated_state — the new emotional state, clamped to [0, 1]internal_monologue — 1–3 sentences, in their voicelikely_private_action / likely_public_actionuncertainty — what the model itself isn't sure aboutsafety_note — a fixed disclaimer that this is fictional rehearsal, not predictionThe system prompt is uncompromising: "Your job is not to produce the most dramatic answer. Your job is to produce a plausible, grounded, internally consistent state transition." If the JSON comes back malformed, a dedicated REPAIR_PROMPT rewrites it cleanly.
This is the design lever that lets a 7B model carry a six-character, multi-day simulation: we never ask it to generate freely, we ask it to fill a tightly-shaped form.
The hackathon push added five differentiators on top of the original single-shot prototype.
After every broadcast, each sprite's avatar ring tints to that character's dominant emotion: red for anger, orange for fear, amber for hope, green for trust, blue for curiosity, purple for social energy. The map becomes a glanceable mood report — you can see at a single look which way each resident bent.
If any axis of an agent's emotion_delta swings by more than 0.6 in a single broadcast, a pulsing 📢 badge appears on their sprite. Writers and GMs use this to spot dramatic story beats automatically — these are the residents whose minds just lurched.
Tune the dial to an agent or click their sprite, and a distinct voice speaks their monologue aloud through the existing ambient-static loop. The static smoothly ducks while the voice plays and swells back up when it ends. Each character gets a deterministically-picked voice from a curated pool of 30+ system voices (male/female, with pitch shifts for elder/young characters), so Margaret the retired stationmaster doesn't sound like Devon the line cook.
We chose browser-side SpeechSynthesisUtterance over an HF Inference TTS endpoint for three reasons: zero latency, zero rate-limit risk, and zero GPU cost — true to the "build small" spirit.
Today's most differentiating feature. Press TRANSMIT once for Day 1. Press it again — agents continue from their post-Day-1 emotional state, with a prompt prefix that tells the model "this is Day 2, do not echo what they said last time." A small horizontal timeline strip shows each broadcast as a clickable pill (DAY 1 · Hotel announcement · ⚠ 2 anomalies). The map heatmap visibly drifts day over day. Click any past pill to time-travel: the map reverts to that day's state, the slider re-tunes through that day's monologues, the day badge updates. A 🔁 RESET button next to TRANSMIT clears the chain back to Day 1.
This turns a one-shot rehearsal into a multi-day arc. Day 1: "the factory will reopen." Day 3: "wages will be 30% lower than last time." Day 5: "first walkout scheduled." Watch each character's mood lurch across the map.
Open the 🎨 CREATIVE MODE accordion, type a 1–3 sentence concept ("a haunted seaside lighthouse where the keeper has vanished and the village is split on whether to investigate"), pick how many residents you want (3–6), and hit 🪄 GENERATE WITH AI. Qwen 2.5 7B drafts a complete, schema-validated Town JSON — full agent dossiers, frequencies, map positions, relationships, and a default broadcast event. You can edit the JSON inline, upload a custom map PNG, then 💾 SAVE & LOAD to register the town and start broadcasting. Frequencies are auto-spread, sprite positions clamped, and the town gets a custom_ prefix so it never overwrites a preset.
The interface is the point. Analog Town is built to feel like the inside of a beat-up radio van parked at the edge of a town you don't live in.
The CRT theme. A custom Gradio CSS theme renders the whole app in amber-and-green phosphor type on pitch-black cards, with scanning-line overlays and glowing horizontal signal-strength bars. IBM Plex Mono for instrument readouts, Crimson Pro for monologues — the typography itself splits "machine talking" from "human talking."
The dial. A frequency slider runs from 87.0 to 108.0 FM. Each character lives on a fixed frequency. As you slide closer to a station, the signal-strength bar climbs, the corresponding character's dossier comes into focus, and their voice fades in through the static.
The static. A loopable 15-second analog hiss bed lives under everything. A small client-side audio manager watches the signal-strength value and smoothly fades the static out when you tune into a station, then back in when you scan between them — and ducks further whenever a voice is speaking.
The map. Nine town presets each ship with a custom 2D isometric pixel-art background, each representing a unique narrative conflict:
Characters are placed on each map as circular pixel-art tokens. Clicking a token tunes the receiver, swaps in their dossier, and starts speaking their latest intercepted thought.
You load Port Whisper — a coastal fishing village. Four residents, one foggy harbor map. The broadcast box pre-fills with the town's default scenario:
Day 1: "The state energy department has approved the construction of a 40-turbine offshore wind array, 5 miles off the Port Whisper coast."
You hit TRANSMIT. Four model calls run, four typed transitions come back. The map sprites tint: Captain Arthur (the elder fisherman) goes red, Mayor Sarah (the pragmatist) goes amber, Chloe (the marine biologist) goes blue. A 📢 badge pulses on Captain Arthur — his emotional delta exceeded 0.6.
You tune to 89.1 MHz — the static fades and Captain Arthur's voice comes through:
"They're putting iron in my ocean. They didn't ask. They didn't visit. They never do."
You change the broadcast text for Day 2 to "The state has scheduled a public hearing at the harbor next Friday and is offering union-scale jobs in turbine maintenance." You TRANSMIT again. Now the prompt carries DAY 2, the temperature bumps up slightly, and the explicit instruction tells the model don't echo prior beats.
The map redraws. Captain Arthur is still red but the anomaly badge is gone (his delta this round was smaller). Mayor Sarah turned green — trust climbing. A new DAY 2 pill appears in the timeline strip and the map's day badge updates.
You tune back to Arthur:
"A hearing. I've sat through a hundred hearings. But maintenance jobs — that's the first thing they've said that sounds like remembering us."
You click the DAY 1 pill — the map snaps back to Day 1's heatmap and Arthur's Day 1 voice returns. Click DAY 2 to come back. Press 🔁 RESET to clear the chain.
Analog Town was engineered against the Build Small Hackathon constraints from the first commit:
Qwen/Qwen2.5-7B-Instruct as the primary inference target through the Hugging Face Inference Client, with Qwen/Qwen2.5-14B-Instruct as an automatic fallback if rate limits hit.max_tokens=900 and temperature=0.3 for Day 1, ramping to ~0.55 for chained days to encourage variation without losing JSON discipline.window.speechSynthesis instead of a server-side TTS endpoint. Zero GPU, zero rate-limits, instant playback, distinct per-character voices.cpu-basic hardware.Qwen 2.5 7B endpoint as the simulator — no separate model, no separate inference path.town.json, broadcast_event.json, agent_traces.jsonl, auto-generated dataset card). Naive PII patterns are stripped during export, and a safety note is attached to every transition.🎙 Tune in: https://huggingface.co/spaces/build-small-hackathon/analog-town
Analog Town is not a chatbot. It's a tiny social weather station for fictional worlds.
Simulate fictional personas' thoughts on a broadcast event
More from this author