419 lines
12 KiB
Markdown
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?
|