The Problem: A Static View of a Living Universe
EVE Frontier is a living universe. Players link smart gates, form tribes, anchor space stations, deploy fuel, and engage in constant activity. But until now, EF-Map showed you a static snapshot—you'd refresh the page and wonder what happened in the 30 minutes since your last load.
We wanted something better: a map that breathes with the universe. When someone links their smart gate to another across the cosmos, you should see it appear. When a tribe adds new members, the map should reflect it. When fuel gets delivered to a distant station, you should know.
Architecture: From Blockchain to Browser
The challenge was connecting our existing blockchain indexing infrastructure (Primordium pg-indexer → PostgreSQL) to browsers viewing the EVE Frontier map. Here's how we built it:
Component Breakdown
- Event Emitter (
tools/event-emitter/): A lightweight Node.js Docker container that polls Postgres for new events every 5 seconds. When it finds fresh smart gate links, tribe changes, or fuel deliveries, it POSTs them to our Cloudflare Worker. - Durable Object Hub: A single Cloudflare Durable Object that maintains WebSocket connections to all browsers. Uses the Hibernation API—meaning zero CPU charges when no events are flowing.
- Browser Components:
EventTicker(scrolling text feed),EventHalos(glowing rings on affected systems), andEventFlashes(instant attention-grabbing flashes).
The 10 Event Types We Track
Every event gets transformed into plain English with emoji indicators. Here's what you'll see in the EVE Frontier map ticker:
Visual Effects: Making Events Pop
The Event Ticker
A CSS marquee-style scrolling ticker runs along the bottom of the map. Events fade in, scroll left-to-right, and maintain the last 50 items. Each event stays visible for about 30 seconds as it crosses the screen, giving you time to notice what's happening across the universe.
Event Halos
When an event affects a star system, that system gets a glowing halo ring. The ring pulses gently for 10 seconds, drawing your eye to the action. If you're zoomed out viewing the entire EVE Frontier galaxy, these halos let you spot activity at a glance.
Event Flashes
For immediate attention, affected systems also get a brief 240px flash effect—a quick brightness burst that fades over 2 seconds. Combined with halos, this creates a "pop then glow" effect that's noticeable without being obnoxious.
Why Durable Objects + WebSocket Hibernation?
We evaluated several real-time architectures before settling on Cloudflare Durable Objects with the WebSocket Hibernation API. Here's why:
Billing Model Deep Dive
Cloudflare Durable Objects bill on two axes:
- Requests: 100,000/day free, then $0.15/million
- Duration: 12,500 GB-s/day free (~13,000 GB-s with wall-clock billing)
The critical insight: outgoing WebSocket messages to browsers don't count as requests. Only the initial connection handshake and incoming messages count. Since our architecture is push-only (server → browser), our request count scales with blockchain events, not user count.
(1,561 events, 2.98k requests, 0.364 GB-s)
Even if EVE Frontier sees 10x more blockchain activity, we'd still be comfortably within limits. And because of hibernation, connected-but-idle browsers cost almost nothing.
Implementation Details
Event Emitter: Polling Postgres
The emitter runs as a Docker container alongside our existing indexing infrastructure. Every 5 seconds it queries Postgres for events newer than its last checkpoint:
SELECT * FROM events
WHERE timestamp > $last_checkpoint
ORDER BY timestamp ASC
LIMIT 100
Found events get POSTed to /api/events/emit with an admin token. The Worker validates the token, routes to the Durable Object, and the DO broadcasts to all connected WebSockets.
Durable Object: The Fan-Out Hub
The DO maintains a Map<WebSocket, ClientInfo> of connections. When an event arrives:
// Broadcast to all connected clients
for (const [ws, info] of this.connections) {
try {
ws.send(JSON.stringify(event));
} catch (e) {
// Dead connection, clean up
this.connections.delete(ws);
}
}
The Hibernation API handles the complexity: WebSockets automatically wake the DO on incoming messages, and the DO goes dormant (zero CPU) when quiet. No keep-alive polling needed.
Browser: React + Three.js Integration
On the frontend, useLiveEvents.ts manages the WebSocket lifecycle:
const ws = new WebSocket(wsUrl);
ws.onmessage = (e) => {
const event = JSON.parse(e.data);
addTickerItem(formatEvent(event));
triggerHalo(event.solarSystemId);
triggerFlash(event.solarSystemId);
};
Effects are managed in Three.js: halos are animated rings rendered as sprites, flashes are temporary brightness multipliers on star meshes. Both auto-cleanup after their duration expires.
Operational Considerations
Graceful Degradation
If the WebSocket connection fails, the EVE Frontier map continues working—you just won't see live events. The ticker shows "Connecting..." and retries with exponential backoff. Users never see errors; they just get a slightly less dynamic experience.
Event Deduplication
The emitter tracks the last-seen event timestamp and only forwards newer items. The browser also maintains a seen-IDs set to prevent duplicate ticker entries from race conditions.
Rate Limiting
The /api/events/emit endpoint requires an admin token and rate-limits to 100 events/minute. This prevents runaway loops if the emitter malfunctions.
What's Next
Live events opens up exciting possibilities for EVE Frontier map:
- Event filtering: Show only tribe events, only your corporation's activity, or only a specific region
- Audio cues: Optional sounds for high-priority events (gate attacks, territory changes)
- Historical replay: Scrub through the last hour of events to see what you missed
- Activity heatmaps: Aggregate event density to show where the action is hottest
For now, just watching the universe pulse with activity is deeply satisfying. Load up the EVE Frontier map, zoom out, and watch the smart gate links appear as players build the transportation network in real-time.