Skip to main content

proof_engine/editor/
network_editor.rs

1
2//! Network editor — visual multiplayer graph, RPC system, replication settings,
3//! lag simulation, bandwidth monitor, session browser, and lobby management.
4
5use glam::{Vec2, Vec3, Vec4};
6use std::collections::HashMap;
7
8// ---------------------------------------------------------------------------
9// Network topology
10// ---------------------------------------------------------------------------
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum NetworkTopology {
14    ClientServer,
15    PeerToPeer,
16    Dedicated,
17    ListenServer,
18    Relay,
19    Hybrid,
20}
21
22impl NetworkTopology {
23    pub fn label(&self) -> &'static str {
24        match self {
25            Self::ClientServer => "Client-Server",
26            Self::PeerToPeer => "Peer-to-Peer",
27            Self::Dedicated => "Dedicated Server",
28            Self::ListenServer => "Listen Server",
29            Self::Relay => "Relay",
30            Self::Hybrid => "Hybrid",
31        }
32    }
33    pub fn description(&self) -> &'static str {
34        match self {
35            Self::ClientServer => "Authoritative server with multiple clients",
36            Self::PeerToPeer => "Direct peer connections, no central server",
37            Self::Dedicated => "Headless dedicated server process",
38            Self::ListenServer => "One client acts as host",
39            Self::Relay => "All traffic routed through relay server",
40            Self::Hybrid => "Mix of server-authority and P2P for low-latency data",
41        }
42    }
43}
44
45// ---------------------------------------------------------------------------
46// Replication
47// ---------------------------------------------------------------------------
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum ReplicationMode {
51    Unreliable,
52    Reliable,
53    ReliableOrdered,
54    ReliableSequenced,
55    Multicast,
56    OwnerOnly,
57    ServerOnly,
58    Skip,
59}
60
61impl ReplicationMode {
62    pub fn label(&self) -> &'static str {
63        match self {
64            Self::Unreliable => "Unreliable",
65            Self::Reliable => "Reliable",
66            Self::ReliableOrdered => "Reliable Ordered",
67            Self::ReliableSequenced => "Reliable Sequenced",
68            Self::Multicast => "Multicast",
69            Self::OwnerOnly => "Owner Only",
70            Self::ServerOnly => "Server Only",
71            Self::Skip => "No Replication",
72        }
73    }
74
75    pub fn uses_sequence_numbers(&self) -> bool {
76        matches!(self, Self::ReliableOrdered | Self::ReliableSequenced)
77    }
78
79    pub fn is_reliable(&self) -> bool {
80        matches!(self, Self::Reliable | Self::ReliableOrdered | Self::ReliableSequenced)
81    }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub enum ReplicatedPropertyType {
86    Bool,
87    Byte,
88    Int32,
89    Int64,
90    Float,
91    Double,
92    Vector3,
93    Quaternion,
94    String,
95    Transform,
96    Color,
97    ObjectRef,
98    Custom,
99}
100
101impl ReplicatedPropertyType {
102    pub fn label(&self) -> &'static str {
103        match self {
104            Self::Bool => "bool",
105            Self::Byte => "byte",
106            Self::Int32 => "int32",
107            Self::Int64 => "int64",
108            Self::Float => "float",
109            Self::Double => "double",
110            Self::Vector3 => "Vector3",
111            Self::Quaternion => "Quaternion",
112            Self::String => "string",
113            Self::Transform => "Transform",
114            Self::Color => "Color",
115            Self::ObjectRef => "Object Reference",
116            Self::Custom => "Custom",
117        }
118    }
119
120    pub fn size_bytes(&self) -> usize {
121        match self {
122            Self::Bool | Self::Byte => 1,
123            Self::Int32 | Self::Float => 4,
124            Self::Int64 | Self::Double => 8,
125            Self::Vector3 => 12,
126            Self::Quaternion | Self::Color | Self::Transform => 16,
127            Self::String => 64, // estimate
128            Self::ObjectRef => 8,
129            Self::Custom => 0,
130        }
131    }
132}
133
134#[derive(Debug, Clone)]
135pub struct ReplicatedProperty {
136    pub name: String,
137    pub prop_type: ReplicatedPropertyType,
138    pub replication_mode: ReplicationMode,
139    pub update_rate_hz: f32,
140    pub send_on_change_only: bool,
141    pub priority: f32,
142    pub relevancy_distance: f32,
143    pub use_delta_compression: bool,
144    pub custom_serializer: bool,
145    pub size_bytes: usize,
146    pub dirty: bool,
147    pub last_sent_frame: u64,
148}
149
150impl ReplicatedProperty {
151    pub fn new(name: &str, prop_type: ReplicatedPropertyType) -> Self {
152        let size_bytes = prop_type.size_bytes();
153        Self {
154            name: name.to_string(),
155            prop_type,
156            replication_mode: ReplicationMode::Unreliable,
157            update_rate_hz: 30.0,
158            send_on_change_only: true,
159            priority: 1.0,
160            relevancy_distance: 0.0,
161            use_delta_compression: false,
162            custom_serializer: false,
163            size_bytes,
164            dirty: false,
165            last_sent_frame: 0,
166        }
167    }
168
169    pub fn bits_per_second(&self) -> f32 {
170        self.size_bytes as f32 * 8.0 * self.update_rate_hz
171    }
172}
173
174// ---------------------------------------------------------------------------
175// RPC system
176// ---------------------------------------------------------------------------
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
179pub enum RpcTarget {
180    Server,
181    AllClients,
182    AllClientsIncludingSelf,
183    OwnerClient,
184    MulticastReliable,
185    MulticastUnreliable,
186}
187
188impl RpcTarget {
189    pub fn label(&self) -> &'static str {
190        match self {
191            Self::Server => "Server",
192            Self::AllClients => "All Clients",
193            Self::AllClientsIncludingSelf => "All Clients (incl. Self)",
194            Self::OwnerClient => "Owner Client",
195            Self::MulticastReliable => "Multicast Reliable",
196            Self::MulticastUnreliable => "Multicast Unreliable",
197        }
198    }
199}
200
201#[derive(Debug, Clone)]
202pub struct RpcParam {
203    pub name: String,
204    pub param_type: ReplicatedPropertyType,
205}
206
207#[derive(Debug, Clone)]
208pub struct RpcDefinition {
209    pub name: String,
210    pub target: RpcTarget,
211    pub params: Vec<RpcParam>,
212    pub reliable: bool,
213    pub requires_authority: bool,
214    pub rate_limit_hz: f32,
215    pub estimated_bytes: usize,
216}
217
218impl RpcDefinition {
219    pub fn new(name: &str, target: RpcTarget) -> Self {
220        Self {
221            name: name.to_string(),
222            target,
223            params: Vec::new(),
224            reliable: true,
225            requires_authority: false,
226            rate_limit_hz: 0.0,
227            estimated_bytes: 8, // name + overhead
228        }
229    }
230
231    pub fn add_param(mut self, name: &str, param_type: ReplicatedPropertyType) -> Self {
232        self.estimated_bytes += param_type.size_bytes();
233        self.params.push(RpcParam { name: name.to_string(), param_type });
234        self
235    }
236
237    pub fn call_signature(&self) -> String {
238        let params: Vec<String> = self.params.iter().map(|p| format!("{}: {}", p.name, p.param_type.label())).collect();
239        format!("{}({}) -> {}", self.name, params.join(", "), self.target.label())
240    }
241}
242
243// ---------------------------------------------------------------------------
244// Replicated actor definition
245// ---------------------------------------------------------------------------
246
247#[derive(Debug, Clone)]
248pub struct ReplicatedActor {
249    pub class_name: String,
250    pub properties: Vec<ReplicatedProperty>,
251    pub rpcs: Vec<RpcDefinition>,
252    pub net_update_frequency: f32,
253    pub net_cull_distance_squared: f32,
254    pub bnet_use_owner_relevancy: bool,
255    pub always_relevant: bool,
256    pub relevancy_method: RelevancyMethod,
257    pub bandwidth_bytes_per_sec: f32,
258    pub priority: f32,
259}
260
261#[derive(Debug, Clone, Copy, PartialEq)]
262pub enum RelevancyMethod {
263    Distance,
264    AlwaysRelevant,
265    OwnerAndViewer,
266    TeammatesOnly,
267    Custom,
268}
269
270impl RelevancyMethod {
271    pub fn label(&self) -> &'static str {
272        match self {
273            Self::Distance => "Distance",
274            Self::AlwaysRelevant => "Always Relevant",
275            Self::OwnerAndViewer => "Owner & Viewer",
276            Self::TeammatesOnly => "Teammates Only",
277            Self::Custom => "Custom",
278        }
279    }
280}
281
282impl ReplicatedActor {
283    pub fn new(class_name: &str) -> Self {
284        Self {
285            class_name: class_name.to_string(),
286            properties: Vec::new(),
287            rpcs: Vec::new(),
288            net_update_frequency: 30.0,
289            net_cull_distance_squared: 150.0 * 150.0,
290            bnet_use_owner_relevancy: false,
291            always_relevant: false,
292            relevancy_method: RelevancyMethod::Distance,
293            bandwidth_bytes_per_sec: 0.0,
294            priority: 1.0,
295        }
296    }
297
298    pub fn with_property(mut self, prop: ReplicatedProperty) -> Self {
299        self.bandwidth_bytes_per_sec += prop.bits_per_second() / 8.0;
300        self.properties.push(prop);
301        self
302    }
303
304    pub fn with_rpc(mut self, rpc: RpcDefinition) -> Self {
305        self.rpcs.push(rpc);
306        self
307    }
308
309    pub fn total_bandwidth_bps(&self) -> f32 {
310        self.properties.iter().map(|p| p.bits_per_second()).sum()
311    }
312
313    pub fn generate_replication_code(&self) -> String {
314        let mut lines = Vec::new();
315        lines.push(format!("// Replicated properties for {}", self.class_name));
316        for prop in &self.properties {
317            lines.push(format!("// [Replicated({:?})] pub {}: {},", prop.replication_mode, prop.name, prop.prop_type.label()));
318        }
319        lines.push(String::new());
320        lines.push("// RPCs:".to_string());
321        for rpc in &self.rpcs {
322            lines.push(format!("// [RPC({:?})] fn {};", rpc.target, rpc.call_signature()));
323        }
324        lines.join("\n")
325    }
326
327    pub fn player_actor() -> Self {
328        Self::new("PlayerController")
329            .with_property({
330                let mut p = ReplicatedProperty::new("position", ReplicatedPropertyType::Vector3);
331                p.update_rate_hz = 60.0;
332                p.use_delta_compression = true;
333                p
334            })
335            .with_property({
336                let mut p = ReplicatedProperty::new("rotation", ReplicatedPropertyType::Quaternion);
337                p.update_rate_hz = 60.0;
338                p
339            })
340            .with_property({
341                let mut p = ReplicatedProperty::new("health", ReplicatedPropertyType::Float);
342                p.replication_mode = ReplicationMode::Reliable;
343                p.send_on_change_only = true;
344                p
345            })
346            .with_property({
347                let mut p = ReplicatedProperty::new("ammo", ReplicatedPropertyType::Int32);
348                p.replication_mode = ReplicationMode::ReliableOrdered;
349                p.send_on_change_only = true;
350                p
351            })
352            .with_rpc(
353                RpcDefinition::new("ServerFire", RpcTarget::Server)
354                    .add_param("origin", ReplicatedPropertyType::Vector3)
355                    .add_param("direction", ReplicatedPropertyType::Vector3)
356            )
357            .with_rpc(
358                RpcDefinition::new("ClientPlayHitEffect", RpcTarget::OwnerClient)
359                    .add_param("hit_position", ReplicatedPropertyType::Vector3)
360            )
361            .with_rpc(
362                RpcDefinition::new("MulticastExplosion", RpcTarget::MulticastUnreliable)
363                    .add_param("position", ReplicatedPropertyType::Vector3)
364                    .add_param("radius", ReplicatedPropertyType::Float)
365            )
366    }
367}
368
369// ---------------------------------------------------------------------------
370// Network simulation settings
371// ---------------------------------------------------------------------------
372
373#[derive(Debug, Clone)]
374pub struct LagSimulation {
375    pub enabled: bool,
376    pub latency_ms: f32,
377    pub jitter_ms: f32,
378    pub packet_loss_pct: f32,
379    pub duplication_pct: f32,
380    pub reorder_pct: f32,
381    pub bandwidth_kbps: u32,
382    pub profile: LagProfile,
383}
384
385#[derive(Debug, Clone, Copy, PartialEq)]
386pub enum LagProfile {
387    None,
388    Good,
389    Average,
390    Poor,
391    Terrible,
392    Mobile3G,
393    Mobile4G,
394    Wifi,
395    Custom,
396}
397
398impl LagProfile {
399    pub fn label(&self) -> &'static str {
400        match self {
401            Self::None => "No Lag",
402            Self::Good => "Good (20ms)",
403            Self::Average => "Average (60ms)",
404            Self::Poor => "Poor (150ms)",
405            Self::Terrible => "Terrible (300ms+)",
406            Self::Mobile3G => "Mobile 3G",
407            Self::Mobile4G => "Mobile 4G",
408            Self::Wifi => "WiFi",
409            Self::Custom => "Custom",
410        }
411    }
412
413    pub fn apply_to_sim(&self, sim: &mut LagSimulation) {
414        match self {
415            Self::None => { sim.latency_ms = 0.0; sim.jitter_ms = 0.0; sim.packet_loss_pct = 0.0; sim.bandwidth_kbps = 100_000; }
416            Self::Good => { sim.latency_ms = 20.0; sim.jitter_ms = 2.0; sim.packet_loss_pct = 0.0; sim.bandwidth_kbps = 10_000; }
417            Self::Average => { sim.latency_ms = 60.0; sim.jitter_ms = 10.0; sim.packet_loss_pct = 0.5; sim.bandwidth_kbps = 5_000; }
418            Self::Poor => { sim.latency_ms = 150.0; sim.jitter_ms = 30.0; sim.packet_loss_pct = 2.0; sim.bandwidth_kbps = 1_000; }
419            Self::Terrible => { sim.latency_ms = 300.0; sim.jitter_ms = 80.0; sim.packet_loss_pct = 5.0; sim.bandwidth_kbps = 256; }
420            Self::Mobile3G => { sim.latency_ms = 100.0; sim.jitter_ms = 20.0; sim.packet_loss_pct = 1.0; sim.bandwidth_kbps = 384; }
421            Self::Mobile4G => { sim.latency_ms = 40.0; sim.jitter_ms = 8.0; sim.packet_loss_pct = 0.2; sim.bandwidth_kbps = 5_000; }
422            Self::Wifi => { sim.latency_ms = 5.0; sim.jitter_ms = 5.0; sim.packet_loss_pct = 0.1; sim.bandwidth_kbps = 20_000; }
423            Self::Custom => {}
424        }
425        sim.profile = *self;
426    }
427}
428
429impl Default for LagSimulation {
430    fn default() -> Self {
431        Self {
432            enabled: false,
433            latency_ms: 0.0,
434            jitter_ms: 0.0,
435            packet_loss_pct: 0.0,
436            duplication_pct: 0.0,
437            reorder_pct: 0.0,
438            bandwidth_kbps: 100_000,
439            profile: LagProfile::None,
440        }
441    }
442}
443
444impl LagSimulation {
445    pub fn rtt_ms(&self) -> f32 { self.latency_ms * 2.0 }
446    pub fn bandwidth_bytes_per_sec(&self) -> f32 { self.bandwidth_kbps as f32 * 128.0 }
447}
448
449// ---------------------------------------------------------------------------
450// Bandwidth monitor
451// ---------------------------------------------------------------------------
452
453#[derive(Debug, Clone, Default)]
454pub struct BandwidthSample {
455    pub timestamp_ms: f64,
456    pub bytes_sent: u64,
457    pub bytes_recv: u64,
458    pub packets_sent: u32,
459    pub packets_recv: u32,
460    pub packets_lost: u32,
461    pub rtt_ms: f32,
462}
463
464#[derive(Debug, Clone)]
465pub struct BandwidthMonitor {
466    pub samples: Vec<BandwidthSample>,
467    pub max_samples: usize,
468    pub total_bytes_sent: u64,
469    pub total_bytes_recv: u64,
470    pub total_packets_lost: u64,
471    pub session_start_ms: f64,
472}
473
474impl Default for BandwidthMonitor {
475    fn default() -> Self {
476        Self {
477            samples: Vec::new(),
478            max_samples: 512,
479            total_bytes_sent: 0,
480            total_bytes_recv: 0,
481            total_packets_lost: 0,
482            session_start_ms: 0.0,
483        }
484    }
485}
486
487impl BandwidthMonitor {
488    pub fn push_sample(&mut self, sample: BandwidthSample) {
489        self.total_bytes_sent += sample.bytes_sent;
490        self.total_bytes_recv += sample.bytes_recv;
491        self.total_packets_lost += sample.packets_lost as u64;
492        self.samples.push(sample);
493        if self.samples.len() > self.max_samples {
494            self.samples.remove(0);
495        }
496    }
497
498    pub fn current_send_kbps(&self) -> f32 {
499        if self.samples.len() < 2 { return 0.0; }
500        let n = self.samples.len();
501        let bytes = self.samples[n-1].bytes_sent;
502        let dt_s = (self.samples[n-1].timestamp_ms - self.samples[n-2].timestamp_ms) / 1000.0;
503        if dt_s <= 0.0 { return 0.0; }
504        bytes as f32 / dt_s as f32 / 1024.0
505    }
506
507    pub fn current_recv_kbps(&self) -> f32 {
508        if self.samples.len() < 2 { return 0.0; }
509        let n = self.samples.len();
510        let bytes = self.samples[n-1].bytes_recv;
511        let dt_s = (self.samples[n-1].timestamp_ms - self.samples[n-2].timestamp_ms) / 1000.0;
512        if dt_s <= 0.0 { return 0.0; }
513        bytes as f32 / dt_s as f32 / 1024.0
514    }
515
516    pub fn avg_rtt_ms(&self) -> f32 {
517        if self.samples.is_empty() { return 0.0; }
518        self.samples.iter().map(|s| s.rtt_ms).sum::<f32>() / self.samples.len() as f32
519    }
520
521    pub fn packet_loss_pct(&self) -> f32 {
522        let total_sent: u64 = self.samples.iter().map(|s| s.packets_sent as u64).sum();
523        if total_sent == 0 { return 0.0; }
524        self.total_packets_lost as f32 / total_sent as f32 * 100.0
525    }
526
527    pub fn generate_synthetic_samples(&mut self, count: usize) {
528        let mut rng_seed: u64 = 42;
529        for i in 0..count {
530            rng_seed = rng_seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
531            let noise = ((rng_seed >> 33) as f32) / (u32::MAX as f32) * 2.0 - 1.0;
532            self.push_sample(BandwidthSample {
533                timestamp_ms: i as f64 * 16.666,
534                bytes_sent: (1024 + (noise * 200.0) as i64).max(0) as u64,
535                bytes_recv: (512 + (noise * 100.0) as i64).max(0) as u64,
536                packets_sent: 8,
537                packets_recv: 8,
538                packets_lost: if noise > 0.9 { 1 } else { 0 },
539                rtt_ms: 45.0 + noise * 10.0,
540            });
541        }
542    }
543}
544
545// ---------------------------------------------------------------------------
546// Session and lobby
547// ---------------------------------------------------------------------------
548
549#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
550pub enum SessionState {
551    Creating,
552    Searching,
553    Joining,
554    InLobby,
555    InGame,
556    Ending,
557    Disconnected,
558}
559
560impl SessionState {
561    pub fn label(&self) -> &'static str {
562        match self {
563            Self::Creating => "Creating",
564            Self::Searching => "Searching",
565            Self::Joining => "Joining",
566            Self::InLobby => "In Lobby",
567            Self::InGame => "In Game",
568            Self::Ending => "Ending",
569            Self::Disconnected => "Disconnected",
570        }
571    }
572}
573
574#[derive(Debug, Clone)]
575pub struct NetworkPlayer {
576    pub player_id: u64,
577    pub display_name: String,
578    pub ping_ms: u32,
579    pub is_host: bool,
580    pub is_local: bool,
581    pub is_ready: bool,
582    pub team: Option<u32>,
583    pub connection_quality: ConnectionQuality,
584    pub bytes_sent: u64,
585    pub bytes_recv: u64,
586    pub packet_loss_pct: f32,
587    pub load_pct: f32,
588}
589
590#[derive(Debug, Clone, Copy, PartialEq)]
591pub enum ConnectionQuality {
592    Excellent,
593    Good,
594    Fair,
595    Poor,
596    Disconnecting,
597}
598
599impl ConnectionQuality {
600    pub fn from_ping(ping_ms: u32) -> Self {
601        match ping_ms {
602            0..=40 => Self::Excellent,
603            41..=80 => Self::Good,
604            81..=150 => Self::Fair,
605            151..=300 => Self::Poor,
606            _ => Self::Disconnecting,
607        }
608    }
609
610    pub fn color(&self) -> Vec4 {
611        match self {
612            Self::Excellent => Vec4::new(0.2, 0.9, 0.2, 1.0),
613            Self::Good => Vec4::new(0.6, 0.9, 0.2, 1.0),
614            Self::Fair => Vec4::new(0.9, 0.8, 0.1, 1.0),
615            Self::Poor => Vec4::new(0.9, 0.4, 0.1, 1.0),
616            Self::Disconnecting => Vec4::new(0.9, 0.1, 0.1, 1.0),
617        }
618    }
619}
620
621#[derive(Debug, Clone)]
622pub struct NetworkSession {
623    pub session_id: String,
624    pub session_name: String,
625    pub topology: NetworkTopology,
626    pub state: SessionState,
627    pub max_players: u32,
628    pub players: Vec<NetworkPlayer>,
629    pub map_name: String,
630    pub game_mode: String,
631    pub is_private: bool,
632    pub server_region: String,
633    pub server_ip: String,
634    pub server_port: u16,
635    pub tick_rate: u32,
636    pub uptime_secs: f64,
637}
638
639impl NetworkSession {
640    pub fn new_lobby(name: &str) -> Self {
641        Self {
642            session_id: format!("session_{}", name.to_lowercase().replace(' ', "_")),
643            session_name: name.to_string(),
644            topology: NetworkTopology::Dedicated,
645            state: SessionState::InLobby,
646            max_players: 16,
647            players: Vec::new(),
648            map_name: "MainMap".to_string(),
649            game_mode: "Team Deathmatch".to_string(),
650            is_private: false,
651            server_region: "us-east-1".to_string(),
652            server_ip: "192.168.1.100".to_string(),
653            server_port: 7777,
654            tick_rate: 60,
655            uptime_secs: 0.0,
656        }
657    }
658
659    pub fn player_count(&self) -> u32 { self.players.len() as u32 }
660    pub fn is_full(&self) -> bool { self.player_count() >= self.max_players }
661
662    pub fn average_ping(&self) -> f32 {
663        if self.players.is_empty() { return 0.0; }
664        self.players.iter().map(|p| p.ping_ms as f32).sum::<f32>() / self.players.len() as f32
665    }
666
667    pub fn host_player(&self) -> Option<&NetworkPlayer> {
668        self.players.iter().find(|p| p.is_host)
669    }
670
671    pub fn all_ready(&self) -> bool {
672        self.players.iter().filter(|p| !p.is_host).all(|p| p.is_ready)
673    }
674
675    pub fn total_bandwidth_kbps(&self) -> f32 {
676        self.players.iter().map(|p| (p.bytes_sent + p.bytes_recv) as f32 / 1024.0).sum()
677    }
678}
679
680// ---------------------------------------------------------------------------
681// Network node graph (visual routing)
682// ---------------------------------------------------------------------------
683
684#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
685pub enum NetNodeKind {
686    Client,
687    Server,
688    Relay,
689    LoadBalancer,
690    Database,
691    Matchmaker,
692    AuthServer,
693    VoiceServer,
694    Metrics,
695    Gateway,
696}
697
698impl NetNodeKind {
699    pub fn label(&self) -> &'static str {
700        match self {
701            Self::Client => "Client",
702            Self::Server => "Server",
703            Self::Relay => "Relay",
704            Self::LoadBalancer => "Load Balancer",
705            Self::Database => "Database",
706            Self::Matchmaker => "Matchmaker",
707            Self::AuthServer => "Auth Server",
708            Self::VoiceServer => "Voice Server",
709            Self::Metrics => "Metrics",
710            Self::Gateway => "Gateway",
711        }
712    }
713
714    pub fn color(&self) -> Vec4 {
715        match self {
716            Self::Client => Vec4::new(0.3, 0.6, 0.9, 1.0),
717            Self::Server => Vec4::new(0.9, 0.6, 0.2, 1.0),
718            Self::Relay => Vec4::new(0.5, 0.8, 0.5, 1.0),
719            Self::LoadBalancer => Vec4::new(0.8, 0.5, 0.8, 1.0),
720            Self::Database => Vec4::new(0.8, 0.4, 0.3, 1.0),
721            Self::Matchmaker => Vec4::new(0.4, 0.8, 0.8, 1.0),
722            Self::AuthServer => Vec4::new(0.8, 0.8, 0.3, 1.0),
723            Self::VoiceServer => Vec4::new(0.6, 0.6, 0.9, 1.0),
724            Self::Metrics => Vec4::new(0.6, 0.9, 0.6, 1.0),
725            Self::Gateway => Vec4::new(0.9, 0.7, 0.5, 1.0),
726        }
727    }
728}
729
730#[derive(Debug, Clone)]
731pub struct NetNode {
732    pub id: u64,
733    pub kind: NetNodeKind,
734    pub position: Vec2,
735    pub size: Vec2,
736    pub name: String,
737    pub region: String,
738    pub instance_count: u32,
739    pub player_count: u32,
740    pub cpu_usage: f32,
741    pub mem_usage_mb: f32,
742    pub bandwidth_kbps: f32,
743    pub status_ok: bool,
744}
745
746impl NetNode {
747    pub fn new(id: u64, kind: NetNodeKind, position: Vec2) -> Self {
748        Self {
749            id,
750            kind,
751            position,
752            size: Vec2::new(140.0, 60.0),
753            name: kind.label().to_string(),
754            region: "us-east-1".to_string(),
755            instance_count: 1,
756            player_count: 0,
757            cpu_usage: 0.0,
758            mem_usage_mb: 0.0,
759            bandwidth_kbps: 0.0,
760            status_ok: true,
761        }
762    }
763}
764
765#[derive(Debug, Clone)]
766pub struct NetEdge {
767    pub id: u64,
768    pub from_id: u64,
769    pub to_id: u64,
770    pub protocol: NetworkProtocol,
771    pub latency_ms: f32,
772    pub bandwidth_kbps: f32,
773    pub encrypted: bool,
774    pub bidirectional: bool,
775}
776
777#[derive(Debug, Clone, Copy, PartialEq)]
778pub enum NetworkProtocol {
779    Udp,
780    Tcp,
781    WebSocket,
782    WebRtc,
783    Quic,
784    Rudp,
785}
786
787impl NetworkProtocol {
788    pub fn label(&self) -> &'static str {
789        match self {
790            Self::Udp => "UDP",
791            Self::Tcp => "TCP",
792            Self::WebSocket => "WebSocket",
793            Self::WebRtc => "WebRTC",
794            Self::Quic => "QUIC",
795            Self::Rudp => "RUDP",
796        }
797    }
798    pub fn is_reliable(&self) -> bool {
799        matches!(self, Self::Tcp | Self::WebSocket | Self::Quic)
800    }
801}
802
803#[derive(Debug, Clone)]
804pub struct NetworkTopologyGraph {
805    pub nodes: Vec<NetNode>,
806    pub edges: Vec<NetEdge>,
807    pub next_id: u64,
808}
809
810impl Default for NetworkTopologyGraph {
811    fn default() -> Self {
812        let mut g = Self { nodes: Vec::new(), edges: Vec::new(), next_id: 1 };
813        g.build_typical_dedicated_topology();
814        g
815    }
816}
817
818impl NetworkTopologyGraph {
819    fn next_id(&mut self) -> u64 {
820        let id = self.next_id;
821        self.next_id += 1;
822        id
823    }
824
825    pub fn add_node(&mut self, kind: NetNodeKind, pos: Vec2, name: &str) -> u64 {
826        let id = self.next_id();
827        let mut node = NetNode::new(id, kind, pos);
828        node.name = name.to_string();
829        self.nodes.push(node);
830        id
831    }
832
833    pub fn add_edge(&mut self, from: u64, to: u64, protocol: NetworkProtocol) -> u64 {
834        let id = self.next_id();
835        self.edges.push(NetEdge {
836            id,
837            from_id: from,
838            to_id: to,
839            protocol,
840            latency_ms: 20.0,
841            bandwidth_kbps: 10_000.0,
842            encrypted: true,
843            bidirectional: true,
844        });
845        id
846    }
847
848    pub fn build_typical_dedicated_topology(&mut self) {
849        let gw = self.add_node(NetNodeKind::Gateway, Vec2::new(400.0, 20.0), "CDN Gateway");
850        let auth = self.add_node(NetNodeKind::AuthServer, Vec2::new(100.0, 120.0), "Auth Service");
851        let match_ = self.add_node(NetNodeKind::Matchmaker, Vec2::new(400.0, 120.0), "Matchmaker");
852        let lb = self.add_node(NetNodeKind::LoadBalancer, Vec2::new(700.0, 120.0), "Load Balancer");
853        let db = self.add_node(NetNodeKind::Database, Vec2::new(100.0, 260.0), "Game DB");
854        let srv1 = self.add_node(NetNodeKind::Server, Vec2::new(550.0, 260.0), "Game Server 1");
855        let srv2 = self.add_node(NetNodeKind::Server, Vec2::new(700.0, 260.0), "Game Server 2");
856        let srv3 = self.add_node(NetNodeKind::Server, Vec2::new(850.0, 260.0), "Game Server 3");
857        let voice = self.add_node(NetNodeKind::VoiceServer, Vec2::new(250.0, 260.0), "Voice Chat");
858        let metrics = self.add_node(NetNodeKind::Metrics, Vec2::new(400.0, 260.0), "Telemetry");
859        let cli1 = self.add_node(NetNodeKind::Client, Vec2::new(400.0, 400.0), "Client A");
860        let cli2 = self.add_node(NetNodeKind::Client, Vec2::new(550.0, 400.0), "Client B");
861        let cli3 = self.add_node(NetNodeKind::Client, Vec2::new(700.0, 400.0), "Client C");
862
863        self.add_edge(cli1, gw, NetworkProtocol::Quic);
864        self.add_edge(cli2, gw, NetworkProtocol::Quic);
865        self.add_edge(cli3, gw, NetworkProtocol::Quic);
866        self.add_edge(gw, auth, NetworkProtocol::Tcp);
867        self.add_edge(gw, match_, NetworkProtocol::Tcp);
868        self.add_edge(match_, lb, NetworkProtocol::Tcp);
869        self.add_edge(lb, srv1, NetworkProtocol::Udp);
870        self.add_edge(lb, srv2, NetworkProtocol::Udp);
871        self.add_edge(lb, srv3, NetworkProtocol::Udp);
872        self.add_edge(auth, db, NetworkProtocol::Tcp);
873        self.add_edge(srv1, db, NetworkProtocol::Tcp);
874        self.add_edge(srv1, metrics, NetworkProtocol::Udp);
875        self.add_edge(cli1, voice, NetworkProtocol::WebRtc);
876        self.add_edge(cli2, voice, NetworkProtocol::WebRtc);
877    }
878}
879
880// ---------------------------------------------------------------------------
881// Network editor panels
882// ---------------------------------------------------------------------------
883
884#[derive(Debug, Clone, Copy, PartialEq)]
885pub enum NetworkEditorPanel {
886    Topology,
887    Replication,
888    BandwidthMonitor,
889    LagSimulation,
890    Lobby,
891    RpcBrowser,
892    Diagnostics,
893}
894
895impl NetworkEditorPanel {
896    pub fn label(&self) -> &'static str {
897        match self {
898            Self::Topology => "Topology",
899            Self::Replication => "Replication",
900            Self::BandwidthMonitor => "Bandwidth",
901            Self::LagSimulation => "Lag Sim",
902            Self::Lobby => "Lobby",
903            Self::RpcBrowser => "RPCs",
904            Self::Diagnostics => "Diagnostics",
905        }
906    }
907}
908
909// ---------------------------------------------------------------------------
910// Diagnostic events
911// ---------------------------------------------------------------------------
912
913#[derive(Debug, Clone, Copy, PartialEq)]
914pub enum NetworkEventKind {
915    PlayerConnected,
916    PlayerDisconnected,
917    PacketLost,
918    PacketReordered,
919    PacketDuplicated,
920    BandwidthThrottled,
921    HighLatency,
922    TimeOut,
923    Reconnected,
924    RpcDropped,
925    AuthFailed,
926    SessionCreated,
927    SessionDestroyed,
928    LevelLoad,
929}
930
931impl NetworkEventKind {
932    pub fn label(&self) -> &'static str {
933        match self {
934            Self::PlayerConnected => "Player Connected",
935            Self::PlayerDisconnected => "Player Disconnected",
936            Self::PacketLost => "Packet Lost",
937            Self::PacketReordered => "Packet Reordered",
938            Self::PacketDuplicated => "Packet Duplicated",
939            Self::BandwidthThrottled => "Bandwidth Throttled",
940            Self::HighLatency => "High Latency",
941            Self::TimeOut => "Time Out",
942            Self::Reconnected => "Reconnected",
943            Self::RpcDropped => "RPC Dropped",
944            Self::AuthFailed => "Auth Failed",
945            Self::SessionCreated => "Session Created",
946            Self::SessionDestroyed => "Session Destroyed",
947            Self::LevelLoad => "Level Load",
948        }
949    }
950
951    pub fn is_error(&self) -> bool {
952        matches!(self, Self::PacketLost | Self::TimeOut | Self::RpcDropped | Self::AuthFailed)
953    }
954
955    pub fn color(&self) -> Vec4 {
956        if self.is_error() {
957            Vec4::new(0.9, 0.3, 0.3, 1.0)
958        } else if matches!(self, Self::PlayerConnected | Self::SessionCreated | Self::Reconnected) {
959            Vec4::new(0.3, 0.9, 0.3, 1.0)
960        } else {
961            Vec4::new(0.7, 0.7, 0.7, 1.0)
962        }
963    }
964}
965
966#[derive(Debug, Clone)]
967pub struct NetworkDiagnosticEvent {
968    pub timestamp_ms: f64,
969    pub kind: NetworkEventKind,
970    pub player_id: Option<u64>,
971    pub description: String,
972    pub data: HashMap<String, String>,
973}
974
975// ---------------------------------------------------------------------------
976// Network editor state
977// ---------------------------------------------------------------------------
978
979#[derive(Debug)]
980pub struct NetworkEditor {
981    pub topology_graph: NetworkTopologyGraph,
982    pub session: NetworkSession,
983    pub actors: Vec<ReplicatedActor>,
984    pub bandwidth_monitor: BandwidthMonitor,
985    pub lag_sim: LagSimulation,
986    pub active_panel: NetworkEditorPanel,
987    pub selected_node: Option<u64>,
988    pub selected_actor: Option<usize>,
989    pub diagnostic_events: Vec<NetworkDiagnosticEvent>,
990    pub simulation_time_ms: f64,
991    pub network_paused: bool,
992    pub zoom: f32,
993    pub pan: Vec2,
994    pub search_query: String,
995    pub show_bandwidth_overlay: bool,
996    pub show_player_list: bool,
997}
998
999impl Default for NetworkEditor {
1000    fn default() -> Self {
1001        let mut bw_monitor = BandwidthMonitor::default();
1002        bw_monitor.generate_synthetic_samples(256);
1003
1004        let session = {
1005            let mut s = NetworkSession::new_lobby("Dev Session");
1006            s.players = vec![
1007                NetworkPlayer {
1008                    player_id: 1,
1009                    display_name: "HostPlayer".to_string(),
1010                    ping_ms: 5,
1011                    is_host: true,
1012                    is_local: true,
1013                    is_ready: true,
1014                    team: Some(0),
1015                    connection_quality: ConnectionQuality::Excellent,
1016                    bytes_sent: 12_000,
1017                    bytes_recv: 8_000,
1018                    packet_loss_pct: 0.0,
1019                    load_pct: 0.0,
1020                },
1021                NetworkPlayer {
1022                    player_id: 2,
1023                    display_name: "Player2".to_string(),
1024                    ping_ms: 48,
1025                    is_host: false,
1026                    is_local: false,
1027                    is_ready: true,
1028                    team: Some(0),
1029                    connection_quality: ConnectionQuality::Good,
1030                    bytes_sent: 11_000,
1031                    bytes_recv: 9_000,
1032                    packet_loss_pct: 0.1,
1033                    load_pct: 0.0,
1034                },
1035                NetworkPlayer {
1036                    player_id: 3,
1037                    display_name: "Player3".to_string(),
1038                    ping_ms: 142,
1039                    is_host: false,
1040                    is_local: false,
1041                    is_ready: false,
1042                    team: Some(1),
1043                    connection_quality: ConnectionQuality::Fair,
1044                    bytes_sent: 9_000,
1045                    bytes_recv: 7_500,
1046                    packet_loss_pct: 0.8,
1047                    load_pct: 0.0,
1048                },
1049            ];
1050            s
1051        };
1052
1053        let actors = vec![
1054            ReplicatedActor::player_actor(),
1055            {
1056                let mut a = ReplicatedActor::new("Projectile");
1057                a.properties.push({
1058                    let mut p = ReplicatedProperty::new("position", ReplicatedPropertyType::Vector3);
1059                    p.update_rate_hz = 60.0;
1060                    p
1061                });
1062                a.properties.push({
1063                    let mut p = ReplicatedProperty::new("velocity", ReplicatedPropertyType::Vector3);
1064                    p.update_rate_hz = 60.0;
1065                    p
1066                });
1067                a.always_relevant = true;
1068                a
1069            },
1070        ];
1071
1072        Self {
1073            topology_graph: NetworkTopologyGraph::default(),
1074            session,
1075            actors,
1076            bandwidth_monitor: bw_monitor,
1077            lag_sim: LagSimulation::default(),
1078            active_panel: NetworkEditorPanel::Topology,
1079            selected_node: None,
1080            selected_actor: None,
1081            diagnostic_events: Vec::new(),
1082            simulation_time_ms: 0.0,
1083            network_paused: false,
1084            zoom: 1.0,
1085            pan: Vec2::ZERO,
1086            search_query: String::new(),
1087            show_bandwidth_overlay: true,
1088            show_player_list: true,
1089        }
1090    }
1091}
1092
1093impl NetworkEditor {
1094    pub fn simulate_tick(&mut self, dt_ms: f64) {
1095        if self.network_paused { return; }
1096        self.simulation_time_ms += dt_ms;
1097        // push synthetic bandwidth sample
1098        let mut rng: u64 = self.simulation_time_ms as u64 * 2654435761;
1099        rng ^= rng >> 33;
1100        rng = rng.wrapping_mul(0xFF51AFD7ED558CCD);
1101        let noise = ((rng >> 33) as f32) / (u32::MAX as f32) * 2.0 - 1.0;
1102        self.bandwidth_monitor.push_sample(BandwidthSample {
1103            timestamp_ms: self.simulation_time_ms,
1104            bytes_sent: (1024 + (noise * 256.0) as i64).max(0) as u64,
1105            bytes_recv: (512 + (noise * 128.0) as i64).max(0) as u64,
1106            packets_sent: 8,
1107            packets_recv: 8,
1108            packets_lost: if noise > 0.95 { 1 } else { 0 },
1109            rtt_ms: self.lag_sim.rtt_ms() + noise * self.lag_sim.jitter_ms,
1110        });
1111    }
1112
1113    pub fn apply_lag_profile(&mut self, profile: LagProfile) {
1114        profile.apply_to_sim(&mut self.lag_sim);
1115    }
1116
1117    pub fn total_replicated_bandwidth_bps(&self) -> f32 {
1118        self.actors.iter().map(|a| a.total_bandwidth_bps()).sum()
1119    }
1120
1121    pub fn generate_network_report(&self) -> String {
1122        let mut lines = Vec::new();
1123        lines.push("=== Network Report ===".to_string());
1124        lines.push(format!("Topology: {}", self.session.topology.label()));
1125        lines.push(format!("State: {}", self.session.state.label()));
1126        lines.push(format!("Players: {}/{}", self.session.player_count(), self.session.max_players));
1127        lines.push(format!("Avg Ping: {:.1} ms", self.session.average_ping()));
1128        lines.push(format!("Bandwidth Send: {:.1} kB/s", self.bandwidth_monitor.current_send_kbps()));
1129        lines.push(format!("Bandwidth Recv: {:.1} kB/s", self.bandwidth_monitor.current_recv_kbps()));
1130        lines.push(format!("Packet Loss: {:.2}%", self.bandwidth_monitor.packet_loss_pct()));
1131        lines.push(format!("Lag Sim: {} (latency: {:.0} ms, loss: {:.1}%)",
1132            if self.lag_sim.enabled { "ON" } else { "OFF" },
1133            self.lag_sim.latency_ms,
1134            self.lag_sim.packet_loss_pct,
1135        ));
1136        lines.push(format!("Replicated Actors: {}", self.actors.len()));
1137        lines.push(format!("Total Replication Bandwidth: {:.0} b/s", self.total_replicated_bandwidth_bps()));
1138        lines.join("\n")
1139    }
1140}