Files
eagle0/docs/SHARDOK_LATENCY_HIDING.md
T
66efefc8d6 Replace Hetzner plan doc with latency hiding strategies (#5053)
The Hetzner on-demand compute implementation is complete:
- ARM64 builds working in CI
- Shardok running on Hetzner CAX41 in Helsinki
- Production Eagle connected via TLS + token auth
- IPv6/NAT64 networking configured

Delete the completed planning doc and add a new doc covering
future latency optimization strategies:
1. Client-side animation masking (recommended first step)
2. Split Shardok architecture (future if needed)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 13:37:53 -08:00

6.7 KiB
Raw Blame History

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

// 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

// 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