Files
echoes-of-the-ash/old/OPTIMIZATION_STRATEGY.md
2025-11-27 16:27:01 +01:00

419 lines
12 KiB
Markdown

# 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?