From 58ed476d43b1a468fac57f504a52a88ef305ea19 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 15:33:57 +0200 Subject: [PATCH] test: extend coverage of testNetSession --- tests/testNetSession.cpp | 159 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/tests/testNetSession.cpp b/tests/testNetSession.cpp index 85c1342..2a5c906 100644 --- a/tests/testNetSession.cpp +++ b/tests/testNetSession.cpp @@ -310,3 +310,162 @@ VF_TEST(theClientWorldMirrorsTheServerByteForByteAcrossAnUnreliableLink) { pair.client.beginHandshake("vulp", 1, 2); + Lcg rng(0x2ee9d1); + const Block palette[4] = {Block::Glowstone, Block::Obsidian, Block::Ice, Block::Planks}; + uint64_t tick = 0; + for (; tick < 400; ++tick) { + if (pair.server.phase() == SessionPhase::Play && tick % 3 == 0) { + for (int i = 0; i < 4; ++i) { + int gx = rng.range(32) - 16; + int gz = rng.range(32) - 16; + int gy = rng.range(14); + pair.edit(gx, gy, gz, palette[rng.range(4)]); + } + } + pair.step(tick); + pair.clearDirty(); + } + // Editing stops; the link is given time to finish retransmitting the tail. + for (; tick < 600; ++tick) { + pair.step(tick); + pair.clearDirty(); + } + + CHECK(pair.server.phase() == SessionPhase::Play); + CHECK(pair.client.phase() == SessionPhase::Play); + CHECK(pair.link.droppedCount() > 50u); + CHECK(pair.serverTransport.stats().retransmitted > 0u); + + std::vector coords = sortedCoords(pair.server.view().sent); + CHECK_EQ(coords.size(), static_cast(13)); + CHECK_EQ(worldDigest(pair.serverWorld, coords), worldDigest(pair.clientWorld, coords)); + + // A digest that ignores content would pass anything: break one block and it + // must disagree. + pair.clientWorld.setBlock(0, 0, 0, Block::Bedrock); + CHECK(worldDigest(pair.serverWorld, coords) != worldDigest(pair.clientWorld, coords)); +} + +VF_TEST(entityPositionsRideTheUnreliableChannelAndOnlyTheNewestSurvives) { + LinkQuality quality; + quality.latencyTicks = 1; + quality.seed = 0x77777777ull; + TransportConfig transportConfig; + Pair pair(quality, transportConfig, quietConfig()); + + pair.client.beginHandshake("vulp", 1, 2); + for (uint64_t tick = 0; tick < 10; ++tick) pair.step(tick); + CHECK(pair.client.phase() == SessionPhase::Play); + + for (uint64_t tick = 10; tick < 60; ++tick) { + float progress = static_cast(tick - 10); + pair.server.sendEntityMove(42, Vec3{progress, 64.0f, -progress}, 0.5f, -0.25f, true); + pair.step(tick); + } + for (uint64_t tick = 60; tick < 70; ++tick) pair.step(tick); + + EntityMove seen; + CHECK(pair.client.entityMove(42, seen)); + CHECK_NEAR(seen.x, 49.0f, 0.001f); + CHECK_NEAR(seen.z, -49.0f, 0.001f); + CHECK_NEAR(seen.yaw, 0.5f, 0.001f); + CHECK(seen.onGround); + CHECK_EQ(pair.client.trackedEntityCount(), static_cast(1)); + CHECK_EQ(pair.server.stats().entityMovesSent, static_cast(50)); + CHECK_EQ(pair.serverTransport.stats().retransmitted, static_cast(0)); + + EntityMove missing; + CHECK(!pair.client.entityMove(43, missing)); +} + +VF_TEST(aPlayerStateReportMovesTheServerSideViewAndStreamsTheNewChunks) { + LinkQuality quality; + quality.latencyTicks = 1; + TransportConfig transportConfig; + SessionConfig config = quietConfig(); + config.viewRadius = 1; + config.chunksPerTick = 4; + Pair pair(quality, transportConfig, config); + buildTerrain(pair.serverWorld, 4); + + pair.client.beginHandshake("vulp", 1, 2); + for (uint64_t tick = 0; tick < 30; ++tick) pair.step(tick); + CHECK(pair.server.view().centreChunk == Vec3i(0, 0, 0)); + // Radius 1 is a 7-slot sphere; five of those slots sit in the y = 0 plane. + std::size_t nearOrigin = pair.server.view().sent.size(); + CHECK_EQ(nearOrigin, static_cast(5)); + + PlayerState moved; + moved.x = 40.0f; + moved.y = 8.0f; + moved.z = 40.0f; + moved.health = 20.0f; + pair.client.sendPlayerState(moved); + for (uint64_t tick = 30; tick < 70; ++tick) pair.step(tick); + + CHECK(pair.server.view().centreChunk == Vec3i(2, 0, 2)); + CHECK(pair.server.view().hasChunk(Vec3i{2, 0, 2})); + CHECK(!pair.server.view().hasChunk(Vec3i{0, 0, 0})); + CHECK(!pair.server.view().tracker.contains(Vec3i{0, 0, 0})); + CHECK_NEAR(pair.server.view().position.x, 40.0f, 0.001f); +} + +VF_TEST(aClientBlockEditTravelsUpstreamAndComesBackAsAConfirmedChange) { + LinkQuality quality; + quality.lossPercent = 20; + quality.latencyTicks = 1; + quality.seed = 0x31415926ull; + TransportConfig transportConfig; + transportConfig.retransmitTimeoutTicks = 6; + Pair pair(quality, transportConfig, quietConfig()); + buildTerrain(pair.serverWorld, 1); + + pair.client.beginHandshake("vulp", 1, 2); + for (uint64_t tick = 0; tick < 40; ++tick) pair.step(tick); + CHECK(pair.server.view().hasChunk(Vec3i{0, 0, 0})); + CHECK(pair.clientWorld.blockAt(3, 15, 4) == Block::Air); + + pair.client.requestBlockEdit(3, 15, 4, Block::Glowstone); + + for (uint64_t tick = 40; tick < 90; ++tick) { + pair.server.receive(tick); + for (const BlockChange& edit : pair.server.requestedEdits()) + pair.edit(edit.x, edit.y, edit.z, edit.block); + pair.server.clearRequestedEdits(); + pair.server.send(tick); + pair.clearDirty(); + pair.client.receive(tick); + pair.client.send(tick); + } + + CHECK(pair.serverWorld.blockAt(3, 15, 4) == Block::Glowstone); + CHECK(pair.clientWorld.blockAt(3, 15, 4) == Block::Glowstone); + CHECK_EQ(pair.client.stats().blockChangesApplied, static_cast(1)); +} + +VF_TEST(viewTrackerReportsOnlyTheDifferenceWhenTheCentreMoves) { + ViewTracker tracker(2); + std::vector entered; + std::vector left; + + tracker.update(Vec3i{0, 0, 0}, entered, left); + CHECK_EQ(entered.size(), static_cast(33)); + CHECK_EQ(left.size(), static_cast(0)); + CHECK(entered[0] == Vec3i(0, 0, 0)); + CHECK(tracker.contains(Vec3i{0, 0, 2})); + CHECK(!tracker.contains(Vec3i{2, 2, 0})); + CHECK_EQ(tracker.size(), static_cast(33)); + + std::size_t previous = tracker.size(); + tracker.update(Vec3i{1, 0, 0}, entered, left); + CHECK(entered.size() < previous); + CHECK(entered.size() > 0u); + CHECK_EQ(entered.size(), left.size()); + CHECK_EQ(tracker.size(), previous); + for (const Vec3i& coord : entered) CHECK(tracker.contains(coord)); + for (const Vec3i& coord : left) CHECK(!tracker.contains(coord)); + + tracker.update(Vec3i{40, 0, 40}, entered, left); + CHECK_EQ(entered.size(), static_cast(33)); + CHECK_EQ(left.size(), static_cast(33)); +}