This commit is contained in:
SepComet
2026-04-06 16:19:44 +08:00
parent a1ede230bb
commit 79474b53aa
42 changed files with 1264 additions and 18 deletions
@@ -13,11 +13,12 @@ The client SHALL keep one explicit owned authoritative `PlayerState` snapshot fo
- **THEN** presentation and diagnostics read authoritative `position`, `rotation`, `hp`, and optional `velocity` from that owned snapshot
### Requirement: Local player reconciliation applies the full authoritative state by tick
The controlled client SHALL continue reconciling local prediction from authoritative `PlayerState` snapshots while keeping authoritative HP and optional velocity synchronized with the owned player-state snapshot. Reconciliation MUST use the acknowledged movement-input tick defined by the sync strategy, and the visible controlled-player transform MUST keep authoritative gameplay truth separate from short-lived visual correction state. Small divergence after replay MUST converge through explicit bounded correction state, while large divergence or failed convergence MUST still snap immediately to authoritative `position` and `rotation`.
The controlled client SHALL continue reconciling local prediction from authoritative `PlayerState` snapshots while keeping authoritative HP and optional velocity synchronized with the owned player-state snapshot. Reconciliation MUST use the acknowledged movement-input tick defined by the sync strategy, and the visible controlled-player transform MUST keep authoritative gameplay truth separate from short-lived visual correction state. **Replay of pending inputs during reconciliation MUST use fixed-step substeps matching the server authoritative movement cadence, producing identical trajectory to live prediction for the same input sequence.** Small divergence after replay MUST converge through explicit bounded correction state, while large divergence or failed convergence MUST still snap immediately to authoritative `position` and `rotation`.
#### Scenario: Local authoritative state corrects predicted presentation
- **WHEN** the controlled player accepts an authoritative `PlayerState` whose acknowledged movement-input tick is `N`
- **THEN** local reconciliation prunes or replays predicted movement using tick `N` according to the sync strategy
- **THEN** the replay uses fixed-step substeps matching the server authoritative movement cadence
- **THEN** the controlled player's authoritative gameplay state updates immediately to the accepted `position`, `rotation`, HP, and optional velocity
- **THEN** the local player's visible transform may temporarily differ only through bounded visual correction state that converges back to the authoritative baseline
@@ -31,6 +32,12 @@ The controlled client SHALL continue reconciling local prediction from authorita
- **THEN** the controlled player's visible transform snaps immediately to authoritative `position` and `rotation`
- **THEN** any temporary visual correction state is cleared before later local prediction resumes from that authoritative baseline
#### Scenario: Replay produces identical trajectory to live prediction
- **WHEN** the controlled player replays pending inputs after accepting authoritative `PlayerState`
- **THEN** the replay applies inputs in fixed-duration substeps equal to the server authoritative movement cadence
- **THEN** the final predicted pose equals what live `FixedUpdate` prediction would produce for the same input sequence
- **THEN** the result is stable across multiple replays of the same input sequence
### Requirement: Remote players apply authoritative state without inventing gameplay truth
Remote player presentation SHALL consume the accepted authoritative player-state snapshot owned by the client and MUST NOT invent HP or final gameplay state locally. Remote movement presentation MUST smooth authoritative position and rotation through a small buffered snapshot interpolation path instead of applying only the latest snapshot directly. Stale remote `PlayerState` packets that are older than the latest accepted authoritative tick for that player MUST NOT overwrite the owned snapshot or enter the interpolation buffer.
@@ -0,0 +1,30 @@
# client-prediction-cadence Specification
## Purpose
Define that client forward prediction accumulation uses an explicit cadence derived from the server authoritative movement cadence, not `Time.fixedDeltaTime`, ensuring prediction timing aligns with authoritative timing in reconciliation-sensitive paths.
## Requirements
### Requirement: Forward prediction accumulation uses authoritative cadence
The controlled-client forward prediction path SHALL accumulate pending input duration using the server authoritative movement cadence as the unit of accumulation, not `Time.fixedDeltaTime` or other render-loop-derived values. This ensures `SimulatedDurationSeconds` reflects server-time and remains coherent with the server's 50ms step cadence.
#### Scenario: Accumulation uses server cadence regardless of FixedUpdate interval
- **WHEN** the client FixedUpdate runs at a 20ms interval
- **THEN** `AccumulateLatest` adds `kServerSimulationStepSeconds` (50ms) to the pending input duration
- **THEN** the accumulated `SimulatedDurationSeconds` reflects server-time, not real elapsed time
#### Scenario: Accumulation cadence is decoupled from frame rate
- **WHEN** FixedUpdate runs at a non-standard interval due to platform variation or frame drops
- **THEN** the accumulation unit remains `kServerSimulationStepSeconds`
- **THEN** prediction timing does not drift relative to the server's authoritative cadence
### Requirement: Forward prediction and replay use the same cadence source
The controlled-client prediction system SHALL use the same cadence source for both forward accumulation and replay substepping, ensuring that `SimulatedDurationSeconds` consumed during replay matches the cadence used during forward prediction.
#### Scenario: Forward accumulated duration matches replay substep size
- **WHEN** the client accumulates pending input for 100ms of server-time
- **THEN** the replay path consumes the same 100ms in 50ms substeps
- **THEN** the forward accumulated duration and replay duration are derived from the same cadence constant
@@ -0,0 +1,34 @@
# client-prediction-diagnostics Specification
## Purpose
Define diagnostics that expose per-snapshot prediction state for regression testing and runtime debugging, enabling verification that replay produces identical trajectories to live prediction and that small server tick offset fluctuations do not cause visible local cadence oscillation.
## Requirements
### Requirement: Authoritative snapshot exposes acknowledged move tick
The client prediction system SHALL expose the acknowledged movement-input tick from the most recently accepted authoritative `PlayerState` snapshot.
#### Scenario: Diagnostics report acknowledged move tick
- **WHEN** the client accepts an authoritative `PlayerState`
- **THEN** diagnostics can read the acknowledged move tick from that snapshot
- **THEN** this value is available for regression tests and runtime debugging
### Requirement: Authoritative snapshot exposes predicted vs authoritative pose
The client prediction system SHALL expose both the locally predicted pose and the authoritative pose for the controlled player at each snapshot.
#### Scenario: Diagnostics report predicted and authoritative poses
- **WHEN** the client has a locally predicted pose and receives an authoritative `PlayerState`
- **THEN** diagnostics can read both the predicted pose and the authoritative pose
- **THEN** the correction magnitude (difference between predicted and authoritative) is computable
### Requirement: Authoritative snapshot exposes correction magnitude
The client prediction system SHALL expose the correction magnitude applied during reconciliation for regression testing.
#### Scenario: Diagnostics report correction magnitude
- **WHEN** the client reconciles from authoritative `PlayerState`
- **THEN** diagnostics can read the correction magnitude applied
- **THEN** this value is available to verify that small server tick offset fluctuations do not cause excessive local corrections
@@ -0,0 +1,36 @@
# client-prediction-replay Specification
## Purpose
Define the contract that client-side replay of pending movement inputs after authoritative state acknowledgement uses fixed-step substeps matching the server authoritative movement cadence, not a single accumulated duration, so that replay trajectory matches live prediction trajectory for the same input sequence.
## Requirements
### Requirement: Replay uses fixed-step accumulation matching server cadence
The controlled-client prediction replay path SHALL consume each pending `PredictedMoveStep` by applying its input in fixed-duration substeps equal to the server authoritative movement cadence, regardless of the step's total `SimulatedDurationSeconds`. **Forward prediction accumulation SHALL also use the same server authoritative movement cadence as the unit of accumulation, ensuring forward accumulated duration and replay duration are derived from the same cadence constant.** The replay accumulation shape MUST be identical to the live `FixedUpdate` prediction path for the same input values.
#### Scenario: Replay produces same trajectory as live prediction for steady input
- **WHEN** the client replays a `PredictedMoveStep` with turn=0, throttle=1, duration=0.15s using a 0.05s server cadence
- **THEN** the replay applies 0.05s + 0.05s + 0.05s substeps in sequence
- **THEN** the final predicted position matches the position that would result from three consecutive FixedUpdate predictions of 0.05s each with the same input
#### Scenario: Replay produces same trajectory as live prediction for turn-and-move input
- **WHEN** the client replays a `PredictedMoveStep` with turn=0.5, throttle=1, duration=0.10s using a 0.05s server cadence
- **THEN** the replay applies two 0.05s substeps where each substep's heading affects the next substep's forward direction
- **THEN** the final predicted heading and position match the live prediction path for the same input sequence
#### Scenario: Replay handles non-multiples of cadence interval
- **WHEN** the client replays a `PredictedMoveStep` with duration=0.12s using a 0.05s cadence
- **THEN** the replay applies 0.05s + 0.05s + 0.02s substeps sequentially
- **THEN** no remaining duration is lost or double-counted
### Requirement: Replay trajectory determinism is verifiable
The client prediction system SHALL provide a deterministic way to verify that replay and live prediction produce identical trajectories for a given input sequence, enabling regression coverage.
#### Scenario: Replay and live prediction produce identical results
- **WHEN** a controlled client records a `MoveInput` sequence during live play
- **AND** the client triggers reconciliation and replays those same inputs
- **THEN** the final predicted pose after replay equals the predicted pose that would result from live FixedUpdate simulation for the same input sequence
- **THEN** the result is stable across multiple replays of the same input sequence
@@ -0,0 +1,36 @@
# client-send-interval-stabilization Specification
## Purpose
Define that the client send interval is protected from oscillation when the server tick offset hovers near zero, ensuring steady-rate input submission without frame-to-frame cadence jitter.
## Requirements
### Requirement: Send interval correction uses hysteresis dead-band
The controlled-client send interval corrector SHALL apply a dead-band threshold before adjusting `_sendInterval`, so that minor server tick offset fluctuations near zero do not cause the send cadence to toggle between values.
#### Scenario: No correction within dead-band
- **WHEN** `_currentTickOffset` is between -2 and +2 ticks (inclusive)
- **THEN** `_sendInterval` is not changed
- **THEN** the previously active send interval is preserved
#### Scenario: Slow drift correction below threshold
- **WHEN** `_currentTickOffset` stays within the dead-band for an extended period
- **THEN** `_sendInterval` remains stable at its current value
- **THEN** no oscillation occurs regardless of offset sign changes within the band
#### Scenario: Correction applies outside dead-band
- **WHEN** `_currentTickOffset` exceeds +2 (client ahead of server)
- **THEN** `_sendInterval` is set to 0.048f to send slightly faster
- **WHEN** `_currentTickOffset` is below -2 (client behind server)
- **THEN** `_sendInterval` is set to 0.052f to send slightly slower
### Requirement: Send interval stabilizes after offset crosses threshold
Once the offset exits the dead-band and triggers a correction, subsequent corrections SHALL only occur when the offset crosses the threshold again in the opposite direction, preventing rapid re-correction.
#### Scenario: Correction latches until opposite threshold
- **WHEN** offset triggers a correction to 0.048f (offset > +2)
- **THEN** further offset increases within the same sign do not re-trigger correction
- **THEN** the send interval stays at 0.048f until offset crosses back below +2 then exceeds -2