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
//! `plaza_client_utils`
//!
//! This crate provides client-side utilities designed to complement applications
//! built with the Plaza server framework. It focuses on helping client applications
//! implement common networking patterns such as:
//!
//! - **Client-Side Prediction (CSP):** Allowing clients to predict the outcome of their
//! inputs locally for immediate feedback.
//! - **Server Reconciliation:** Correcting client predictions with authoritative state
//! received from the server.
//! - **State Interpolation/Extrapolation:** Smoothing the display of remote entities.
//!
//! The utilities are designed to be generic and unopinionated about the specific
//! game engine or rendering library used by the client application. They provide
//! data structures and algorithms that operate on application-defined `StateType`
//! and `ClientOp` types.
//!
//! # Core Components
//!
//! - **`input_buffer::ClientInputBuffer`**: Stores a history of client inputs sent to
//! the server, essential for replaying inputs during reconciliation.
//! - **`prediction::PredictedEntity`**: Manages the predicted state of a client-controlled
//! entity and handles the reconciliation process against server updates.
//! - **`interpolation::SnapshotBuffer`** and the **`Interpolatable`** trait: buffer server
//! snapshots of remote entities and interpolate between them for smooth rendering.
//! **`interpolation::InterpolationClock`** supplies the render-time target they need.
//! - **`extrapolation::ExtrapolationBase`** and the **`Extrapolatable`** trait: project a
//! remote entity's movement for short durations to hide gaps between updates.
//! - **`smoothing::ErrorSmoother`**: eases a reconciliation correction over a few frames
//! instead of snapping it.
//! - **`timestep::FixedTimestep`** and **`Periodic`**: turning however long the
//! last frame took into whole fixed steps, or into "is it time yet". Two
//! simulations running the same rule at different step sizes are not the same
//! simulation, and the drift reads as network jitter, so both sides taking the
//! step from here is what keeps them equal.
//! - **`rollback`**: the other netcode family, peer-to-peer deterministic lockstep.
//! `StateHistory`, `InputTimeline`, and the `RollbackSession` bundle predict a
//! missing remote input and roll back to re-simulate when the guess is disproved.
//!
//! # Four principles worth knowing before you predict or render anything
//!
//! None is enforceable by a type, and between them they account for every
//! netcode bug found while building the playground examples. They prevent
//! bugs, where everything else in this crate only recovers from them.
//! The first two are about simulation, the last two about rendering, and the
//! examples' `LEARNINGS.md` records what each one cost to learn.
//!
//! **1. A shared rule must be shared code, not code written twice.** The `apply`
//! you hand [`PredictedPlayer`] or [`HeldInputPredictor`] is meant to *be* the
//! server's step function, not a client approximation of it. Anything the server
//! does that your copy leaves out arrives as a permanent correction: it looks
//! like network jitter, it is largest exactly when it is most visible, and it is
//! extremely expensive to find later. Measured across two examples, every entity
//! whose rule lived in one function both sides called was correct and stayed
//! correct, and every entity whose rule was written twice drifted.
//!
//! If your client's rule needs the world to run (gravity, wind, a moving
//! platform), that is what the context parameter is for. Being unable to pass
//! the world in is exactly what pushes people into writing the second, lesser
//! rule, so it is a deficiency in the API rather than a reason to fork the rule.
//!
//! **2. Prediction is presentation; shared rules consume authoritative state.**
//! Feeding a locally predicted position into a rule that *both* sides run
//! creates a second, divergent world, and every packet then fights the local
//! one. Prediction drives the camera and the local player's own marker. The
//! rules both sides run read the authoritative state, even though it is older.
//! This is counterintuitive, because using the freshest local data looks like an
//! improvement.
//!
//! **3. One instant per frame.** A client that renders in the past picks a
//! single instant T for the whole frame, and everything is evaluated at T: not
//! only where entities are drawn, but everything a behaviour rule reads while
//! producing the frame, aim targets and chase context included. An entity
//! simulated to T while reading a target from the newest packet is two
//! timelines in one scene, and the seam between them is a bug whether or not
//! it is visible yet. [`interpolation::InterpolationClock`] supplies T; the
//! discipline of feeding *every* read from it is yours.
//!
//! **4. The timeline comes from declaration, not arrival.** Transport facts,
//! round trips and jitter and arrival times, may size buffers and admit or
//! refuse connections. They never decide which moment is on screen or when an
//! input executes; those are declared numbers the server chooses and
//! publishes. A render clock steered by packet arrival hides bad links instead
//! of reporting them, lets every client pick a different "now", and quietly
//! makes ping an input to the game.
//!
//! # The resume contract
//!
//! Every long-lived client eventually stops reading: a browser tab goes to the
//! background, a laptop sleeps, a frame loop stalls. The socket keeps
//! receiving the whole time, so what a resumed client faces is not a slow
//! stream but a *lump*: minutes of packets, delivered at once, describing
//! moments it can never play. The recovery that works is built from one
//! invariant, stated here because each half lives in a different crate:
//!
//! **A client may discard any stretch of the stream unread, provided it also
//! drops the state derived from it, because an acknowledgement carrying the
//! digest of nothing obligates the server to answer with a full baseline.**
//!
//! That is the digest-and-rebuild machinery of `server_utils::DeltaBaseline`
//! and [`mirror::DeltaMirror`], read as a permission. It is why there is no
//! "resync request" message anywhere: dropping the mirror *is* the request.
//! On top of it, resume is three verdicts at three layers, each owned by a
//! block:
//!
//! - the **transport** discards the backlog before parsing it
//! (`plaza_ws::trim_backlog`), because none of it survives what follows;
//! - the **playout queue** treats the gap as a discontinuity and restarts
//! once, keeping only the newest packet ([`PlayoutBuffer`]);
//! - the **server** stops streaming to a subscriber that has provably stopped
//! reading (`DeltaBaseline::with_flow`), so the lump never grows to
//! megabytes in the first place.
//!
//! The application's remaining job is small and cannot be taken from it: on
//! [`playout::Admission::TimelineLost`], drop the mirror and re-anchor the
//! render clock on what just arrived.
//!
//! # Which predictor
//!
//! The two differ by how the *server* consumes input, not by how the client
//! feels. Choosing wrong is silent, and shows up as a prediction that is always
//! slightly behind.
//!
//! | the server | use |
//! |---|---|
//! | consumes one input per simulation step | [`PredictedPlayer`] (replay unacknowledged inputs) |
//! | holds an input and integrates it every tick | [`HeldInputPredictor`] (dead reckon and ease) |
//!
//! Replaying inputs against a server of the second kind double counts, and gets
//! worse the more you economise on bandwidth, because one coalesced input can
//! cover a long stretch of simulation.
//!
//! # Philosophy
//!
//! `plaza_client_utils` aims to provide foundational building blocks, not a complete
//! client-side framework. The application developer is responsible for:
//! - Defining their `StateType` and `ClientOp` types.
//! - Implementing the client-side game logic (how an `Op` affects `StateType`).
//! - Integrating with their chosen networking library (e.g., WebSockets, WebRTC, renet)
//! to send `ClientOp`s and receive server state updates.
//! - Driving the rendering loop and using the predicted/interpolated states.
pub use AckWindow;
pub use ArrivalMonitor;
pub use ClockSyncEstimator;
pub use InputCoalescer;
pub use ;
pub use SetDigest;
pub use ClientUtilError;
pub use ;
pub use ScalarKalman;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use PredictedEntity;
pub use ;
pub use ;
pub use RttEstimator;
pub use ;
pub use ;
pub use ;
pub use ;