Releases: wallstop/fortress-rollback
Releases · wallstop/fortress-rollback
v0.8.0
Changed
- Breaking:
FortressEvent::ReplayDesync— new variant added. SinceFortressEventis not#[non_exhaustive], exhaustive matches must now handle this variant. - Breaking:
InvalidFrameReason::ReplayExhausted— new variant added. SinceInvalidFrameReasonis not#[non_exhaustive], exhaustive matches must now handle this variant. - Breaking:
Config::Inputnow requiresEqin addition toPartialEq. Types used asConfig::Inputmust derive or implementEq. This ensures reflexive equality, which is a correctness requirement for deterministic rollback — non-reflexive types (e.g., floats withNaN) would cause phantom prediction misses and unnecessary rollbacks. All integer and struct-of-integer types already implementEq; add#[derive(Eq)]to any custom input types that are missing it.
Added
ReplaySession::new_with_validation()constructor that enables checksum validation mode, emittingSaveGameStaterequests, comparing checksums against the replay recording, and flushing final-frame validation whenevents()is drained after completionReplaySession::is_validating()accessor to check if checksum validation mode is enabledSessionBuilder::start_replay_session_with_validation()builder method for creating a validation-enabled replay sessionSessionTelemetrytrait for observing session performance events (rollbacks, prediction misses, frame advances, network stats)CollectingTelemetrytest helper that accumulatesTelemetryEventvalues for assertionsTelemetryEventenum withRollback,PredictionMiss,NetworkStatsUpdate, andFrameAdvancevariantsSessionBuilder::with_telemetry()to attach a telemetry observer to P2P sessionsReplay<I>type for recorded match data withto_bytes()/from_bytes()serialization using deterministic bincode codecReplayMetadatatype containing library version, player count, total frame count, and skipped frame countReplaySession<T>session type implementingSession<T>for deterministic replay playbackSessionBuilder::with_recording(bool)to enable input recording (including game state checksums) during P2P sessionsSessionBuilder::start_replay_session(replay)to create a replay playback sessionP2PSession::is_recording()to check if replay recording is activeP2PSession::into_replay()to extract the recordedReplayafter a session ends (consumes the session)P2PSession::take_replay()to extract the recordedReplaywithout consuming the session (recording stops after extraction)Replay::validate()to verify internal consistency of replay data- Re-exports
Replay,ReplayMetadata, andReplaySessionin prelude
v0.7.0
Added
ClockFntype alias (Arc<dyn Fn() -> Instant + Send + Sync>) for injectable time sources, enabling deterministic time control in tests and simulationsProtocolConfig::clockfield for overriding the system clock in the network protocol, allowing deterministic simulation testing (DST) and controlled time progressionChaosSocket::with_clock()builder method for injecting a custom clock into the chaos socket, enabling deterministic latency simulation
Changed
- Breaking:
ProtocolConfigno longer implementsCopydue to the addition of theclockfield (Option<Arc<dyn Fn>>). Use.clone()instead whereCopywas previously relied upon.
v0.6.0
Added
Session<T: Config>trait — unified interface forP2PSession,SpectatorSession, andSyncTestSession, enabling generic code that works with any session typeRequestVec<T>— stack-allocatedSmallVec<[FortressRequest<T>; 4]>for frame advance requests, avoiding heap allocation in the common caseEventDrain<'_, T>— zero-allocation opaque iterator for session events, replacing directstd::collections::vec_deque::DrainexposureSyncTestSession::events()— drain pending events for API consistency withP2PSessionandSpectatorSession(currently always empty; enables future desync-detection events)InvalidRequestKind::NotSupportedvariant for operations not supported by a particular session type (e.g.,add_local_inputon a spectator session)
Changed
- Breaking:
P2PSession::advance_frame()now returnsFortressResult<RequestVec<T>>instead ofResult<Vec<FortressRequest<T>>, FortressError>.RequestVecimplementsDeref<Target = [FortressRequest<T>]>andIntoIterator, so most code (includinghandle_requests!) works unchanged. Use.to_vec()if you need aVec. - Breaking:
SpectatorSession::advance_frame()now returnsFortressResult<RequestVec<T>>instead ofResult<Vec<FortressRequest<T>>, FortressError> - Breaking:
SyncTestSession::advance_frame()now returnsFortressResult<RequestVec<T>>instead ofResult<Vec<FortressRequest<T>>, FortressError> - Breaking:
P2PSession::events()now returnsEventDrain<'_, T>instead ofstd::collections::vec_deque::Drain<'_, FortressEvent<T>> - Breaking:
SpectatorSession::events()now returnsEventDrain<'_, T>instead ofstd::collections::vec_deque::Drain<'_, FortressEvent<T>> - Breaking: Added
InvalidRequestKind::NotSupportedvariant for unsupported session operations. Exhaustive matches onInvalidRequestKindmust now handle this new variant.
v0.5.0
Added
HandleVectype alias — stack-allocatedSmallVec<[PlayerHandle; 8]>for zero-allocation player handle queries- Zero-allocation iterator methods for
PlayerRegistry:local_player_handles_iter()— iterate over local players without allocationremote_player_handles_iter()— iterate over remote players without allocationspectator_handles_iter()— iterate over spectators without allocationall_player_handles_iter()— iterate over all handles without allocationhandles_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 allocationremote_player_handles_iter()— iterate over remote players without allocationspectator_handles_iter()— iterate over spectators without allocationall_player_handles_iter()— iterate over all handles without allocationhandles_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
PlayerRegistryconvenience methods for player type queries:is_local_player(handle)— check if handle is a local playeris_remote_player(handle)— check if handle is a remote playeris_spectator_handle(handle)— check if handle is a spectatorplayer_type(handle)— get thePlayerTypefor a handlenum_local_players()— count of local playersnum_remote_players()— count of remote players (excluding spectators)all_player_handles()— all registered handlesremote_player_handle_required()— returns error if not exactly 1 remote player
P2PSessionconvenience methods for 1-local-player games:local_player_handle()— first local player handle (returnsOption)local_player_handle_required()— returns error if not exactly 1 local playerremote_player_handle()— first remote player handleremote_player_handle_required()— returns error if not exactly 1 remote playeris_local_player(handle)— check if handle is a local playeris_remote_player(handle)— check if handle is a remote playeris_spectator_handle(handle)— check if handle is a spectatorplayer_type(handle)— get thePlayerTypefor a handlenum_local_players()— count of local playersnum_remote_players()— count of remote playersall_player_handles()— all registered handles
SyncTestSessionconvenience methods:local_player_handles()— all player handles (all are local in sync test)local_player_handle()— first local player handle (returnsOption)local_player_handle_required()— returns error if not exactly 1 player
Displayimpl for core types:Frame,PlayerHandle,DesyncDetection,PlayerType,SessionState,InputStatus,FortressEvent,FortressRequest— enables human-readable formatting for logging and debuggingDisplayimpl for configuration types:SyncConfig,ProtocolConfig,SpectatorConfig,InputQueueConfig,TimeSyncConfig,SaveMode— enables configuration summary outputDisplayimpl for network types:NetworkStats,ConnectionStatus,ProtocolState,Event,ChaosConfig,ChaosStats— enables network diagnostics loggingDisplayimpl for sync types:SyncHealth— enables sync status displayDisplayimpl for prediction strategies:RepeatLastConfirmed,BlankPrediction— enables strategy identification in logsDisplayimpl for error types:FortressError,IndexOutOfBounds,InvalidFrameReason,RleDecodeReason,DeltaDecodeReason,InternalErrorKind,InvalidRequestKind,SerializationErrorKind,SocketErrorKind— enables structured error outputDisplayimpl for checksum types:ChecksumAlgorithm,ChecksumError— enables checksum diagnosticsDisplayimpl for telemetry types:ViolationSeverity,ViolationKind,SpecViolation,InvariantViolation— enables telemetry output
Changed
- Breaking:
PlayerRegistry::local_player_handles()now returnsHandleVecinstead ofVec<PlayerHandle>.HandleVecimplementsDeref<Target = [PlayerHandle]>, so most code using.iter(),.len(), or slice operations works unchanged. Use.to_vec()if you need aVec. - Breaking:
PlayerRegistry::remote_player_handles()now returnsHandleVecinstead ofVec<PlayerHandle> - Breaking:
PlayerRegistry::spectator_handles()now returnsHandleVecinstead ofVec<PlayerHandle> - Breaking:
PlayerRegistry::all_player_handles()now returnsHandleVecinstead ofVec<PlayerHandle> - Breaking:
PlayerRegistry::handles_by_address()now returnsHandleVecinstead ofVec<PlayerHandle> - Breaking:
P2PSession::local_player_handles()now returnsHandleVecinstead ofVec<PlayerHandle> - Breaking:
P2PSession::remote_player_handles()now returnsHandleVecinstead ofVec<PlayerHandle> - Breaking:
P2PSession::spectator_handles()now returnsHandleVecinstead ofVec<PlayerHandle> - Breaking:
P2PSession::all_player_handles()now returnsHandleVecinstead ofVec<PlayerHandle> - Breaking:
P2PSession::handles_by_address()now returnsHandleVecinstead ofVec<PlayerHandle> - Breaking:
SyncTestSession::local_player_handles()now returnsHandleVecinstead ofVec<PlayerHandle> - Breaking: Added
InvalidRequestKind::NoLocalPlayersvariant — exhaustive matches onInvalidRequestKindmust now handle this case - Breaking: Added
InvalidRequestKind::MultipleLocalPlayersvariant — exhaustive matches onInvalidRequestKindmust now handle this case - Breaking: Added
InvalidRequestKind::NoRemotePlayersvariant — exhaustive matches onInvalidRequestKindmust now handle this case - Breaking: Added
InvalidRequestKind::MultipleRemotePlayersvariant — exhaustive matches onInvalidRequestKindmust now handle this case - Breaking:
P2PSession::is_spectator()renamed tois_spectator_handle()for consistency withPlayerRegistry. Update calls fromsession.is_spectator(handle)tosession.is_spectator_handle(handle). - Optimized convenience methods
local_player_handle(),remote_player_handle(),local_player_handle_required(), andremote_player_handle_required()to use iterators directly, avoiding temporary allocations
v0.4.1
- Breaking:
PlayerHandleDisplay format changed from raw index (0) to labeled format (PlayerHandle(0)) for clearer log output. Update any code that parsesPlayerHandleDisplay output.
v0.4.0
Added
GameStateCell::load_or_err()method for strict state loading with proper error handlingSessionBuilder::with_lan_defaults()preset for low-latency LAN playSessionBuilder::with_internet_defaults()preset for typical online playSessionBuilder::with_high_latency_defaults()preset for mobile/unstable connectionsFrameergonomic methods for safe arithmetic and conversion:as_usize(),try_as_usize()— convert to usize with Option/Resultbuffer_index(size),try_buffer_index(size)— ring buffer index calculationtry_add(i32),try_sub(i32)— Result-returning arithmeticnext(),prev()— Result-returning increment/decrementsaturating_next(),saturating_prev()— saturating increment/decrementfrom_usize(usize),try_from_usize(usize)— safe construction from usizedistance_to(Frame)— signed distance calculationis_within(window, reference)— window proximity check
Debugimpl forP2PSession,SpectatorSession, andSyncTestSession— enables logging session state for debuggingDebugimpl forChaosSocket— shows config, stats, and packet queue lengthDebugimpl forGameStateAccessor— delegates to innerTwhenT: DebugPartialEqderive forChaosConfig— enables configuration comparison in testsHashderive forChaosStats,NetworkStats, andPcg32— enables use as map keysCopy,PartialEq,Eq, andHashderives forTracingObserverunit structHashderive for configuration types:TimeSyncConfig,SyncConfig,ProtocolConfig,SpectatorConfig,InputQueueConfig— enables use as map keys for configuration cachingPartialEq,Eq, andHashderives forDeterministicHasherandDeterministicBuildHasher— enables comparison and use as map keys
Changed
- Breaking: Added
InvalidFrameReason::MissingStatevariant — exhaustive matches onInvalidFrameReasonmust now handle this case - Breaking: Added
FortressError::FrameArithmeticOverflowvariant — exhaustive matches onFortressErrormust now handle this case - Breaking: Added
FortressError::FrameValueTooLargevariant — exhaustive matches onFortressErrormust now handle this case - Breaking: Added
InvalidRequestKind::ZeroBufferSizevariant — exhaustive matches onInvalidRequestKindmust now handle this case
v0.3.0
Added
SessionBuilder::add_local_player()convenience method for adding local playersSessionBuilder::add_remote_player()convenience method for adding remote playersP2PSession::local_player_handle()for easily getting the first local player handleProtocolConfignow re-exported infortress_rollback::preludesync_testexample demonstratingSyncTestSessiondeterminism verificationrequest_handlingexample demonstrating both manual matching and thehandle_requests!macro- Structured error reason types for zero-allocation error construction and programmatic inspection:
IndexOutOfBoundsstruct for out-of-bounds errors with collection name, index, and lengthInvalidFrameReason,RleDecodeReason,DeltaDecodeReasonenums for specific failure modesInternalErrorKind,InvalidRequestKind,SerializationErrorKind,SocketErrorKindenums
- New
FortressErrorvariants using structured types:InvalidFrameStructured,InternalErrorStructured,InvalidRequestStructured,SerializationErrorStructured,SocketErrorStructured ChecksumAlgorithmandCodecOperationenums for identifying operations in errorsCompressionErrorenum for RLE and delta decode errors
Changed
- Breaking: Removed
#[non_exhaustive]fromFortressError,FortressEvent,FortressRequest,ViolationKind,CompressionError,CodecOperation,CodecError,ChecksumError,ChecksumAlgorithm,InvalidFrameReason,RleDecodeReason,DeltaDecodeReason,InternalErrorKind,InvalidRequestKind,SerializationErrorKind, andSocketErrorKind— users can now write exhaustive matches without wildcard arms - Breaking:
ChecksumError::SerializationFailednow uses struct fields{ algorithm, message }instead of tuple - Breaking:
CodecError::EncodeErrorandDecodeErrornow use struct fields{ message, operation }instead of tuple
v0.2.2
Fixed
- Removed the possibility for an internal panic under debug mode.
Changed
- Breaking: Renamed
Resulttype alias toFortressResultto avoid shadowingstd::result::Resultwhen using glob imports (use fortress_rollback::*)
v0.2.1
Added
ProtocolConfig::deterministic(seed)preset for fully reproducible network sessionsProtocolConfig::protocol_rng_seedfield for deterministic RNG seedingSessionBuilder::with_event_queue_size()for configurable event queue capacityProtocolConfig::input_history_multiplierfield 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()andwith_num_players()now returnResult<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
DefaultHasherwithDeterministicHasher(FNV-1a) intiming_entropy_seed()for cross-platform consistency - Reduced cloning overhead in
poll_remote_clients()by usingArc<[PlayerHandle]>instead ofVec<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
Added
- Added optional
jsonfeature for JSON serialization of telemetry types- Provides
to_json()andto_json_pretty()methods onSpecViolationandInvariantViolation - Enable with
features = ["json"]in Cargo.toml
- Provides
- 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_jsonis now an optional dependency behind thejsonfeature- Reduces default dependency count from 7 to 6 production dependencies
- Users who need
to_json()methods must enable thejsonfeature - The telemetry types still implement
serde::Serializefor use with any serializer
- Restructured test code to further reduce published crate size
- Moved network peer binary to separate
tests/network-peercrate - Excluded additional test infrastructure from published package
- Moved network peer binary to separate
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