A real-time GPS overlay system for OBS streaming with a Skyrim/Elder Scrolls inspired UI. Built with FastAPI (backend) and Next.js (frontend), featuring WebSocket-based state synchronization and MapLibre GL for map rendering.
- ๐บ๏ธ Real-time Map Display: MapLibre GL powered mini-map centered on San Francisco with POI markers for real landmarks
- ๐ฎ Skyrim-Inspired UI: Fantasy RPG aesthetic with ornate borders, parchment textures, and medieval fonts
- ๐ค AI-Powered Narrative: OpenAI GPT-4o-mini transforms camera descriptions into dynamic RPG objectives and events
- โ๏ธ Boss Fight Detection: AI detects confrontations and triggers boss health bars with danger styling
- ๐ Static POI Database: 30+ real San Francisco locations (Golden Gate Bridge, coffee shops, landmarks)
- ๐ฑ External GPS Input: HTTP endpoints for phone GPS and camera data
- โก Low Latency: WebSocket broadcasting at 100ms intervals for smooth updates
- ๐ฅ OBS Compatible: Transparent overlay perfect for streaming
- ๐ Auto-Reconnect: WebSocket automatically reconnects if connection is lost
External Server (Your Phone/Camera App)
โ POST /api/location (GPS data)
โ POST /api/camera (AI descriptions every 10s)
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FastAPI Backend (Port 8787) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โข Receive GPS โ Update player position โ โ
โ โ โข Filter POIs within 1.5km radius โ โ
โ โ โข Process camera description w/ OpenAI โ โ
โ โ โข Generate objectives, detect danger โ โ
โ โ โข Trigger boss fights if aggressive โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ WebSocket (100ms) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Next.js Frontend (Port 3000) โ
โ โข Map centered on SF showing nearby POIs โ
โ โข Objective bar (red when danger detected) โ
โ โข Boss health bar (appears during fights) โ
โ โข Message overlay with RPG notifications โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
OBS Browser Source
- Python 3.10+ (Python 3.14 supported)
- Node.js 18+ and npm
- OpenAI API key (for camera description processing)
- Git
- Install Poetry (if you don't have it):
curl -sSL https://install.python-poetry.org | python3 -- Navigate to the backend directory:
cd backend- Install dependencies with Poetry:
poetry install- Create
.envfile with your OpenAI API key:
cp .env.example .env
# Edit .env and add your OpenAI API key:
# OPENAI_API_KEY=sk-your-key-hereNote: Poetry automatically manages the virtual environment - no need to manually create or activate it!
- Navigate to the frontend directory:
cd frontend- Install dependencies:
npm installOption 1: Using the run script
cd backend
./run_server.shOption 2: Using Poetry directly
cd backend
poetry run uvicorn main:app --reload --port 8787Option 3: Using Poetry shell
cd backend
poetry shell
uvicorn main:app --reload --port 8787The backend will start on http://localhost:8787. You should see:
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8787
In a new terminal:
cd frontend
npm run devThe frontend will start on http://localhost:3000. Open your browser to verify the overlay is working.
-
Open OBS Studio
-
In your scene, click the + button under Sources
-
Select Browser
-
Configure the Browser Source:
- URL:
http://localhost:3000 - Width: 1920
- Height: 1080
- FPS: 30-60
- โ Check Shutdown source when not visible
- โ Check Refresh browser when scene becomes active (optional)
- URL:
-
Click OK
The overlay should automatically be transparent in OBS. If you see a white background:
- Ensure the Browser Source has "Shutdown source when not visible" checked
- Restart the Browser Source
- Check that your OBS version supports CSS transparency
- Positioning: The map is positioned at bottom-left, objective bar at top-center
- Scaling: Use OBS's transform tools (right-click source โ Transform) to adjust size
- Performance: If you experience lag, lower the browser source FPS to 30
- Development: Keep both servers running while streaming
Update player GPS position from your phone:
curl -X POST http://localhost:8787/api/location \
-H "Content-Type: application/json" \
-d '{"lat": 37.8080, "lon": -122.4177, "heading": 180}'Response: {"status": "location_updated", "nearby_pois": 7}
Process camera AI description (triggers OpenAI processing):
curl -X POST http://localhost:8787/api/camera \
-H "Content-Type: application/json" \
-d '{
"description": "A person in a white shirt stands in a dimly lit room...",
"timestamp": 1234567890
}'Response includes AI-generated objective, danger level, and boss fight status.
Example camera descriptions:
"Person holding a can"โ Objective: "Investigate the mysterious merchant's elixir""Coffee bar closed sign"โ Objective: "Unlock the sealed tavern entrance""Person yelling aggressively, moving towards camera"โ BOSS FIGHT TRIGGERED ๐ด
Manually set objective:
curl -X POST http://localhost:8787/api/objective \
-H "Content-Type: application/json" \
-d '{"text": "Defeat the Dragon"}'Send a temporary message notification:
curl -X POST http://localhost:8787/api/message \
-H "Content-Type: application/json" \
-d '{"text": "Achievement Unlocked!", "timeoutMs": 3000}'Get current game state:
curl http://localhost:8787/api/stateThe backend broadcasts game state in the following JSON format:
{
"player": {
"lat": 37.8080,
"lon": -122.4177,
"heading": 180.0
},
"pois": [
{
"lat": 37.808,
"lon": -122.4177,
"label": "Alcatraz Island"
},
{
"lat": 37.8024,
"lon": -122.4058,
"label": "Coit Tower"
}
],
"objective": "Explore the ancient fortress ruins",
"message": {
"text": "Quest Updated!",
"visible": true,
"timeoutMs": 3000
},
"danger_level": "high",
"boss_fight_active": true,
"boss_name": "The Enraged Guardian",
"environment": "fortress interior"
}You can update the game state via HTTP POST:
curl -X POST http://localhost:8787/update \
-H "Content-Type: application/json" \
-d '{
"player": {"lat": 37.7750, "lon": -122.4195, "heading": 90.0},
"pois": [],
"objective": "New Objective",
"message": {"text": "Achievement Unlocked!", "visible": true, "timeoutMs": 3000}
}'The Skyrim-inspired UI can be customized in:
frontend/styles/fantasy-ui.module.css- UI component stylesfrontend/app/globals.css- Color palette and global styles
Color Variables:
--parchment: #E8DCC4 /* Aged parchment */
--dark-brown: #2B1810 /* Dark text */
--gold: #C9A961 /* Gold accents */
--bronze: #8B6F47 /* Bronze borders */Currently using Google Fonts:
- Cinzel: Headers and objective text
- Spectral: Body text and messages
Change fonts in frontend/app/layout.tsx.
The map uses OpenStreetMap tiles with a darkened fantasy theme. To customize:
Edit frontend/components/MapOverlay.tsx, line ~30:
style: {
// Modify map style here
sources: { /* ... */ },
layers: [ /* ... */ ]
}The backend simulates GPS movement in a circular path. To customize:
Edit backend/mock_gps.py:
center_lat = 37.7749 # Starting latitude
center_lon = -122.4194 # Starting longitude
radius = 0.002 # Circle radius in degreessquaas/
โโโ backend/
โ โโโ main.py # FastAPI server & WebSocket
โ โโโ models.py # Pydantic data models
โ โโโ mock_gps.py # GPS simulation
โ โโโ requirements.txt # Python dependencies
โโโ frontend/
โ โโโ app/
โ โ โโโ layout.tsx # Root layout with fonts
โ โ โโโ page.tsx # Main overlay page
โ โ โโโ globals.css # Global styles
โ โโโ components/
โ โ โโโ MapOverlay.tsx # MapLibre GL map
โ โ โโโ ObjectiveBar.tsx # Top objective display
โ โ โโโ MessageOverlay.tsx # Center message popup
โ โโโ hooks/
โ โ โโโ useWebSocket.ts # WebSocket connection hook
โ โโโ styles/
โ โ โโโ fantasy-ui.module.css # Skyrim-style UI
โ โโโ types/
โ โโโ game-state.ts # TypeScript interfaces
โโโ docs/
โโโ plan.md # Original implementation plan
Backend:
- FastAPI - Modern Python web framework
- Uvicorn - ASGI server
- WebSockets - Real-time communication
- Pydantic - Data validation
Frontend:
- Next.js 15 - React framework with App Router
- MapLibre GL JS - Open-source map rendering
- TypeScript - Type safety
- Tailwind CSS - Utility-first styling
Problem: Red "Disconnected" indicator appears
Solution:
- Ensure backend is running on port 8787
- Check browser console for WebSocket errors
- Verify CORS settings in
backend/main.py
Problem: Black square instead of map
Solution:
- Check browser console for MapLibre GL errors
- Ensure you have internet connection (for OSM tiles)
- Verify
maplibre-glis installed:npm list maplibre-gl
Problem: Overlay isn't transparent in OBS
Solution:
- Verify Browser Source settings
- Check that
background: transparentis inglobals.css - Try refreshing the browser source
Problem: Poetry command not found
Solution:
- Install Poetry:
curl -sSL https://install.python-poetry.org | python3 - - Add to PATH (usually done automatically)
- Restart terminal
Problem: Poetry install fails
Solution:
- Use Python 3.10-3.14
- Try:
poetry env use python3.14(or your Python version) - Clear cache:
poetry cache clear . --all
Problem: Frontend can't connect to backend
Solution:
- Confirm backend is running:
curl http://localhost:8787/ - Check firewall settings
- Verify port 8787 isn't already in use
Planned features for future versions:
- ๐ Real GPS Integration: Connect to actual GPS devices
- ๐ฏ Quest System: Full quest tracking with multiple objectives
- ๐ฌ Custom Notifications: More message types with animations
- ๐จ Theme Switcher: Dark Souls, Elden Ring, and other RPG themes
- ๐ Stats Display: Health, stamina, inventory overlay
- ๐บ๏ธ Multiple Map Styles: Fantasy, sci-fi, modern themes
- ๐ง Web Config Panel: Browser-based configuration interface
- ๐ฑ Mobile Companion: Control overlay from phone
Contributions are welcome! Feel free to:
- Report bugs
- Suggest features
- Submit pull requests
- Improve documentation
MIT License - feel free to use this for your streams!
- Map tiles: ยฉ OpenStreetMap contributors
- Map rendering: MapLibre GL JS
- Fonts: Google Fonts (Cinzel, Spectral)
- Inspiration: The Elder Scrolls V: Skyrim
Happy Streaming! ๐ฎโจ
