Skip to content

Releases: wallstop/fortress-rollback

v0.8.0

30 Mar 00:13

Choose a tag to compare

Changed

  • Breaking: FortressEvent::ReplayDesync — new variant added. Since FortressEvent is not #[non_exhaustive], exhaustive matches must now handle this variant.
  • Breaking: InvalidFrameReason::ReplayExhausted — new variant added. Since InvalidFrameReason is not #[non_exhaustive], exhaustive matches must now handle this variant.
  • Breaking: Config::Input now requires Eq in addition to PartialEq. Types used as Config::Input must derive or implement Eq. This ensures reflexive equality, which is a correctness requirement for deterministic rollback — non-reflexive types (e.g., floats with NaN) would cause phantom prediction misses and unnecessary rollbacks. All integer and struct-of-integer types already implement Eq; add #[derive(Eq)] to any custom input types that are missing it.

Added

  • ReplaySession::new_with_validation() constructor that enables checksum validation mode, emitting SaveGameState requests, comparing checksums against the replay recording, and flushing final-frame validation when events() is drained after completion
  • ReplaySession::is_validating() accessor to check if checksum validation mode is enabled
  • SessionBuilder::start_replay_session_with_validation() builder method for creating a validation-enabled replay session
  • SessionTelemetry trait for observing session performance events (rollbacks, prediction misses, frame advances, network stats)
  • CollectingTelemetry test helper that accumulates TelemetryEvent values for assertions
  • TelemetryEvent enum with Rollback, PredictionMiss, NetworkStatsUpdate, and FrameAdvance variants
  • SessionBuilder::with_telemetry() to attach a telemetry observer to P2P sessions
  • Replay<I> type for recorded match data with to_bytes() / from_bytes() serialization using deterministic bincode codec
  • ReplayMetadata type containing library version, player count, total frame count, and skipped frame count
  • ReplaySession<T> session type implementing Session<T> for deterministic replay playback
  • SessionBuilder::with_recording(bool) to enable input recording (including game state checksums) during P2P sessions
  • SessionBuilder::start_replay_session(replay) to create a replay playback session
  • P2PSession::is_recording() to check if replay recording is active
  • P2PSession::into_replay() to extract the recorded Replay after a session ends (consumes the session)
  • P2PSession::take_replay() to extract the recorded Replay without consuming the session (recording stops after extraction)
  • Replay::validate() to verify internal consistency of replay data
  • Re-exports Replay, ReplayMetadata, and ReplaySession in prelude

v0.7.0

21 Mar 01:53
c5930e1

Choose a tag to compare

Added

  • ClockFn type alias (Arc<dyn Fn() -> Instant + Send + Sync>) for injectable time sources, enabling deterministic time control in tests and simulations
  • ProtocolConfig::clock field for overriding the system clock in the network protocol, allowing deterministic simulation testing (DST) and controlled time progression
  • ChaosSocket::with_clock() builder method for injecting a custom clock into the chaos socket, enabling deterministic latency simulation

Changed

  • Breaking: ProtocolConfig no longer implements Copy due to the addition of the clock field (Option<Arc<dyn Fn>>). Use .clone() instead where Copy was previously relied upon.

v0.6.0

07 Feb 19:16
a94e5f2

Choose a tag to compare

Added

  • Session<T: Config> trait — unified interface for P2PSession, SpectatorSession, and SyncTestSession, enabling generic code that works with any session type
  • RequestVec<T> — stack-allocated SmallVec<[FortressRequest<T>; 4]> for frame advance requests, avoiding heap allocation in the common case
  • EventDrain<'_, T> — zero-allocation opaque iterator for session events, replacing direct std::collections::vec_deque::Drain exposure
  • SyncTestSession::events() — drain pending events for API consistency with P2PSession and SpectatorSession (currently always empty; enables future desync-detection events)
  • InvalidRequestKind::NotSupported variant for operations not supported by a particular session type (e.g., add_local_input on a spectator session)

Changed

  • Breaking: P2PSession::advance_frame() now returns FortressResult<RequestVec<T>> instead of Result<Vec<FortressRequest<T>>, FortressError>. RequestVec implements Deref<Target = [FortressRequest<T>]> and IntoIterator, so most code (including handle_requests!) works unchanged. Use .to_vec() if you need a Vec.
  • Breaking: SpectatorSession::advance_frame() now returns FortressResult<RequestVec<T>> instead of Result<Vec<FortressRequest<T>>, FortressError>
  • Breaking: SyncTestSession::advance_frame() now returns FortressResult<RequestVec<T>> instead of Result<Vec<FortressRequest<T>>, FortressError>
  • Breaking: P2PSession::events() now returns EventDrain<'_, T> instead of std::collections::vec_deque::Drain<'_, FortressEvent<T>>
  • Breaking: SpectatorSession::events() now returns EventDrain<'_, T> instead of std::collections::vec_deque::Drain<'_, FortressEvent<T>>
  • Breaking: Added InvalidRequestKind::NotSupported variant for unsupported session operations. Exhaustive matches on InvalidRequestKind must now handle this new variant.

v0.5.0

06 Feb 01:00
780e549

Choose a tag to compare

Added

  • HandleVec type alias — stack-allocated SmallVec<[PlayerHandle; 8]> for zero-allocation player handle queries
  • Zero-allocation iterator methods for PlayerRegistry:
    • local_player_handles_iter() — iterate over local players without allocation
    • remote_player_handles_iter() — iterate over remote players without allocation
    • spectator_handles_iter() — iterate over spectators without allocation
    • all_player_handles_iter() — iterate over all handles without allocation
    • handles_by_address_iter(addr) — iterate over handles by address without allocation
  • Zero-allocation iterator methods for P2PSession:
    • local_player_handles_iter() — iterate over local players without allocation
    • remote_player_handles_iter() — iterate over remote players without allocation
    • spectator_handles_iter() — iterate over spectators without allocation
    • all_player_handles_iter() — iterate over all handles without allocation
    • handles_by_address_iter(addr) — iterate over handles by address without allocation
  • Zero-allocation iterator method for SyncTestSession:
    • local_player_handles_iter() — iterate over local players without allocation
  • PlayerRegistry convenience methods for player type queries:
    • is_local_player(handle) — check if handle is a local player
    • is_remote_player(handle) — check if handle is a remote player
    • is_spectator_handle(handle) — check if handle is a spectator
    • player_type(handle) — get the PlayerType for a handle
    • num_local_players() — count of local players
    • num_remote_players() — count of remote players (excluding spectators)
    • all_player_handles() — all registered handles
    • remote_player_handle_required() — returns error if not exactly 1 remote player
  • P2PSession convenience methods for 1-local-player games:
    • local_player_handle() — first local player handle (returns Option)
    • local_player_handle_required() — returns error if not exactly 1 local player
    • remote_player_handle() — first remote player handle
    • remote_player_handle_required() — returns error if not exactly 1 remote player
    • is_local_player(handle) — check if handle is a local player
    • is_remote_player(handle) — check if handle is a remote player
    • is_spectator_handle(handle) — check if handle is a spectator
    • player_type(handle) — get the PlayerType for a handle
    • num_local_players() — count of local players
    • num_remote_players() — count of remote players
    • all_player_handles() — all registered handles
  • SyncTestSession convenience methods:
    • local_player_handles() — all player handles (all are local in sync test)
    • local_player_handle() — first local player handle (returns Option)
    • local_player_handle_required() — returns error if not exactly 1 player
  • Display impl for core types: Frame, PlayerHandle, DesyncDetection, PlayerType, SessionState, InputStatus, FortressEvent, FortressRequest — enables human-readable formatting for logging and debugging
  • Display impl for configuration types: SyncConfig, ProtocolConfig, SpectatorConfig, InputQueueConfig, TimeSyncConfig, SaveMode — enables configuration summary output
  • Display impl for network types: NetworkStats, ConnectionStatus, ProtocolState, Event, ChaosConfig, ChaosStats — enables network diagnostics logging
  • Display impl for sync types: SyncHealth — enables sync status display
  • Display impl for prediction strategies: RepeatLastConfirmed, BlankPrediction — enables strategy identification in logs
  • Display impl for error types: FortressError, IndexOutOfBounds, InvalidFrameReason, RleDecodeReason, DeltaDecodeReason, InternalErrorKind, InvalidRequestKind, SerializationErrorKind, SocketErrorKind — enables structured error output
  • Display impl for checksum types: ChecksumAlgorithm, ChecksumError — enables checksum diagnostics
  • Display impl for telemetry types: ViolationSeverity, ViolationKind, SpecViolation, InvariantViolation — enables telemetry output

Changed

  • Breaking: PlayerRegistry::local_player_handles() now returns HandleVec instead of Vec<PlayerHandle>. HandleVec implements Deref<Target = [PlayerHandle]>, so most code using .iter(), .len(), or slice operations works unchanged. Use .to_vec() if you need a Vec.
  • Breaking: PlayerRegistry::remote_player_handles() now returns HandleVec instead of Vec<PlayerHandle>
  • Breaking: PlayerRegistry::spectator_handles() now returns HandleVec instead of Vec<PlayerHandle>
  • Breaking: PlayerRegistry::all_player_handles() now returns HandleVec instead of Vec<PlayerHandle>
  • Breaking: PlayerRegistry::handles_by_address() now returns HandleVec instead of Vec<PlayerHandle>
  • Breaking: P2PSession::local_player_handles() now returns HandleVec instead of Vec<PlayerHandle>
  • Breaking: P2PSession::remote_player_handles() now returns HandleVec instead of Vec<PlayerHandle>
  • Breaking: P2PSession::spectator_handles() now returns HandleVec instead of Vec<PlayerHandle>
  • Breaking: P2PSession::all_player_handles() now returns HandleVec instead of Vec<PlayerHandle>
  • Breaking: P2PSession::handles_by_address() now returns HandleVec instead of Vec<PlayerHandle>
  • Breaking: SyncTestSession::local_player_handles() now returns HandleVec instead of Vec<PlayerHandle>
  • Breaking: Added InvalidRequestKind::NoLocalPlayers variant — exhaustive matches on InvalidRequestKind must now handle this case
  • Breaking: Added InvalidRequestKind::MultipleLocalPlayers variant — exhaustive matches on InvalidRequestKind must now handle this case
  • Breaking: Added InvalidRequestKind::NoRemotePlayers variant — exhaustive matches on InvalidRequestKind must now handle this case
  • Breaking: Added InvalidRequestKind::MultipleRemotePlayers variant — exhaustive matches on InvalidRequestKind must now handle this case
  • Breaking: P2PSession::is_spectator() renamed to is_spectator_handle() for consistency with PlayerRegistry. Update calls from session.is_spectator(handle) to session.is_spectator_handle(handle).
  • Optimized convenience methods local_player_handle(), remote_player_handle(), local_player_handle_required(), and remote_player_handle_required() to use iterators directly, avoiding temporary allocations

v0.4.1

31 Jan 23:25
7abd66f

Choose a tag to compare

  • Breaking: PlayerHandle Display format changed from raw index (0) to labeled format (PlayerHandle(0)) for clearer log output. Update any code that parses PlayerHandle Display output.

v0.4.0

30 Jan 21:37
129b490

Choose a tag to compare

Added

  • GameStateCell::load_or_err() method for strict state loading with proper error handling
  • SessionBuilder::with_lan_defaults() preset for low-latency LAN play
  • SessionBuilder::with_internet_defaults() preset for typical online play
  • SessionBuilder::with_high_latency_defaults() preset for mobile/unstable connections
  • Frame ergonomic methods for safe arithmetic and conversion:
    • as_usize(), try_as_usize() — convert to usize with Option/Result
    • buffer_index(size), try_buffer_index(size) — ring buffer index calculation
    • try_add(i32), try_sub(i32) — Result-returning arithmetic
    • next(), prev() — Result-returning increment/decrement
    • saturating_next(), saturating_prev() — saturating increment/decrement
    • from_usize(usize), try_from_usize(usize) — safe construction from usize
    • distance_to(Frame) — signed distance calculation
    • is_within(window, reference) — window proximity check
  • Debug impl for P2PSession, SpectatorSession, and SyncTestSession — enables logging session state for debugging
  • Debug impl for ChaosSocket — shows config, stats, and packet queue length
  • Debug impl for GameStateAccessor — delegates to inner T when T: Debug
  • PartialEq derive for ChaosConfig — enables configuration comparison in tests
  • Hash derive for ChaosStats, NetworkStats, and Pcg32 — enables use as map keys
  • Copy, PartialEq, Eq, and Hash derives for TracingObserver unit struct
  • Hash derive for configuration types: TimeSyncConfig, SyncConfig, ProtocolConfig, SpectatorConfig, InputQueueConfig — enables use as map keys for configuration caching
  • PartialEq, Eq, and Hash derives for DeterministicHasher and DeterministicBuildHasher — enables comparison and use as map keys

Changed

  • Breaking: Added InvalidFrameReason::MissingState variant — exhaustive matches on InvalidFrameReason must now handle this case
  • Breaking: Added FortressError::FrameArithmeticOverflow variant — exhaustive matches on FortressError must now handle this case
  • Breaking: Added FortressError::FrameValueTooLarge variant — exhaustive matches on FortressError must now handle this case
  • Breaking: Added InvalidRequestKind::ZeroBufferSize variant — exhaustive matches on InvalidRequestKind must now handle this case

v0.3.0

29 Jan 04:23
0098310

Choose a tag to compare

Added

  • SessionBuilder::add_local_player() convenience method for adding local players
  • SessionBuilder::add_remote_player() convenience method for adding remote players
  • P2PSession::local_player_handle() for easily getting the first local player handle
  • ProtocolConfig now re-exported in fortress_rollback::prelude
  • sync_test example demonstrating SyncTestSession determinism verification
  • request_handling example demonstrating both manual matching and the handle_requests! macro
  • Structured error reason types for zero-allocation error construction and programmatic inspection:
    • IndexOutOfBounds struct for out-of-bounds errors with collection name, index, and length
    • InvalidFrameReason, RleDecodeReason, DeltaDecodeReason enums for specific failure modes
    • InternalErrorKind, InvalidRequestKind, SerializationErrorKind, SocketErrorKind enums
  • New FortressError variants using structured types: InvalidFrameStructured, InternalErrorStructured, InvalidRequestStructured, SerializationErrorStructured, SocketErrorStructured
  • ChecksumAlgorithm and CodecOperation enums for identifying operations in errors
  • CompressionError enum for RLE and delta decode errors

Changed

  • Breaking: Removed #[non_exhaustive] from FortressError, FortressEvent, FortressRequest, ViolationKind, CompressionError, CodecOperation, CodecError, ChecksumError, ChecksumAlgorithm, InvalidFrameReason, RleDecodeReason, DeltaDecodeReason, InternalErrorKind, InvalidRequestKind, SerializationErrorKind, and SocketErrorKind — users can now write exhaustive matches without wildcard arms
  • Breaking: ChecksumError::SerializationFailed now uses struct fields { algorithm, message } instead of tuple
  • Breaking: CodecError::EncodeError and DecodeError now use struct fields { message, operation } instead of tuple

v0.2.2

23 Jan 00:34
20145ec

Choose a tag to compare

Fixed

  • Removed the possibility for an internal panic under debug mode.

Changed

  • Breaking: Renamed Result type alias to FortressResult to avoid shadowing std::result::Result when using glob imports (use fortress_rollback::*)

v0.2.1

27 Dec 00:56
8e8b8ee

Choose a tag to compare

Added

  • ProtocolConfig::deterministic(seed) preset for fully reproducible network sessions
  • ProtocolConfig::protocol_rng_seed field for deterministic RNG seeding
  • SessionBuilder::with_event_queue_size() for configurable event queue capacity
  • ProtocolConfig::input_history_multiplier field with presets (competitive=2, high_latency=3)
  • ProtocolConfig::validate() method for configuration validation
  • #[must_use] attributes on key session methods (advance_frame(), disconnect_player(), etc.)

Changed

  • Breaking: SessionBuilder::with_input_delay() and with_num_players() now return Result<Self, FortressError> instead of silently clamping invalid values
  • Replaced floating-point arithmetic with integer-only calculation in TimeSync::average_frame_advantage() to eliminate potential non-determinism
  • Replaced DefaultHasher with DeterministicHasher (FNV-1a) in timing_entropy_seed() for cross-platform consistency
  • Reduced cloning overhead in poll_remote_clients() by using Arc<[PlayerHandle]> instead of Vec<PlayerHandle>
  • Pre-allocated compression buffers to reduce allocations in network hot paths

Fixed

  • Fixed sync timeout event flooding that could occur under certain conditions

v0.2.0

20 Dec 07:42
66f7840

Choose a tag to compare

Added

  • Added optional json feature for JSON serialization of telemetry types
    • Provides to_json() and to_json_pretty() methods on SpecViolation and InvariantViolation
    • Enable with features = ["json"] in Cargo.toml
  • Added SyncConfig::extreme() preset for very hostile network conditions
    • Sends 20 sync packets (vs 10 for mobile, 5 for default) with 250ms retry intervals
    • 30-second sync timeout to handle multiple burst loss events
    • Designed for scenarios with 10%+ burst loss probability and 8+ packet burst lengths
    • Not recommended for production use due to long timeouts
  • Added burst loss recommendations to packet loss documentation table

Changed

  • Breaking: serde_json is now an optional dependency behind the json feature
    • Reduces default dependency count from 7 to 6 production dependencies
    • Users who need to_json() methods must enable the json feature
    • The telemetry types still implement serde::Serialize for use with any serializer
  • Restructured test code to further reduce published crate size
    • Moved network peer binary to separate tests/network-peer crate
    • Excluded additional test infrastructure from published package

Fixed

  • Fixed flaky CI tests on macOS under high load by using more robust timing margins
  • Improved test reliability for high packet loss scenarios using appropriate sync presets