Skip to content

Add MockBitStream and sync structure round-trip tests#4791

Open
Lpsd wants to merge 4 commits intomultitheftauto:masterfrom
Lpsd:mock-game-tests
Open

Add MockBitStream and sync structure round-trip tests#4791
Lpsd wants to merge 4 commits intomultitheftauto:masterfrom
Lpsd:mock-game-tests

Conversation

@Lpsd
Copy link
Copy Markdown
Member

@Lpsd Lpsd commented Apr 10, 2026

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.h are 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.h

An in-memory implementation of NetBitStreamInterface that supports:

  • Bit-level read/write with separate cursors
  • ReadBits / WriteBits, ReadBit / WriteBit
  • Typed Read<T> / Write<T> for primitives (float, short, unsigned char, etc.)
  • ReadCompressed / WriteCompressed (leading-zero suppression for unsigned short)
  • ReadNormVector / WriteNormVector (simplified 16-bit-per-component encoding)
  • Read(ISyncStructure*) / Write(ISyncStructure*) delegation

New client test files (82 new tests, 299 total):

File Tests Coverage
MockBitStream_Tests.cpp 12 MockBitStream self-validation (bits, bytes, compressed, norm vectors)
SyncDataTypes_Tests.cpp 14 SFloatSync, SIntegerSync, SFloatAsBitsSync, health/armor structs
SyncMovement_Tests.cpp 11 SPositionSync, SPosition2DSync, SLowPrecisionPositionSync, SRotationDegreesSync, SRotationRadiansSync, SVelocitySync
SyncPlayer_Tests.cpp 16 SPlayerPuresyncFlags, SPedRotationSync, SCameraRotationSync, SKeysyncRotation, SFullKeysyncSync, SWeaponSlotSync, SWeaponTypeSync, SWeaponAmmoSync, SWeaponAimSync, SBodypartSync
SyncVehicle_Tests.cpp 14 SVehiclePuresyncFlags, SVehicleDamageSync, SVehicleDamageSyncMethodeB, SVehicleTurretSync, SDoorOpenRatioSync, SVehicleSirenAddSync, SVehicleSirenSync, SVehicleHandlingSync, SUnoccupiedVehicleSync
SyncMisc_Tests.cpp 15 SExplosionTypeSync, SMapInfoFlagsSync, SFunBugsStateSync, SWorldSpecialPropertiesStateSync, SEntityAlphaSync, SColorSync, SHeatHazeSync, sWeaponPropertySync, and more

Why MockBitStream is reliable without RakNet

The sync structures don't depend on RakNet - they program against the NetBitStreamInterface abstraction. MockBitStream implements that same interface with a simple in-memory byte buffer. As long as Read/Write semantics 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 use WriteNormVector verify 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:

  1. Construct a sync structure and populate .data with known values
  2. Write() to a MockBitStream
  3. Reset the read cursor
  4. Read() into a fresh instance
  5. Compare output values against expected results (accounting for quantization loss where applicable)

Test plan

  • Built Tests_Client.vcxproj in Debug/Win32 and ran Bin\tests\Tests_Client_d.exe
  • Ran utils/clang-format.ps1 - no formatting issues.
  • Audited every test comment against the actual struct definitions in SyncStructures.h to verify bit counts, ranges, and step sizes are accurate.
  • If you modify a sync structure in the future, rebuild and run the tests. Any round-trip breakage will show up as a failing test for that struct.

All 299 tests pass.

Checklist

  • Your code should follow the coding guidelines.
  • Smaller pull requests are easier to review. If your pull request is beefy, your pull request should be reviewable commit-by-commit.

github-actions bot and others added 4 commits April 9, 2026 20:46
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
@Lpsd Lpsd requested a review from a team as a code owner April 10, 2026 19:50
Copy link
Copy Markdown
Member

@qaisjp qaisjp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 MockBitStream test double and 82 new round-trip serialization tests covering the network sync structures in SyncStructures.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)

  1. MockBitStream bit ordering differs from RakNetWriteBits/ReadBits uses 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.

  2. WriteCompressed/ReadCompressed algorithm 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.

  3. Misleading comment in SHeatHazeSync test (SyncMisc_Tests.cpp): Says "All are raw scalar types, so they should round-trip exactly" but several fields use bit-truncated ReadRange/WriteRange (10-bit and 11-bit). The test values happen to fit, but the comment is inaccurate.

Missing Coverage (Low-Medium)

  1. No SSmallKeysyncSync test — Only SFullKeysyncSync is tested, but the two structs have a subtle field-ordering difference (ucButtonCross/ucButtonSquare order) that could hide bugs.

  2. SWeaponAimSync NormVector Z/Y swap untested — The sync structure swaps Y and Z in WriteNormVector/ReadNormVector calls, 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.

  3. No negative rotation test — Rotation degrees sync casts to unsigned short, so negative inputs wrap around. No test covers this behavior.

  4. SUnoccupiedVehicleSync only tests position+health — Missing bSyncVelocity = true which would exercise the NormVector path through the composite structure.

Nits

  1. SHeatHazeSync and sWeaponPropertySync don't verify GetNumberOfBitsUsed() — Unlike most other tests which check wire size.

  2. AlignWriteToByteBoundary uses const_cast — Works but is technically UB. Using mutable on 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants