dreamwell-runtime 1.0.0

Dreamwell Runtime — cross-platform GPU-accelerated game client
Documentation
//! Remote authority — SpacetimeDB multiplayer backend.
//!
//! # Status: Structural Stub
//!
//! This module is an **intentional structural stub** for the multiplayer feature.
//! It implements `AuthorityClient` with the same immediate-ack behavior as
//! `LocalAuthority`. It is **not yet connected** to the SpacetimeDB SDK.
//!
//! The purpose of this stub is to:
//! - Establish the correct trait interface (`AuthorityClient`) for remote authority
//! - Enable compile-time verification of the `multiplayer` feature gate
//! - Provide a drop-in replacement point for real SDK integration
//! - Allow integration tests to exercise the remote authority code path
//!
//! # Future Implementation
//!
//! When the SpacetimeDB SDK is wired in, the following changes are needed:
//! - `submit_intents`: Call SpacetimeDB reducers via `DbConnection`
//! - `poll_events`: Drain the subscription event stream from the SDK callback
//! - `request_snapshot`: Initiate a full-state subscription query
//! - `connection_state`: Reflect actual SDK connection lifecycle
//!
//! # Feature Gate
//!
//! This module is only compiled when the `multiplayer` feature is enabled.
//! Without it, `AuthorityMode::Remote` falls back to `LocalAuthority` at runtime.

#![cfg(feature = "multiplayer")]

use super::protocol::{AuthorityClient, AuthorityEvent, ConnectionState};
use dreamwell_fabric::packets::ClientIntent;

/// Remote authority client — SpacetimeDB-backed multiplayer backend.
///
/// **This is an intentional structural stub.** It is not yet connected to the
/// SpacetimeDB SDK. All methods mirror `LocalAuthority` behavior (immediate acks,
/// empty snapshots) to allow the multiplayer code path to compile and run without
/// a live server connection.
///
/// When the SDK is integrated, this struct will hold the `DbConnection` handle
/// and an event buffer populated by subscription callbacks.
pub struct RemoteAuthority {
    state: ConnectionState,
    next_seq: u64,
    pending_events: Vec<AuthorityEvent>,
}

impl RemoteAuthority {
    /// Create a new remote authority in Connecting state.
    pub fn new() -> Self {
        Self {
            state: ConnectionState::Connecting,
            next_seq: 0,
            pending_events: Vec::new(),
        }
    }

    /// Simulate a successful connection (for testing).
    /// Real implementation will be driven by SDK connection callbacks.
    pub fn simulate_connected(&mut self) {
        self.state = ConnectionState::Connected;
    }
}

impl Default for RemoteAuthority {
    fn default() -> Self {
        Self::new()
    }
}

impl AuthorityClient for RemoteAuthority {
    fn submit_intents(&mut self, intents: &[ClientIntent]) {
        // Stub: immediate ack, same as LocalAuthority.
        // Real implementation: call SpacetimeDB reducers.
        for _intent in intents {
            self.pending_events.push(AuthorityEvent::Ack { seq: self.next_seq });
            self.next_seq += 1;
        }
    }

    fn poll_events(&mut self, out: &mut Vec<AuthorityEvent>) {
        out.append(&mut self.pending_events);
    }

    fn request_snapshot(&mut self) {
        // Stub: empty snapshot.
        // Real implementation: request full state via SpacetimeDB subscription.
        self.pending_events.push(AuthorityEvent::SnapshotChunk {
            chunk_id: 0,
            total_chunks: 1,
            data: Vec::new(),
        });
    }

    fn connection_state(&self) -> ConnectionState {
        self.state
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::authority::protocol::ConnectionState;

    #[test]
    fn starts_connecting() {
        let ra = RemoteAuthority::new();
        assert_eq!(ra.connection_state(), ConnectionState::Connecting);
    }

    #[test]
    fn simulate_connected() {
        let mut ra = RemoteAuthority::new();
        ra.simulate_connected();
        assert_eq!(ra.connection_state(), ConnectionState::Connected);
    }

    #[test]
    fn submit_and_poll() {
        let mut ra = RemoteAuthority::new();
        ra.submit_intents(&[ClientIntent {
            actor_id: 1,
            tick_hint: 0,
            action: dreamwell_fabric::packets::IntentKind::Move { dx: 1, dy: 0 },
        }]);
        let mut events = Vec::new();
        ra.poll_events(&mut events);
        assert_eq!(events.len(), 1);
        assert!(matches!(events[0], AuthorityEvent::Ack { seq: 0 }));
    }

    #[test]
    fn request_snapshot_returns_chunk() {
        let mut ra = RemoteAuthority::new();
        ra.request_snapshot();
        let mut events = Vec::new();
        ra.poll_events(&mut events);
        assert_eq!(events.len(), 1);
        assert!(matches!(
            events[0],
            AuthorityEvent::SnapshotChunk {
                chunk_id: 0,
                total_chunks: 1,
                ..
            }
        ));
    }
}