Commit
This commit is contained in:
418
old/OPTIMIZATION_STRATEGY.md
Normal file
418
old/OPTIMIZATION_STRATEGY.md
Normal file
@@ -0,0 +1,418 @@
|
||||
# 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?
|
||||
Reference in New Issue
Block a user