🎭 Inspiration Traditional tabletop RPGs have always sparked our imagination—the thrill of collaborative storytelling, the suspense of a Game Master's narrative, and the creative freedom to shape worlds together. But we saw an opportunity: What if we could harness the power of multimodal AI to transform imagination into visual reality in real-time?
Our inspiration came from three key insights:
The Visualization Gap: Players often struggle to align their mental images of the game world, leading to miscommunication and broken immersion. Real-time Multimodal AI: Google's Gemini and modern image generation models have reached a point where they can understand context and generate compelling visuals in seconds. Social Gaming Evolution: The pandemic taught us that digital experiences can be just as meaningful as in-person ones when designed with intentionality. We asked ourselves: "Can we create a game where words literally become images, where every player sees the same world, and where an AI Game Master orchestrates the experience in real-time?"
The answer became AI Nexus.
🎯 What It Does AI Nexus is a real-time multiplayer narrative game where 4 players collaborate in an AI-orchestrated storytelling session:
1 Game Master (Human): Sets the initial scenario and guides players with text descriptions 3 Players: Interpret the GM's descriptions and submit their own visual interpretations AI Integration: Transforms every description into stunning AI-generated images instantly Real-time Sync: All players see updates simultaneously through WebSocket connections Scoring System: Players compete to match the GM's vision most accurately The Gameplay Loop Players join a lobby and roles are assigned (1 GM + 3 Players) GM sends an initial prompt → AI generates scene image GM provides text description of the scene (without showing the image to players) Players submit their own text interpretations AI generates images from each player's description Results reveal the GM's original image vs. player guesses Scoring based on visual similarity and creativity The Magic: Every action triggers real-time AI generation and broadcasts results to all connected players—no refresh needed.
🛠️ How We Built It Architecture Philosophy We chose a decoupled, real-time-first architecture to handle the complexity of simultaneous AI operations and multi-player synchronization.
Tech Stack Backend (Laravel 12 + PHP 8.2)
├── Laravel Reverb: Native WebSocket server (no external dependencies!) ├── Broadcasting Events: Real-time event distribution ├── Cache-based State: Fast player/room management without database overhead └── Service Layer: Abstracted AI integrations Frontend (Next.js 16 + React 19)
├── App Router: Server components for initial render performance ├── Laravel Echo: WebSocket client with Pusher protocol ├── TypeScript: End-to-end type safety └── Tailwind CSS 4: Rapid UI development with modern design system AI Services
├── Google Gemini 1.5 Pro: Text generation & image understanding ├── Pollinations.ai: Free image generation (primary) ├── Replicate API: Premium Stable Diffusion XL (fallback) └── Intelligent Fallback: Auto-switches if primary service fails The Three-Terminal Workflow Development requires 3 concurrent processes:
Terminal 1: Laravel API Server (Port 8000)
php artisan serve
Terminal 2: Laravel Reverb WebSocket (Port 8081)
php artisan reverb:start
Terminal 3: Next.js Frontend (Port 3000)
npm run dev This separation ensures WebSocket connections remain stable even during API hot-reloads.
Key Technical Decisions
- Why Laravel Reverb over Socket.io/Pusher?
Zero cost: No external WebSocket service subscriptions Native integration: Broadcasting events feel like normal Laravel code Scalability: Redis adapter for multi-server deployments Developer experience: broadcast(new PlayerJoined($data)) just works
- Why Cache Instead of Database?
Game sessions are ephemeral (1-hour lifespan) Read-heavy operations (constant player list updates) Eliminates migration complexity during rapid iteration TTL auto-cleanup prevents memory leaks
- Why Public Channels (No Auth)?
Hackathon scope: Focus on core functionality Room IDs act as natural access control Future enhancement: Add Laravel Sanctum for production Real-time Event Flow
graph LR A[Player Action] --> B[API Endpoint] B --> C[GameController] C --> D[Update Cache] C --> E[Broadcast Event] E --> F[Laravel Reverb] F --> G[All Connected Clients] G --> H[Frontend Listeners] H --> I[UI Updates] Example: Player Joins Room
// Backend: GameController.php public function joinRoom(Request $request) { $player = [...$validated]; cache()->put("room.{$roomId}.players", $players, 3600); broadcast(new PlayerJoined($roomId, $player, count($players))); return response()->json(['success' => true]); }
// Event: PlayerJoined.php public function broadcastOn() { return [new Channel('game.room.' . $this->roomId)]; }
public function broadcastAs() { return 'player.joined'; }
// Frontend: socket.ts channel.listen('.player.joined', (data: { player, playerCount }) => { setPlayers(prev => [...prev, data.player]); setPlayerCount(data.playerCount); }); 🧠 What We Learned
- WebSocket State Management is Hard Problem: Frontend components unmount/remount during navigation, but WebSocket connections persist globally.
Learning: Created a singleton Echo instance with proper cleanup:
// lib/echo.ts - Singleton pattern let echoInstance: Echo | null = null;
export function getEcho(): Echo { if (!echoInstance) { echoInstance = new Echo({ broadcaster: 'reverb', key: process.env.NEXT_PUBLIC_REVERB_APP_KEY, // ... config }); } return echoInstance; } Key Insight: Global state (WebSocket) + local state (React) = careful lifecycle management needed.
- AI Services Fail... Often Problem: Free image generation APIs have rate limits and downtime. Early demos crashed when Pollinations went down.
Learning: Implemented intelligent fallback hierarchy:
// ImageGenerationService.php public static function generate(string $prompt): string { if (config('services.replicate.token')) { try { return self::generateWithReplicate($prompt); } catch (Exception $e) { Log::warning('Replicate failed, falling back to Pollinations'); } }
return self::generateWithPollinations($prompt);
} Key Insight: Always have a backup plan for external services. Free tier = unreliable tier.
- Real-time Debugging is Painful Problem: With 3 servers, 4+ browser tabs, and WebSocket events flying around, debugging felt like chasing ghosts.
Learning: Built comprehensive logging:
// lib/debug.ts
export function debugLog(context: string, message: string, data?: any) {
console.log([${context}] ${message}, data || '');
}
// Usage throughout codebase debugLog('Socket', 'Player joined event received', { player, roomId }); Created DEBUGGING_GUIDE.md with systematic troubleshooting steps.
Key Insight: Logging infrastructure is not optional in distributed systems—it's essential.
- Environment Variables are Security Risks Problem: We accidentally committed .env.local with a real Gemini API key. GitHub exposed it publicly.
Learning:
Added .env.local to .gitignore Created .env.example templates Regenerated compromised API keys Added security warnings to CLAUDE.md Key Insight: Security isn't an afterthought—it's a habit you build from day one.
- Cache Invalidation is One of the Two Hard Problems Problem: Players leaving abruptly (closed tab, network drop) left "ghost players" in the cache.
Learning:
Set aggressive TTL (1 hour) Implemented manual cleanup on player.left events Added connection heartbeat checks (future enhancement) Key Insight: Phil Karlton was right: "There are only two hard things in Computer Science: cache invalidation and naming things."
- Type Safety Saves Lives Problem: Early in development, we had mismatched payload structures between Laravel events and TypeScript listeners. Bugs were frequent.
Learning: Defined shared TypeScript interfaces:
// types/socket.ts export interface PlayerJoinedPayload { player: Player; playerCount: number; }
export interface GMDescriptionPayload { description: string; }
// Enforced in event handlers channel.listen('.player.joined', (data: PlayerJoinedPayload) => { // TypeScript ensures data.player and data.playerCount exist }); Key Insight: The few minutes spent defining types saves hours of debugging runtime errors.
🚧 Challenges We Faced Challenge 1: The Port 8080/8081 Mystery Problem: Documentation said Reverb runs on port 8080, but connections kept failing.
Root Cause: .env had REVERB_PORT=8081, but our docs/configs referenced 8080.
Solution:
Standardized on 8081 across all configs Created systematic port-checking commands Updated WEBSOCKET_SETUP.md Time Lost: 3 hours of "Why won't this connect?!"
Lesson: Configuration drift is real. Single source of truth > scattered constants.
Challenge 2: CORS Hell with WebSockets Problem: Frontend couldn't connect to Reverb—browser blocked with CORS errors.
Root Cause: Laravel Broadcasting config had restrictive allowed_origins.
Solution:
// config/reverb.php 'allowed_origins' => ['*'], // Allow all origins (dev only!) Time Lost: 2 hours
Lesson: WebSocket CORS is different from HTTP CORS. Read the docs carefully.
Challenge 3: Image Generation Latency Problem: Players submitted guesses → waited 10-15 seconds → lost engagement.
Initial Approach: Synchronous API calls blocked the UI.
Solution:
Async image generation with loading states WebSocket broadcast when image ready Optimistic UI updates (show "Generating..." immediately)
// Optimistic update setPlayerImages(prev => [...prev, { playerId, status: 'generating' }]);
// Listen for completion channel.listen('.image.generated', (data) => { setPlayerImages(prev => prev.map(img => img.playerId === data.playerId ? { ...img, status: 'complete', url: data.imageUrl } : img )); }); Time Saved: Made 15-second wait feel like 2 seconds.
Lesson: Perceived performance > actual performance. Keep users informed.
Challenge 4: Cache Doesn't Persist (And That's Okay) Problem: During testing, we'd restart Laravel and all players disappeared.
Initial Reaction: "We need a database!"
Realization: Game sessions are meant to be ephemeral. Persistence would add unnecessary complexity.
Solution:
Embraced statelessness Added clear warnings in docs Implemented proper cleanup on server restart Lesson: Not every problem needs a database. Match your solution to your requirements.
Challenge 5: Team Coordination Across Frontend/Backend Problem: 6-person team (3 FE, 3 BE) led to API contract mismatches.
Solution:
Created detailed WebSocket event documentation in CLAUDE.md Defined TypeScript interfaces as contracts Used curl commands for testing endpoints independently Implemented debug logging on both sides Lesson: Documentation isn't overhead—it's multiplier. Good docs = faster development.
🎓 Key Takeaways Technical Lessons Real-time systems require different thinking: Event-driven architecture > request-response Observability is infrastructure: Logging, debugging, and monitoring aren't optional Type safety across boundaries: Shared contracts between frontend/backend prevent bugs Graceful degradation: Always have fallbacks for external services Configuration as code: Centralize settings, document thoroughly Team Lessons Communication overhead is real: Clear API contracts reduce back-and-forth Hackathon time is precious: Focus on MVP, ruthlessly cut scope Documentation pays dividends: Future-you will thank present-you Test incrementally: Don't wait until integration to test WebSockets Product Lessons Perceived performance matters: Users forgive slowness if they're informed Real-time is magical: When it works, it delights users AI is a feature, not the product: The game loop matters more than AI quality Multiplayer amplifies fun: Social elements make simple mechanics engaging 🚀 What's Next for AI Nexus Short-term (Post-Hackathon) Authentication: Implement Laravel Sanctum for secure room access Persistent Leaderboards: Track top players across sessions Mobile Responsive: Optimize for tablets and phones Image Comparison AI: Use Gemini Vision to auto-score visual similarity Long-term (Production) Scaling: Redis adapter for Reverb, multi-server deployment Monetization: Premium AI models (DALL-E 3, Midjourney) for paid tiers Advanced Scoring: ML-based semantic similarity scoring Community Features: Public room browser, replays, spectator mode Custom Game Modes: Timed challenges, team battles, tournaments Dream Features Voice Integration: Google Cloud Speech-to-Text for voice commands Procedural Narratives: Gemini generates entire campaign arcs 3D Scene Generation: Text → 3D environments via Google's DreamFusion Cross-platform: Native mobile apps with React Native 🙏 Acknowledgments Built with ❤️ during the Google Hackathon 2026 by a team passionate about pushing the boundaries of multiplayer AI experiences.
Powered By:
Google Gemini API for multimodal AI Laravel & Next.js communities for incredible frameworks Pollinations.ai for free image generation Replicate for premium AI models Every Stack Overflow answer that saved us at 3 AM Special Thanks to the hackathon organizers for creating space for innovation, and to every playtester who endured our bugs with patience and enthusiasm.
📊 Stats Lines of Code: ~8,000+ (Backend: 3,500 | Frontend: 4,500) Development Time: 24 hours (Hackathon) Team Size: 6 developers (3 Frontend, 3 Backend) WebSocket Events: 9 real-time events API Endpoints: 14 endpoints AI Services Integrated: 3 (Gemini, Pollinations, Replicate) Coffee Consumed: Immeasurable ☕ "In the intersection of imagination and technology, we found magic. In the chaos of real-time multiplayer, we found joy. In the challenge of a hackathon, we found ourselves."
— The AI Nexus Team
Built With
- next-js-and-express-js
Log in or sign up for Devpost to join the conversation.