# Shardok Latency Hiding Strategies ## Problem Statement With Shardok running on Hetzner (Helsinki) and Eagle on DigitalOcean (US), the round-trip latency for human commands is ~200-400ms: ``` Human posts command: Unity → Eagle (DO) → Shardok (Hetzner) → Eagle (DO) → Unity ~10ms ~100ms ~100ms ~10ms Total: ~220ms round-trip ``` This latency is acceptable for AI turns (users watch animations anyway), but creates noticeable lag when humans post commands. --- ## Strategy 1: Client-Side Animation Masking ### Concept Start animations immediately when the user clicks, before server confirmation arrives. The animation duration masks the network latency. ### Implementation by Command Type **Movement Commands** (deterministic): - Client knows the destination hex and movement path - Start movement animation immediately on click - Server confirms the move (should always match) - If server rejects (invalid state), snap unit back to origin **Attack Commands** (RNG-dependent): - Show attack animation immediately (unit swings sword, fires arrow) - Wait for server to return dice roll result - Show damage numbers / hit effects when server responds - Animation typically takes 300-500ms, masking most of the latency **End Turn**: - Latency not noticeable (user expects transition delay) ### Unity Implementation Sketch ```csharp // In CommandHandler.cs public void OnCommandSelected(Command command) { // Start animation immediately if (command.Type == CommandType.Move) { unitController.StartMoveAnimation(command.TargetHex); } else if (command.Type == CommandType.Attack) { unitController.StartAttackAnimation(command.TargetUnit); } // Send to server in parallel connection.SendCommand(command, (response) => { if (response.Success) { // Animation continues, apply result ApplyCommandResult(response); } else { // Rollback animation unitController.CancelAnimation(); ShowError(response.ErrorMessage); } }); } ``` ### Pros - Simple implementation - No server-side changes - Works with existing architecture ### Cons - Doesn't eliminate latency for attacks with RNG (must wait for dice roll) - Rollback needed if server rejects command (rare but possible) ### Estimated Improvement - Movement: ~200ms latency hidden (feels instant) - Attacks: ~100-200ms hidden by animation, ~100ms visible wait for dice result --- ## Strategy 2: Split Shardok Architecture ### Concept Run two Shardok instances: - **Shardok-Primary (DigitalOcean)**: Handles command processing, source of truth - **Shardok-AI (Hetzner)**: AI computation only Human commands go to the nearby Primary for low latency. AI computation uses the powerful Hetzner instance. ### Architecture ``` Human commands (low latency ~20ms) Unity ←→ Eagle ←→ Shardok-Primary (DigitalOcean) ↓ state sync (when AI turn starts) Shardok-AI (Hetzner) ↑ AI command response ``` ### How It Works **Human Turn:** 1. Human posts command → Eagle → Shardok-Primary (DO) 2. Primary processes command immediately (~10ms local) 3. Primary streams result to client via Eagle (~10ms) 4. **Total latency: ~20ms** (vs ~220ms current) **AI Turn:** 1. When AI's turn starts, Primary sends game state snapshot to Hetzner 2. Shardok-AI computes best command using full CPU power 3. Shardok-AI returns command index to Primary 4. Primary executes command locally and streams to client 5. Repeat until AI turn ends ### Protocol Changes ```protobuf // New service for AI-only computation service ShardokAIService { // Send game state, receive AI's chosen command rpc GetAICommand(AICommandRequest) returns (AICommandResponse); } message AICommandRequest { bytes game_state = 1; // Serialized game state int32 player_id = 2; // Which AI player repeated bytes available_commands = 3; // Available command descriptors } message AICommandResponse { int32 command_index = 1; // Index into available_commands int32 search_depth = 2; // For debugging double best_score = 3; // For debugging } ``` ### Shardok-Primary Requirements Shardok-Primary on DigitalOcean needs to: - Process all commands (human and AI) - Maintain authoritative game state - Serialize/deserialize game state for AI requests - Run on minimal CPU (command processing is fast) This is essentially the current Shardok, but without running the AI search. ### Shardok-AI Requirements Shardok-AI on Hetzner needs to: - Receive game state snapshots - Run AI evaluation (IterativeDeepeningAI or MCTS) - Return best command index - No persistent state (stateless worker) ### AI Turn Latency Each AI command has ~200ms network latency. This is acceptable because: 1. User is watching animations anyway 2. Natural pacing lets user observe AI decisions 3. AI computation is fast on Hetzner's 16 cores For a typical AI turn with 5 commands: 5 × 200ms = 1 second network overhead, plus AI thinking time. With animations, this feels natural. ### Implementation Phases **Phase 1: Add Shardok-Primary (minimal)** - Deploy existing Shardok container to DigitalOcean - Configure Eagle to use local Shardok for all commands - Human latency immediately improves **Phase 2: Add AI offload** - Implement `ShardokAIService` RPC - Shardok-Primary calls Hetzner for AI commands - Shardok-AI processes requests statelessly **Phase 3: Optimize** - Batch multiple AI actions if possible - Pre-warm Shardok-AI connection - Add fallback if Hetzner unavailable ### Pros - Human command latency drops from ~220ms to ~20ms - AI still gets Hetzner's CPU power - Clear separation of concerns - Shardok-Primary can fall back to local AI if Hetzner unavailable ### Cons - Two Shardok instances to maintain - State serialization overhead for AI requests - Each AI action has network round-trip (acceptable with animations) --- ## Comparison | Approach | Human Latency | AI Throughput | Complexity | Changes Required | |----------|---------------|---------------|------------|------------------| | Current | ~220ms | High | - | - | | Animation masking | ~220ms (perceived ~50ms) | High | Low | Unity only | | Split architecture | ~20ms | High | Medium | New RPC, two deployments | --- ## Recommendation **Phase 1 (now)**: Implement animation masking in Unity client - Quick win, no server changes - Improves perceived latency significantly for movement - Attacks still show dice animation while waiting **Phase 2 (future)**: Split architecture if animation masking insufficient - Only needed if users complain about attack latency - More complex but provides true low latency - Natural evolution of current architecture