# Game Optimization Strategy ## Current Performance Analysis ### Polling Overhead (Every 5 seconds) Current polling fetches **5 endpoints simultaneously**: 1. `/api/game/state` - ~1.3KB+ (inventory, equipment, dropped items, player data) 2. `/api/game/location` - Location data 3. `/api/game/profile` - Player profile 4. `/api/game/combat` - Combat status 5. `/api/game/pvp/status` - PvP combat status **Problems:** - Inventory sent every poll (~1.3KB) even though it rarely changes - Equipment sent every poll even though it rarely changes - Dropped items fetched every poll even though location doesn't change often - Duplicate data across endpoints (player data in multiple responses) --- ## Optimization Strategy ### Phase 1: Smart Polling - Only Fetch What Changes #### A. **Lightweight Polling Endpoint** (Every 5s) Create `/api/game/state/minimal` that returns ONLY data that changes frequently: ```json { "player": { "hp": 100, "stamina": 95, "location_id": "ruins_entrance", "movement_cooldown": 2 }, "in_combat": false, "in_pvp_combat": false, "location_changed": false, // Flag if location changed "inventory_changed": false, // Flag if inventory changed "equipment_changed": false, // Flag if equipment changed "last_update_timestamp": 1699380123.456 } ``` **Size estimate:** ~200 bytes vs current 1.5KB+ = **87% reduction** #### B. **On-Demand Fetching** Only fetch full data when flags indicate changes or after specific actions: **Inventory** - Fetch only when: - `inventory_changed` flag is true - After pickup/drop/craft/use actions - After equipment changes - On initial load **Equipment** - Fetch only when: - `equipment_changed` flag is true - After equip/unequip actions - On initial load **Location Details** - Fetch only when: - `location_changed` flag is true - After movement - On initial load **Dropped Items** - Fetch only when: - Location changes - After dropping items - After picking up items #### C. **Change Tracking in Database** Add columns to `players` table: ```sql ALTER TABLE players ADD COLUMN inventory_version INTEGER DEFAULT 0; ALTER TABLE players ADD COLUMN equipment_version INTEGER DEFAULT 0; ALTER TABLE players ADD COLUMN last_state_change FLOAT DEFAULT 0; ``` Increment versions when: - Inventory changes: `inventory_version++` - Equipment changes: `equipment_version++` Frontend caches versions and only re-fetches if version changed. --- ### Phase 2: Delta Updates (Advanced) **Pros:** - Even smaller payload (only changes) - Extremely efficient for large inventories **Cons:** - More complex implementation - Need to handle edge cases (out-of-sync states) - Need full refresh mechanism as fallback **Verdict:** **Not recommended initially** - Current optimization (Phase 1) will give 85%+ bandwidth reduction - Delta updates add complexity for diminishing returns - Only consider if player base grows significantly (10k+ concurrent users) --- ### Phase 3: WebSocket for Real-Time Updates (Future) Instead of polling, use WebSocket for: - Push notifications when state changes - Real-time PvP combat updates - Instant location updates from other players - Server broadcasts (events, announcements) **Benefits:** - Zero polling overhead - True real-time experience - Server can push updates only when needed **Implementation complexity:** Medium-High --- ## Immediate Action Plan ### 1. Create Minimal State Endpoint (1-2 hours) **File:** `api/main.py` ```python @app.get("/api/game/state/minimal") async def get_minimal_state(current_user: dict = Depends(get_current_user)): """Lightweight endpoint for polling - returns only frequently changing data""" player = await db.get_player_by_id(current_user['id']) # Calculate movement cooldown import time current_time = time.time() last_movement = player.get('last_movement_time', 0) movement_cooldown = max(0, 5 - (current_time - last_movement)) # Check if data changed since last poll # Client sends last known versions, server returns flags inventory_version = player.get('inventory_version', 0) equipment_version = player.get('equipment_version', 0) return { "player": { "hp": player['hp'], "max_hp": player['max_hp'], "stamina": player['stamina'], "max_stamina": player['max_stamina'], "location_id": player['location_id'], "movement_cooldown": int(movement_cooldown), "is_dead": player.get('is_dead', False) }, "versions": { "inventory": inventory_version, "equipment": equipment_version }, "timestamp": current_time } ``` ### 2. Update Frontend Polling Logic (2-3 hours) **File:** `pwa/src/components/Game.tsx` ```typescript // Cache for full data const [cachedInventory, setCachedInventory] = useState(null) const [cachedEquipment, setCachedEquipment] = useState(null) const [lastVersions, setLastVersions] = useState({ inventory: 0, equipment: 0 }) const fetchMinimalState = async () => { const res = await api.get('/api/game/state/minimal') // Update HP/Stamina/Location immediately setPlayerState(prev => ({ ...prev, health: res.data.player.hp, stamina: res.data.player.stamina, location_id: res.data.player.location_id })) // Check if inventory changed if (res.data.versions.inventory !== lastVersions.inventory) { await fetchFullInventory() setLastVersions(prev => ({ ...prev, inventory: res.data.versions.inventory })) } // Check if equipment changed if (res.data.versions.equipment !== lastVersions.equipment) { await fetchFullEquipment() setLastVersions(prev => ({ ...prev, equipment: res.data.versions.equipment })) } } ``` ### 3. Add Version Tracking to Database Functions (1 hour) Update these functions to increment versions: - `add_item_to_inventory()` - increment `inventory_version` - `remove_inventory_item()` - increment `inventory_version` - `equip_item()` - increment `equipment_version` - `unequip_item()` - increment `equipment_version` ### 4. Keep Full State Fetch for Actions (Already done!) After actions (pickup, craft, equip, etc.), fetch full data: ```typescript await handlePickup(itemId) await fetchFullInventory() // Refresh immediately after action ``` --- ## Expected Performance Gains ### Bandwidth Reduction - **Current:** 1.5KB every 5s = **18KB/minute** per player - **Optimized:** 200 bytes every 5s = **2.4KB/minute** per player - **Savings:** **87% reduction** in polling bandwidth ### Server Load Reduction - **Database queries per poll:** - Current: 8-12 queries (inventory items, equipment, dropped items, etc.) - Optimized: 1 query (player state only) - **CPU usage:** ~80% reduction per poll - **Memory:** Significant reduction from not loading/serializing inventory every poll ### Scalability - **Current:** ~100 concurrent users max - **Optimized:** ~800+ concurrent users with same resources --- ## Steam Integration & Monetization ### Can you release on Steam? **YES!** Your game architecture is perfect for Steam: - Progressive Web App can be packaged as desktop app - Electron or Tauri wrapper around your PWA - Steamworks SDK integration is straightforward ### Integration Steps #### 1. **Package as Desktop App** (Choose one) **Option A: Electron (Most common)** ```json { "name": "echoes-of-the-ashes", "main": "main.js", "dependencies": { "electron": "^27.0.0" } } ``` **Option B: Tauri (More efficient, Rust-based)** - Smaller bundle size - Better performance - Rust backend integration #### 2. **Integrate Steamworks** ```typescript // Steam initialization const steamworks = require('steamworks.js') // Initialize Steam client if (steamworks.init()) { const steamId = steamworks.localplayer.getSteamId() const username = steamworks.localplayer.getName() // Use Steam ID for authentication await api.post('/api/auth/steam', { steamId, username, ticket: steamworks.auth.getSessionTicket() }) } ``` #### 3. **Two-Version Strategy (Free vs Premium)** **Database Schema:** ```sql ALTER TABLE players ADD COLUMN account_type VARCHAR(20) DEFAULT 'free'; -- Values: 'free', 'steam_premium', 'web_premium' ALTER TABLE players ADD COLUMN steam_id VARCHAR(50) UNIQUE; ALTER TABLE players ADD COLUMN premium_expires_at FLOAT; ``` **Backend Restrictions:** ```python @app.post("/api/game/move") async def move(req, current_user): player = await db.get_player_by_id(current_user['id']) # Check premium restrictions if player['account_type'] == 'free': if player['level'] >= 3: raise HTTPException( status_code=402, detail="Level 3 is the maximum for free accounts. Upgrade to premium to continue!" ) # Check location restrictions if location_id in PREMIUM_LOCATIONS: raise HTTPException( status_code=402, detail="This area is only accessible to premium accounts." ) # Continue with move logic... ``` **Monetization Options:** 1. **Steam Purchase (One-time)** - Buy on Steam = Permanent premium - Price: $9.99 - $19.99 - Steam handles payment, you get 70% 2. **Web Subscription** - Stripe/PayPal integration - $4.99/month or $39.99/year - Separate from Steam version 3. **Hybrid Model** ```python def is_premium(player): # Steam premium (permanent) if player['account_type'] == 'steam_premium': return True # Web premium (subscription) if player['account_type'] == 'web_premium': if player['premium_expires_at'] > time.time(): return True return False ``` ### Steam Features You Can Use 1. **Achievements** ```typescript steamworks.achievement.activate('FIRST_COMBAT_WIN') ``` 2. **Cloud Saves** - Sync player state across devices - Automatic backups 3. **Leaderboards** ```typescript steamworks.leaderboards.uploadScore('PLAYER_LEVEL', player.level) ``` 4. **Friends/Multiplayer** - See Steam friends in-game - Invite to PvP combat - Group features 5. **Workshop** - User-created content - Custom locations/items - Community maps ### Legal Considerations 1. **Steam Agreement** - Need business entity (or individual) to sign Steamworks agreement - Need Tax ID (EIN for business, SSN for individual) 2. **Separate Versions** - ✅ **ALLOWED:** Selling Steam version + separate web subscription - ❌ **NOT ALLOWED:** Forcing Steam users to pay again on web - ✅ **ALLOWED:** Different pricing models - ✅ **ALLOWED:** Web has features Steam doesn't (and vice versa) 3. **Revenue Sharing** - Steam: 30% cut (you get 70%) - Web direct: 100% yours (minus payment processor ~3%) ### Recommended Strategy **Phase 1: Optimize & Test** (Current) - Implement polling optimizations - Test with small player base - Gather feedback **Phase 2: Steam Preparation** (2-4 weeks) - Package as Electron app - Integrate Steamworks SDK - Implement premium restrictions - Add achievements/leaderboards **Phase 3: Soft Launch** (Steam Early Access) - Launch as Early Access ($14.99) - Gather Steam community feedback - Regular updates **Phase 4: Dual Platform** (After Steam is stable) - Keep Steam version - Launch web version with subscription - Cross-platform support (shared servers) --- ## Priority Order 1. **✅ Immediate:** Polling optimization (biggest impact, low effort) 2. **⏳ Short-term:** Premium restrictions system (prep for monetization) 3. **⏳ Medium-term:** Steam integration (packaging + Steamworks) 4. **🔮 Long-term:** WebSocket real-time updates (if needed) Would you like me to start implementing the polling optimization now?