Real-time crowd evacuation simulator for FIFA World Cup stadiums, built at HackCU 12 (March 2026).
Drop 500 people into a stadium. Start a fire. Watch them evacuate. AI responder agents β powered by Google Gemini β autonomously detect bottlenecks and reroute the crowd. A stadium PA system (ElevenLabs) announces emergencies in real-time. You control the chaos.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FRONTEND (Next.js + Canvas) β
β ββββββββββββββ ββββββββββββββββ βββββββββββββ βββββββββββββ β
β β Stadium β β Control β β Metrics β β Gemini β β
β β Canvas β β Panel β β Dashboardβ β Chat β β
β β (rAF+Lerp)β β (Obstacles) β β (Stats) β β (NL Q&A) β β
β βββββββ¬βββββββ ββββββββ¬ββββββββ βββββββ¬ββββββ βββββββ¬ββββββ β
β ββββββββββββββββββ΄βββββββββββββββββ΄βββββββββββββββ β
β β WebSocket (20fps) β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ
β BACKEND (Python FastAPI) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Simulation Engine β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββββ β β
β β β Crowd Agents β β Responder β β Obstacle β β β
β β β (Flow Fields β β Agents (5-10β β Manager β β β
β β β + Steering) β β Gemini AI) β β (Fire, Block)β β β
β β β 500 agents β β β β β β β
β β ββββββββββββββββ ββββββββ¬ββββββββ ββββββββββββββββββ β β
β βββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββ βββββββββ΄βββββββββ ββββββββββββββββββββββ β
β β WebSocket β β Gemini API β β ElevenLabs API β β
β β Manager β β (5 endpoints) β β (PA + Narration) β β
β βββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β MongoDB Atlas β β
β β (Scenarios, Runs, Responder Decisions) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
This is the defining feature that makes EvacSim a genuine multi-agent AI system:
Tier 1 β Crowd Agents (200-500, algorithmic): Lightweight particles following Dijkstra-based flow fields + steering behaviors. Each cell stores a direction vector toward the nearest exit β O(1) per agent per tick. Cheap to compute, smooth to render.
Tier 2 β Responder Agents (5-10, Gemini-powered): Autonomous AI decision-makers. Every ~3 seconds, each responder observes its local sector, calls Gemini for a tactical decision, and executes actions that directly mutate the simulation grid β causing hundreds of crowd agents to change direction. Actions include: redirect crowd flow, open emergency exits, deploy barricades (rendered as amber X-hatched barriers), or hold position. Responders spread across different bottlenecks using nearest-available assignment rather than all rushing to the same exit.
| Layer | Technology |
|---|---|
| Frontend | Next.js 14, React 18, TypeScript, Tailwind CSS v4, HTML5 Canvas, CSS Animations |
| Backend | Python 3.12, FastAPI, async WebSocket, numpy |
| AI | Google Gemini API β gemini-2.5-flash (real-time), gemini-2.5-pro (reports) |
| Voice | ElevenLabs TTS API β streaming + pre-generated announcements |
| Database | MongoDB Atlas via motor (async driver) |
| Pathfinding | Dijkstra-based flow fields + A* fallback for stuck agents. Agents in fire immediately trapped. |
| Dev Tools | Cursor, Google Antigravity |
cd backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # Add your API keys
uvicorn main:app --reload --port 8000cd frontend
npm install
cp .env.example .env # Defaults to localhost:8000
npm run devOpen http://localhost:3000.
backend/.env (copy from backend/.env.example):
GEMINI_API_KEY=your_gemini_api_key
ELEVENLABS_API_KEY=your_elevenlabs_api_key
ELEVENLABS_PA_VOICE_ID=pNInz6obpgDQGcFmaJgB
ELEVENLABS_NARRATOR_VOICE_ID=EXAVITQu4vr4xnSDxMaL
MONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/evacsim
frontend/.env (copy from frontend/.env.example):
NEXT_PUBLIC_WS_URL=ws://localhost:8000/ws
NEXT_PUBLIC_API_URL=http://localhost:8000
backend/
main.py # FastAPI app, CORS, lifespan
requirements.txt
simulation/
engine.py # Fixed-timestep loop, state management, scenarios
agent.py # Crowd agent: position, velocity, steering, mobility types
responder.py # Responder agent: Gemini-powered AI decision-maker
grid.py # Stadium grid: walls, exits, obstacles, flow weights
pathfinding.py # Dijkstra flow fields + A* fallback
events.py # Fire spread, timed obstacle injection
routes/
simulation.py # WebSocket endpoint, sim control, responder loop
scenarios.py # Scenario + Run CRUD
analysis.py # Gemini analysis + NL query endpoints
voice.py # ElevenLabs TTS endpoints
services/
gemini.py # Gemini API wrapper (5 integration points)
elevenlabs.py # ElevenLabs TTS wrapper
db.py # MongoDB Atlas connection + operations
analysis.py # Structured analysis service
frontend/
src/
app/
page.tsx # Main simulation page
layout.tsx # Root layout, fonts, metadata
globals.css # Tailwind base + custom styles
components/
SimulationCanvas.tsx # 2D stadium renderer (Canvas)
ControlPanel.tsx # Play/pause, speed, obstacle injection
MetricsDashboard.tsx # Evacuation stats, flow rates
ResponderLog.tsx # Live feed of AI responder decisions
GeminiChat.tsx # Natural language query interface
VoiceStatus.tsx # PA speaker indicator, audio controls
ScenarioSelector.tsx # Preset scenario dropdown
hooks/
useSimulation.ts # WebSocket connection + ref-based state buffer
useCanvas.ts # requestAnimationFrame loop + lerp interpolation
useAudio.ts # Web Audio API playback for ElevenLabs
lib/
api.ts # REST API client
ws.ts # WebSocket client with auto-reconnect
types.ts # Shared TypeScript interfaces
| Method | Path | Description |
|---|---|---|
GET |
/scenarios |
List built-in + saved scenarios |
POST |
/scenarios |
Create a new scenario |
GET |
/scenarios/{id} |
Load a specific scenario |
GET |
/runs |
List past simulation runs |
POST |
/runs |
Save a completed run |
POST |
/analyze |
Trigger Gemini tactical analysis |
GET |
/analysis |
Get structured analysis (JSON) |
POST |
/query |
Natural language query to Gemini |
POST |
/announce |
Generate ElevenLabs PA announcement |
POST |
/generate-scenario |
Generate scenario from text description |
POST |
/report |
Generate post-run Gemini Pro report |
GET |
/health |
Health check with DB status |
All messages are JSON with a type field.
Backend to Frontend:
| Type | Description |
|---|---|
state_update |
Agent positions, responder positions, metrics (20fps) |
responder_action |
Responder decision with action, params, reasoning |
gemini_analysis |
Tactical analysis or query response text |
audio_chunk |
Base64-encoded ElevenLabs audio |
pa_announcement |
Text-only PA announcement (fallback) |
simulation_complete |
Evacuation finished with final metrics |
Frontend to Backend:
| Type | Description |
|---|---|
start_simulation |
Start with { scenarioId } |
pause_simulation |
Pause the tick loop |
resume_simulation |
Resume from pause |
inject_obstacle |
Add fire/blockage at position |
remove_obstacle |
Remove obstacle at position |
set_speed |
Set simulation speed multiplier |
toggle_astar |
Enable/disable A* fallback for stuck agents |
reset_simulation |
Reset to initial state |
load_scenario |
Load a built-in scenario |
load_custom_scenario |
Load a Gemini-generated scenario |
query_gemini |
Ask a question about the simulation |
-
Responder Agent Brain β Each responder calls Gemini Flash every ~3s with its local observation. Returns JSON action that directly mutates the simulation grid. This is autonomous AI action, not analysis.
-
Tactical Analyst β Periodic dashboard-level analysis of evacuation state including responder performance evaluation.
-
Natural Language Query β Chat interface where users ask about simulation state and responder behavior.
-
Scenario Generator β Generates disaster scenarios from text descriptions as structured JSON.
-
Post-Run Report β After-action review using Gemini Pro including responder agent performance analysis.
-
Stadium PA System β Male PA voice. Pre-generated common announcements at startup for zero latency. Dynamic event-triggered announcements streamed over WebSocket.
-
Live Narration β Female narrator voice calls out every responder decision in real-time (~3-second cadence). Distinct from the PA voice so judges hear a steady stream of AI decision commentary during the demo.
- Scenario Store β Stadium layouts, obstacle configurations, agent spawn points.
- Run History β Every simulation run saved with full metrics, bottleneck data, and responder logs.
- Responder Decision Log β Individual responder decisions with reasoning for analysis and replay.
| Scenario | Agents | Events | Description |
|---|---|---|---|
| Standard Evacuation | 300 | None | Baseline β normal evacuation, no incidents |
| North Stand Fire | 400 | 2 fires | Fire breaks out in the north stand at two ignition points |
| Exit B Collapse | 500 | Exit closure + fire | Exit B collapses at tick 0, fire starts near south stand at tick 60 |
Canvas Performance: Agent positions are written to a mutable useRef, never useState. A standalone requestAnimationFrame loop reads from the ref and draws to the Canvas with lerp interpolation (20fps server data rendered at 60fps). This prevents React virtual DOM reconciliation from freezing the browser.
Pure Tick Loop: The simulation tick is pure computation β no await, no I/O. Gemini calls, ElevenLabs, and MongoDB writes run in separate async tasks. The tick loop stays at a consistent 20fps.
Pydantic Validation: Every Gemini response passes through a ResponderDecision Pydantic model. On validation failure, responders fall back to hold_position. Raw LLM output never touches the simulation grid.
Parallel Decide, Sequential Apply: Responders call Gemini in parallel via asyncio.gather, but decisions are applied one at a time with conflict checks to prevent contradictory actions.
Responder Spatial Awareness: Idle responders are dispatched to the nearest unclaimed bottleneck rather than all converging on the worst one. Responders issuing redirect or open-exit commands stay in their sector β only barricade deployment physically moves a responder to the target location.
Fire Trapping: Agents on a fire cell are immediately marked trapped. Non-fire stuck agents get A* pathfinding rescue at 50 ticks, then are marked trapped at 200 ticks (~10 seconds) if still stuck.
Built in 24 hours at HackCU 12 by a team of 4.
Side Prizes: Google Antigravity | Best Use of Gemini API | Best Use of ElevenLabs | Best Use of MongoDB Atlas