aetheris-protocol 0.2.23

High-performance binary contracts and communication traits for the Aetheris Engine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
use crate::types::{ClientId, ComponentKind, NetworkId};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

/// A reliable discrete platform event (Phase 1 / VS-02).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PlatformEvent {
    /// A resource was completely exhausted of its payload.
    ResourceExhausted {
        /// The network ID of the resource that was exhausted.
        network_id: NetworkId,
    },
    /// Explicitly informs a client that they now own/control a specific entity.
    Possession {
        /// The network ID of the entity now owned by the client.
        network_id: NetworkId,
    },
    /// Sends extensible server-side metadata (versions, counters, debug data).
    WorkspaceManifest {
        /// The collection of metadata key-value pairs.
        manifest: BTreeMap<String, String>,
    },
    /// Interaction (e.g. damage) applied to an entity.
    Interaction {
        source: NetworkId,
        target: NetworkId,
        amount: u16,
    },
    /// An entity has been terminated.
    Termination { target: NetworkId },
    /// An entity has been reinitialized.
    Reinitialization { target: NetworkId, x: f32, y: f32 },
    /// A data drop was collected by an agent.
    PayloadCollected {
        /// The network ID of the data drop that was collected.
        network_id: NetworkId,
        /// The amount of payload collected.
        amount: u16,
    },
}

impl PlatformEvent {
    /// Converts a `PlatformEvent` into a `WireEvent`.
    #[must_use]
    pub fn into_wire_event(self) -> WireEvent {
        WireEvent::PlatformEvent(self)
    }
}

/// An event representing a fragment of a larger message.
/// Used for MTU stability to prevent packet drops and enable reassembly.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FragmentedEvent {
    /// Unique identifier for the fragmented message.
    pub message_id: u32,
    /// The index of this fragment (0-based).
    pub fragment_index: u16,
    /// Total number of fragments for this message.
    pub total_fragments: u16,
    /// The raw payload of this fragment.
    #[serde(with = "serde_bytes")]
    pub payload: Vec<u8>,
}

/// An event representing a change to a single component on a single entity.
/// Produced by `WorldState::extract_deltas()` on the server.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReplicationEvent {
    /// Which entity changed.
    pub network_id: NetworkId,
    /// Which component type changed.
    pub component_kind: ComponentKind,
    /// The serialized delta payload (only the changed fields).
    /// In Phase 1, this is a full snapshot per component for simplicity.
    #[serde(with = "serde_bytes")]
    pub payload: Vec<u8>,
    /// The server tick at which this change was recorded.
    pub tick: u64,
}

/// An inbound update to be applied to the ECS.
/// Produced by `Encoder::decode()` on the receiving end.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ComponentUpdate {
    /// The entity to update.
    pub network_id: NetworkId,
    /// Which component type to update.
    pub component_kind: ComponentKind,
    /// The deserialized field values.
    #[serde(with = "serde_bytes")]
    pub payload: Vec<u8>,
    /// The tick this update originated from.
    pub tick: u64,
}

impl From<ReplicationEvent> for ComponentUpdate {
    fn from(event: ReplicationEvent) -> Self {
        Self {
            network_id: event.network_id,
            component_kind: event.component_kind,
            payload: event.payload,
            tick: event.tick,
        }
    }
}

/// Events produced by `PlatformTransport::poll_events()`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum NetworkEvent {
    /// A new client has connected and been assigned a `ClientId`.
    ClientConnected(ClientId),
    /// A client has disconnected (graceful or timeout).
    ClientDisconnected(ClientId),
    /// Raw unreliable data received from a client.
    UnreliableMessage {
        /// The client that sent the message.
        client_id: ClientId,
        /// The raw message bytes.
        #[serde(with = "serde_bytes")]
        data: Vec<u8>,
    },
    /// Raw reliable data received from a client.
    ReliableMessage {
        /// The client that sent the message.
        client_id: ClientId,
        /// The raw message bytes.
        #[serde(with = "serde_bytes")]
        data: Vec<u8>,
    },
    /// A heartbeat ping from a client.
    Ping {
        /// The client that sent the ping.
        client_id: ClientId,
        /// The client's tick/timestamp when the ping was sent.
        tick: u64,
    },
    /// A heartbeat pong from the server.
    Pong {
        /// The original tick/timestamp from the ping.
        tick: u64,
    },
    /// A new entity has been spawned in the world.
    EntitySpawned {
        /// The client involved in the spawn (or targeted).
        client_id: ClientId,
        /// The unique network identifier for the entity.
        network_id: NetworkId,
        /// The kind/type of entity being spawned.
        kind: u16,
    },
    /// An entity has been despawned from the world.
    EntityDespawned {
        /// The client involved in the despawn (or targeted).
        client_id: ClientId,
        /// The network identifier of the entity to remove.
        network_id: NetworkId,
    },
    /// A session authentication request from the client.
    Auth {
        /// The session token obtained from the Control Plane.
        session_token: String,
    },
    /// A WebTransport session was closed by the remote or due to error.
    SessionClosed(ClientId),
    /// A WebTransport stream was reset.
    StreamReset(ClientId),
    /// A fragment of a larger message.
    Fragment {
        /// The client that sent the fragment.
        client_id: ClientId,
        /// The fragment data.
        fragment: FragmentedEvent,
    },
    /// A testing command to trigger a stress test (Phase 1/Playground only).
    StressTest {
        /// The client that requested the stress test.
        client_id: ClientId,
        /// Number of entities to spawn.
        count: u16,
        /// Whether spawned entities should rotate.
        rotate: bool,
    },
    /// A testing command to spawn a specific entity (Phase 1/Playground only).
    Spawn {
        /// The client that requested the spawn.
        client_id: ClientId,
        /// Which entity type to spawn.
        entity_type: u16,
        /// Position X
        x: f32,
        /// Position Y
        y: f32,
        /// Initial rotation
        rot: f32,
    },
    /// A command to clear all entities from the world (Phase 1/Playground only).
    ClearWorld {
        /// The client that requested the clear.
        client_id: ClientId,
    },
    /// Client requests to start a gameplay session: spawns the session agent and grants Possession.
    StartSession {
        /// The client starting the session.
        client_id: ClientId,
    },
    /// A request from a client to receive the current workspace manifest.
    RequestWorkspaceManifest {
        /// The client that requested the manifest.
        client_id: ClientId,
    },
    /// A local event indicating the client transport has been disconnected.
    Disconnected(ClientId),
    /// A discrete platform event (e.g. exhaustion, termination).
    PlatformEvent {
        /// The client involved (or targeted).
        client_id: ClientId,
        /// The event data.
        event: PlatformEvent,
    },
    /// A batch of replication updates sent together to save bandwidth/packets.
    ReplicationBatch {
        /// The client that should receive the batch.
        client_id: ClientId,
        /// The collection of updates.
        events: Vec<ReplicationEvent>,
    },
}

impl NetworkEvent {
    /// Returns true if this event is capable of being sent over the wire.
    #[must_use]
    pub const fn is_wire(&self) -> bool {
        match self {
            Self::Ping { .. }
            | Self::Pong { .. }
            | Self::Auth { .. }
            | Self::Fragment { .. }
            | Self::StressTest { .. }
            | Self::Spawn { .. }
            | Self::ClearWorld { .. }
            | Self::StartSession { .. }
            | Self::RequestWorkspaceManifest { .. }
            | Self::EntitySpawned { .. }
            | Self::EntityDespawned { .. }
            | Self::PlatformEvent { .. }
            | Self::ReplicationBatch { .. } => true,
            Self::ClientConnected(_)
            | Self::ClientDisconnected(_)
            | Self::UnreliableMessage { .. }
            | Self::ReliableMessage { .. }
            | Self::SessionClosed(_)
            | Self::StreamReset(_)
            | Self::Disconnected(_) => false,
        }
    }
}

/// A restricted view of `NetworkEvent` for over-the-wire transport.
/// Prevents local-only variants (like `ClientConnected`) from being sent/received.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum WireEvent {
    /// A heartbeat ping.
    Ping {
        /// The client's tick/timestamp when the ping was sent.
        tick: u64,
    },
    /// A heartbeat pong.
    Pong {
        /// The original tick/timestamp from the ping.
        tick: u64,
    },
    /// A session authentication request.
    Auth {
        /// The session token.
        session_token: String,
    },
    /// A fragment of a larger message.
    Fragment(FragmentedEvent),
    /// A testing command to trigger a stress test.
    StressTest {
        /// Number of entities to spawn.
        count: u16,
        /// Whether spawned entities should rotate.
        rotate: bool,
    },
    /// A testing command to spawn a specific entity.
    Spawn {
        /// Which entity type to spawn.
        entity_type: u16,
        /// Position X
        x: f32,
        /// Position Y
        y: f32,
        /// Initial rotation
        rot: f32,
    },
    /// A command to clear all entities from the world.
    ClearWorld,
    /// A new entity has been spawned.
    EntitySpawned {
        /// The entity ID.
        network_id: NetworkId,
        /// The entity type.
        kind: u16,
    },
    /// An entity has been despawned.
    EntityDespawned {
        /// The entity ID.
        network_id: NetworkId,
    },
    /// Client requests to start a gameplay session: spawns the session agent and grants Possession.
    StartSession,
    /// A request to receive the current workspace manifest.
    RequestWorkspaceManifest,
    /// A discrete platform event.
    PlatformEvent(PlatformEvent),
    /// A batch of replication updates.
    ReplicationBatch(Vec<ReplicationEvent>),
}

impl WireEvent {
    /// Converts a `WireEvent` into a `NetworkEvent` for a specific client context.
    #[must_use]
    pub fn into_network_event(self, client_id: crate::types::ClientId) -> NetworkEvent {
        match self {
            Self::Ping { tick } => NetworkEvent::Ping { client_id, tick },
            Self::Pong { tick } => NetworkEvent::Pong { tick },
            Self::Auth { session_token } => NetworkEvent::Auth { session_token },
            Self::Fragment(fragment) => NetworkEvent::Fragment {
                client_id,
                fragment,
            },
            Self::StressTest { count, rotate } => NetworkEvent::StressTest {
                client_id,
                count,
                rotate,
            },
            Self::Spawn {
                entity_type,
                x,
                y,
                rot,
            } => NetworkEvent::Spawn {
                client_id,
                entity_type,
                x,
                y,
                rot,
            },
            Self::ClearWorld => NetworkEvent::ClearWorld { client_id },
            Self::StartSession => NetworkEvent::StartSession { client_id },
            Self::RequestWorkspaceManifest => NetworkEvent::RequestWorkspaceManifest { client_id },
            Self::PlatformEvent(event) => NetworkEvent::PlatformEvent { client_id, event },
            Self::EntitySpawned { network_id, kind } => NetworkEvent::EntitySpawned {
                client_id,
                network_id,
                kind,
            },
            Self::EntityDespawned { network_id } => NetworkEvent::EntityDespawned {
                client_id,
                network_id,
            },
            Self::ReplicationBatch(events) => NetworkEvent::ReplicationBatch { client_id, events },
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_network_event_is_wire() {
        assert!(
            NetworkEvent::Ping {
                client_id: ClientId(1),
                tick: 100
            }
            .is_wire()
        );
        assert!(
            NetworkEvent::PlatformEvent {
                client_id: ClientId(1),
                event: PlatformEvent::ResourceExhausted {
                    network_id: NetworkId(1)
                }
            }
            .is_wire()
        );
        assert!(
            NetworkEvent::ReplicationBatch {
                client_id: ClientId(1),
                events: vec![]
            }
            .is_wire()
        );
        assert!(!NetworkEvent::ClientConnected(ClientId(1)).is_wire());
        assert!(!NetworkEvent::ClientDisconnected(ClientId(1)).is_wire());
        assert!(!NetworkEvent::Disconnected(ClientId(1)).is_wire());

        // Test new wire events
        assert!(
            NetworkEvent::EntitySpawned {
                client_id: ClientId(1),
                network_id: NetworkId(1),
                kind: 1
            }
            .is_wire()
        );
        assert!(
            NetworkEvent::EntityDespawned {
                client_id: ClientId(1),
                network_id: NetworkId(1)
            }
            .is_wire()
        );
        assert!(
            NetworkEvent::RequestWorkspaceManifest {
                client_id: ClientId(1)
            }
            .is_wire()
        );
    }

    #[test]
    fn test_wire_event_conversion_roundtrip() {
        let wire = WireEvent::PlatformEvent(PlatformEvent::ResourceExhausted {
            network_id: NetworkId(42),
        });
        let client_id = ClientId(7);
        let network = wire.clone().into_network_event(client_id);

        if let NetworkEvent::PlatformEvent {
            client_id: cid,
            event,
        } = network
        {
            assert_eq!(cid, client_id);
            assert_eq!(
                event,
                PlatformEvent::ResourceExhausted {
                    network_id: NetworkId(42)
                }
            );
        } else {
            panic!("Conversion failed to preserve PlatformEvent variant");
        }

        // Test ReplicationBatch conversion
        let event = ReplicationEvent {
            network_id: NetworkId(1),
            component_kind: ComponentKind(1),
            payload: vec![1, 2, 3],
            tick: 100,
        };
        let batch_wire = WireEvent::ReplicationBatch(vec![event.clone()]);
        let batch_network = batch_wire.into_network_event(client_id);
        if let NetworkEvent::ReplicationBatch {
            client_id: cid,
            events,
        } = batch_network
        {
            assert_eq!(cid, client_id);
            assert!(!events.is_empty());
            assert_eq!(events[0].tick, 100);
            assert_eq!(events[0].payload, vec![1, 2, 3]);
            assert_eq!(events[0].network_id, NetworkId(1));
            assert_eq!(events[0].component_kind, ComponentKind(1));
        } else {
            panic!("Conversion failed to preserve ReplicationBatch variant");
        }
    }
}