10 KiB
Server-Client Model Compatibility Assessment
Executive Summary
The current refactoring is HIGHLY POSITIVE for server-client migration. The new architecture requires only minimal changes to transition to lazy-loaded server-side room data. The separation of Tiled visual structure from scenario game logic is perfectly aligned with a remote data model.
Current Architecture (Post-Refactoring)
Data Flow
1. Preload Phase:
- Load ALL Tiled map JSONs (room_reception2.json, room_office2.json, etc.)
- Load ALL image assets
- Load entire scenario JSON (ceo_exfil.json with all rooms)
2. Create Phase:
- Initialize rooms system
- Calculate room positions for lazy loading
3. Runtime - When room is unlocked:
loadRoom(roomId)
→ Fetch roomData from window.gameScenario.rooms[roomId]
→ Call createRoom(roomId, roomData, position)
Key Components
Visual Layer (Tiled):
- Tiled JSON files (room_reception2.json)
- Static sprite positions and layers
- Loaded at game start
- Independent of room unlock state
Game Logic Layer (Scenario):
- Scenario JSON (ceo_exfil.json)
- Object properties (locked, contents, takeable, etc.)
- Currently loaded entirely at game start
- Used during createRoom() to populate loaded rooms
Analysis: Server-Client Compatibility
✅ POSITIVE FACTORS
1. Clean Separation of Concerns
The refactoring cleanly separates:
- Visual Structure (Tiled): Room layouts, sprite positions, layers
- Game Logic (Scenario): Object properties, interactions, contents
This is IDEAL for server-client because:
- Client loads Tiled visuals once at startup
- Server sends game logic data on-demand
- No coupling between visual structure and dynamic data
2. TiledItemPool Architecture
Current approach:
const itemPool = new TiledItemPool(objectsByLayer, map);
usedItem = itemPool.findMatchFor(scenarioObj);
sprite = createSpriteFromMatch(usedItem, scenarioObj, position, roomId, index, map);
Why this is perfect for server-client:
- ✅ Item pool operates entirely on LOCAL Tiled data (already loaded)
- ✅ Matching logic doesn't depend on ALL scenario objects being present
- ✅ Can match objects incrementally as server sends scenario data
- ✅ Matching is deterministic: same Tiled item + same scenario object = same result
3. Lazy Loading Already Exists
function loadRoom(roomId) {
const roomData = gameScenario.rooms[roomId];
createRoom(roomId, roomData, position);
}
Current implementation:
- Rooms are lazy-loaded when approached
- Only createRoom() is called at runtime
- Perfect hook point for fetching server data
4. Modular Helper Functions
All room loading logic is cleanly separated:
applyTiledProperties()- works with Tiled data onlyapplyScenarioProperties()- works with scenario data onlycreateSpriteFromMatch()- combines both data sourcessetDepthAndStore()- finalizes sprite in game world
These can be called incrementally as data arrives.
5. No Hard Coupling to Preload
// Current: gameScenario loaded at preload
this.load.json('gameScenarioJSON', scenarioFile);
// But: Only accessed when loadRoom() is called
const roomData = gameScenario.rooms[roomId];
This single point of access (loadRoom) is easily replaceable with a server call.
Migration Path: Current → Server-Client Model
Phase 1: Minimal Changes (1-2 hours)
Modify loadRoom() to fetch from server:
async function loadRoom(roomId) {
const position = window.roomPositions[roomId];
if (!position) {
console.error(`Cannot load room ${roomId}: missing position`);
return;
}
// CHANGE: Fetch room data from server instead of window.gameScenario
let roomData;
try {
const response = await fetch(`/api/rooms/${roomId}`, {
headers: {
'Authorization': `Bearer ${window.playerToken}`
}
});
roomData = await response.json();
} catch (error) {
console.error(`Failed to fetch room ${roomId}:`, error);
return;
}
console.log(`Lazy loading room: ${roomId}`);
createRoom(roomId, roomData, position);
revealRoom(roomId);
}
Remove preloading of scenario JSON:
// OLD (in game.js preload):
this.load.json('gameScenarioJSON', scenarioFile);
// NEW: Not needed - will fetch on demand
// This reduces initial load time significantly
Keep preloading Tiled files:
// KEEP these - they're visual structure, not gameplay logic
this.load.tilemapTiledJSON('room_reception', 'assets/rooms/room_reception2.json');
this.load.tilemapTiledJSON('room_office', 'assets/rooms/room_office2.json');
// ... etc
Phase 2: Initial Inventory Items (1 hour)
Update processInitialInventoryItems():
// OLD: Loop through window.gameScenario.rooms
// NEW: Need to fetch initial room data before processing
export async function processInitialInventoryItems() {
console.log('Processing initial inventory items');
// Option A: Fetch only the starting room
const startRoomId = window.gameScenario.startRoom;
const response = await fetch(`/api/rooms/${startRoomId}`);
const roomData = await response.json();
if (roomData.objects && Array.isArray(roomData.objects)) {
roomData.objects.forEach(obj => {
if (obj.inInventory === true) {
const inventoryItem = createInventorySprite(obj);
if (inventoryItem) {
addToInventory(inventoryItem);
}
}
});
}
}
Keep scenario bootstrap data:
// In create() or early initialization, fetch minimal data
const response = await fetch('/api/scenario/metadata');
window.gameScenario = await response.json();
// This should contain: { startRoom, scenarioName, briefDescription, ... }
// But NOT the full room data
What DOESN'T Need to Change
✅ Tiled Map Loading (Already Perfect)
- Tiled files stay local
- No server interaction needed
- Massive visual content loaded once at startup
- No change required
✅ Room Positioning System
const position = window.roomPositions[roomId];
- This is calculated from Tiled file dimensions
- Works identically with server-side scenario data
- Zero changes needed
✅ Sprite Creation Logic
TiledItemPool.findMatchFor()- works identicallycreateSpriteFromMatch()- works identicallyapplyScenarioProperties()- works identically- Only data source changes (server instead of preloaded JSON)
✅ All Interaction Systems
- Inventory system
- Lock/key system
- Container system
- Minigames
- All operate on sprite properties that come from scenario data
- Work identically once sprite is populated
Key Benefits of Current Architecture for Server-Client
1. Bandwidth Optimization
- Tiled files (~500KB) loaded once
- Room scenario data loaded on-demand
- Average room: ~10-20KB
- vs. Loading entire scenario upfront: ~100-200KB
2. Security Improvement
- Server doesn't send room data until unlocked
- Can't explore entire scenario from network tab
- Dynamic content validation server-side
3. Scalability
- Can add rooms without affecting startup time
- Scenario data can be regenerated/updated server-side
- Client remains thin, server-driven
4. Flexibility
- Server can send different room data based on:
- Player progression
- Difficulty level
- Custom rules
- Time limits
- Client doesn't need to know about these rules
Implementation Checklist
Required Changes:
- Modify
loadRoom()to fetch from/api/rooms/{roomId} - Remove
this.load.json('gameScenarioJSON', scenarioFile)from preload - Create minimal scenario bootstrap endpoint
- Update
processInitialInventoryItems()to fetch start room - Add authorization headers to server calls
- Handle network errors gracefully
No Changes Needed:
- TiledItemPool matching logic
- Sprite creation (createSpriteFromMatch, etc.)
- Interaction systems
- Inventory system
- All minigames
- Tiled file loading
- Room positioning calculations
Testing Points:
- Rooms load when approached
- Properties match scenario data
- Interactions work correctly
- Initial inventory populates correctly
- No race conditions with concurrent room loads
- Network errors handled gracefully
Potential Challenges & Solutions
Challenge 1: Race Conditions
Problem: What if player approaches multiple rooms before first request completes?
Solution:
const loadingRooms = new Set();
async function loadRoom(roomId) {
if (loadingRooms.has(roomId)) {
return; // Already loading
}
loadingRooms.add(roomId);
try {
// ... load from server
} finally {
loadingRooms.delete(roomId);
}
}
Challenge 2: Network Failures
Problem: Server unreachable when player enters room?
Solution:
- Show loading indicator
- Implement retry logic with exponential backoff
- Fallback to cached data if available
- Show error modal if all retries fail
Challenge 3: Latency
Problem: Noticeable delay loading room?
Solution:
- Prefetch adjacent room data in background
- Show smooth transition/animation during load
- Cache room data client-side (IndexedDB)
Conclusion
This refactoring is EXCELLENT preparation for server-client migration.
The clean separation of visual structure (Tiled) from game logic (Scenario) means:
- ✅ Can migrate with minimal code changes
- ✅ Existing TiledItemPool architecture works perfectly
- ✅ All existing interaction systems continue to work
- ✅ Only data source changes (preloaded JSON → server API)
Recommended approach:
- Keep current architecture as-is
- When ready for server migration:
- Change
loadRoom()to fetch from API - Update initial inventory loading
- Add authentication/authorization
- That's it - everything else works
- Change
Estimated migration time: 4-6 hours of development