Add MockBitStream and sync structure round-trip tests#4791
Add MockBitStream and sync structure round-trip tests#4791Lpsd wants to merge 4 commits intomultitheftauto:masterfrom
Conversation
cc @Lpsd please make sure to run clang-format locally before pushing changes to avoid this in the future.
Introduces a MockBitStream test double & comprehensive round-trip serialization tests for all network sync structures used in MTA
qaisjp
left a comment
There was a problem hiding this comment.
This looks great. I didn't closely analyse each test but the approach you've described is sound.
I gave Claude Code (Opus model) your code to review too, and it had this to say. Take it or leave it
Overview
This PR adds a
MockBitStreamtest double and 82 new round-trip serialization tests covering the network sync structures inSyncStructures.h. The approach is solid — write known values, serialize, reset cursor, deserialize, compare. Code quality is generally good with correct tolerance calculations throughout.Issues Found
Should Document (Medium)
MockBitStream bit ordering differs from RakNet —
WriteBits/ReadBitsuses LSB-first, while RakNet uses MSB-first. Round-trip tests are self-consistent so they pass, but this should be prominently documented. If anyone feeds real captured RakNet data into MockBitStream, it will silently produce wrong results.
WriteCompressed/ReadCompressedalgorithm differs from RakNet — The mock uses a simplified 1-flag-bit approach while RakNet iterates byte-by-byte with multiple flag bits. Again fine for round-trip but wire sizes differ. Should be documented.Misleading comment in
SHeatHazeSynctest (SyncMisc_Tests.cpp): Says "All are raw scalar types, so they should round-trip exactly" but several fields use bit-truncatedReadRange/WriteRange(10-bit and 11-bit). The test values happen to fit, but the comment is inaccurate.Missing Coverage (Low-Medium)
No
SSmallKeysyncSynctest — OnlySFullKeysyncSyncis tested, but the two structs have a subtle field-ordering difference (ucButtonCross/ucButtonSquareorder) that could hide bugs.
SWeaponAimSyncNormVector Z/Y swap untested — The sync structure swaps Y and Z inWriteNormVector/ReadNormVectorcalls, but the test uses direction(1, 0, 0)where Y=Z=0, making the swap invisible. A diagonal aim direction test would catch component ordering bugs.No negative rotation test — Rotation degrees sync casts to
unsigned short, so negative inputs wrap around. No test covers this behavior.
SUnoccupiedVehicleSynconly tests position+health — MissingbSyncVelocity = truewhich would exercise the NormVector path through the composite structure.Nits
SHeatHazeSyncandsWeaponPropertySyncdon't verifyGetNumberOfBitsUsed()— Unlike most other tests which check wire size.
AlignWriteToByteBoundaryusesconst_cast— Works but is technically UB. Usingmutableon the cursor fields would be cleaner.Verdict
Good first PR for test coverage of critical network code. The core approach is sound and tolerances are well-calculated. The main concern is that the MockBitStream diverges from RakNet semantics in non-obvious ways (bit ordering, compression algorithm) that should be explicitly documented so future maintainers don't misuse the mock for wire-format compatibility testing. The missing test cases (items 4-7) would strengthen coverage of subtle edge cases.
Summary
Introduces a MockBitStream test double and comprehensive round-trip serialization tests for all network sync structures used in mtasa-blue.
Motivation
The sync structures in
SyncStructures.hare critical to MTA's network protocol - they serialize and deserialize player state, vehicle state, weapons, and more. Until now, none of them had unit tests. A bug in any sync structure could cause desyncs, crashes, or data corruption that would be difficult to diagnose in production.What changed
New file:
Tests/client/MockBitStream.hAn in-memory implementation of
NetBitStreamInterfacethat supports:ReadBits/WriteBits,ReadBit/WriteBitRead<T>/Write<T>for primitives (float,short,unsigned char, etc.)ReadCompressed/WriteCompressed(leading-zero suppression forunsigned short)ReadNormVector/WriteNormVector(simplified 16-bit-per-component encoding)Read(ISyncStructure*)/Write(ISyncStructure*)delegationNew client test files (82 new tests, 299 total):
MockBitStream_Tests.cppSyncDataTypes_Tests.cppSFloatSync,SIntegerSync,SFloatAsBitsSync, health/armor structsSyncMovement_Tests.cppSPositionSync,SPosition2DSync,SLowPrecisionPositionSync,SRotationDegreesSync,SRotationRadiansSync,SVelocitySyncSyncPlayer_Tests.cppSPlayerPuresyncFlags,SPedRotationSync,SCameraRotationSync,SKeysyncRotation,SFullKeysyncSync,SWeaponSlotSync,SWeaponTypeSync,SWeaponAmmoSync,SWeaponAimSync,SBodypartSyncSyncVehicle_Tests.cppSVehiclePuresyncFlags,SVehicleDamageSync,SVehicleDamageSyncMethodeB,SVehicleTurretSync,SDoorOpenRatioSync,SVehicleSirenAddSync,SVehicleSirenSync,SVehicleHandlingSync,SUnoccupiedVehicleSyncSyncMisc_Tests.cppSExplosionTypeSync,SMapInfoFlagsSync,SFunBugsStateSync,SWorldSpecialPropertiesStateSync,SEntityAlphaSync,SColorSync,SHeatHazeSync,sWeaponPropertySync, and moreWhy MockBitStream is reliable without RakNet
The sync structures don't depend on RakNet - they program against the
NetBitStreamInterfaceabstraction. MockBitStream implements that same interface with a simple in-memory byte buffer. As long asRead/Writesemantics are correct at the interface level, the tests exercise exactly the same serialization code paths that run in production.MockBitStream intentionally uses a simplified
WriteNormVector/ReadNormVector(16-bit signed per component × 3) rather than attempting to replicate the proprietary RakNet compression. Tests that useWriteNormVectorverify the sync structure's logic - not the compression algorithm - which is the correct boundary for unit testing.Testing approach
Every test follows the same pattern:
.datawith known valuesWrite()to a MockBitStreamRead()into a fresh instanceTest plan
Tests_Client.vcxprojin Debug/Win32 and ranBin\tests\Tests_Client_d.exeutils/clang-format.ps1- no formatting issues.SyncStructures.hto verify bit counts, ranges, and step sizes are accurate.All 299 tests pass.
Checklist