#include "testHarness.h" #include "vf/ChunkSync.h" #include #include using namespace vf; namespace { struct Lcg { uint64_t state; explicit Lcg(uint64_t seed) : state(seed) {} uint32_t next() { state = state * 6364136223846793005ull + 1442695040888963407ull; return static_cast(state >> 32); } int range(int n) { return static_cast(next() % static_cast(n)); } }; // Server-side edit: mutate the authoritative world and flag the slot dirty. void serverEdit(World& world, ServerChunkSync& sync, const Vec3i& chunkCoord, int x, int y, int z, Block block) { Chunk& chunk = world.ensureChunk(chunkCoord); chunk.set(x, y, z, block); sync.markDirty(chunkCoord, x, y, z); } bool chunksEqual(const World& a, const World& b, const Vec3i& coord) { const Chunk* ca = a.chunkAt(coord); const Chunk* cb = b.chunkAt(coord); if (!ca || !cb) return false; return ca->raw() == cb->raw(); } ChunkData reframeChunkData(const ChunkData& packet) { PacketWriter w; packet.encode(w); PacketReader r(w.bytes().data(), w.bytes().size()); ChunkData back; CHECK(back.decode(r)); return back; } MultiBlockChange reframeDelta(const MultiBlockChange& packet) { PacketWriter w; packet.encode(w); PacketReader r(w.bytes().data(), w.bytes().size()); MultiBlockChange back; CHECK(back.decode(r)); return back; } } // namespace VF_TEST(chunkSyncFullSnapshotReplicatesChunk) { World server; ServerChunkSync sync; Vec3i coord{2, -1, 3}; for (int x = 0; x < chunkSize; ++x) for (int z = 0; z < chunkSize; ++z) { server.ensureChunk(coord).set(x, 0, z, Block::Bedrock); for (int y = 1; y < 6; ++y) server.ensureChunk(coord).set(x, y, z, Block::Stone); server.ensureChunk(coord).set(x, 6, z, Block::Grass); } ChunkData full; CHECK(sync.buildFull(server, coord, full)); ChunkData wire = reframeChunkData(full); World client; ClientChunkSync clientSync; clientSync.applyFull(wire, client); CHECK(chunksEqual(server, client, coord)); } VF_TEST(chunkSyncDeltaAppliesDirtyBlocks) { World server; ServerChunkSync sync; Vec3i coord{0, 0, 0}; server.ensureChunk(coord); World client; ClientChunkSync clientSync; ChunkData full; CHECK(sync.buildFull(server, coord, full)); clientSync.applyFull(reframeChunkData(full), client); CHECK(chunksEqual(server, client, coord)); serverEdit(server, sync, coord, 1, 1, 1, Block::Glowstone); serverEdit(server, sync, coord, 5, 8, 2, Block::DiamondOre); serverEdit(server, sync, coord, 15, 15, 15, Block::Obsidian); CHECK_EQ(sync.dirtyCount(coord), static_cast(3)); MultiBlockChange delta; CHECK(sync.buildDelta(server, coord, delta)); clientSync.applyDelta(reframeDelta(delta), client); CHECK(chunksEqual(server, client, coord)); sync.clear(coord); CHECK(!sync.anyDirty()); MultiBlockChange empty; CHECK(!sync.buildDelta(server, coord, empty)); } VF_TEST(chunkSyncSingleBlockChange) { World client; ClientChunkSync clientSync; BlockChange packet; packet.x = -7; packet.y = 70; packet.z = 42; packet.block = Block::RedstoneLampLit; clientSync.applyBlockChange(packet, client); CHECK(client.blockAt(-7, 70, 42) == Block::RedstoneLampLit); } VF_TEST(chunkSyncResendThreshold) { World server; ServerChunkSync sync; Vec3i coord{1, 0, 1}; server.ensureChunk(coord); for (int i = 0; i < 40; ++i) serverEdit(server, sync, coord, i % 16, i / 16, 0, Block::Sand); CHECK(sync.shouldResendFull(coord, 32)); CHECK(!sync.shouldResendFull(coord, 1000)); } VF_TEST(chunkSyncOverlappingEditsCollapse) { World server; ServerChunkSync sync; Vec3i coord{0, 0, 0}; server.ensureChunk(coord); serverEdit(server, sync, coord, 3, 3, 3, Block::Dirt); serverEdit(server, sync, coord, 3, 3, 3, Block::Stone); serverEdit(server, sync, coord, 3, 3, 3, Block::Grass); // The same slot touched three times is one dirty entry carrying the final block. CHECK_EQ(sync.dirtyCount(coord), static_cast(1)); MultiBlockChange delta; CHECK(sync.buildDelta(server, coord, delta)); CHECK_EQ(delta.changes.size(), static_cast(1)); CHECK(delta.changes[0].block == Block::Grass); } VF_TEST(chunkSyncManyTicksConverge) { World server; World client; ServerChunkSync sync; ClientChunkSync clientSync; Vec3i coord{4, 2, -6}; server.ensureChunk(coord); ChunkData full; CHECK(sync.buildFull(server, coord, full)); clientSync.applyFull(reframeChunkData(full), client); Lcg rng(0xC0FFEE); Block palette[6] = {Block::Air, Block::Stone, Block::Dirt, Block::Water, Block::Glowstone, Block::Sand}; for (int tick = 0; tick < 30; ++tick) { int edits = 1 + rng.range(20); for (int e = 0; e < edits; ++e) { int x = rng.range(chunkSize); int y = rng.range(chunkSize); int z = rng.range(chunkSize); serverEdit(server, sync, coord, x, y, z, palette[rng.range(6)]); } MultiBlockChange delta; if (sync.buildDelta(server, coord, delta)) { clientSync.applyDelta(reframeDelta(delta), client); sync.clear(coord); } } CHECK(chunksEqual(server, client, coord)); CHECK(!sync.anyDirty()); } VF_TEST(chunkSyncFramedEndToEnd) { World server; ServerChunkSync sync; Vec3i coord{7, 0, 7}; Chunk& source = server.ensureChunk(coord); for (int i = 0; i < chunkVolume; ++i) source.raw()[static_cast(i)] = static_cast(i % 5); ChunkData full; CHECK(sync.buildFull(server, coord, full)); ByteWriter stream; PacketWriter body; full.encode(body); writeFramed(stream, PacketId::ChunkData, body); std::vector frames; std::size_t consumed = 0; splitFrames(stream.bytes().data(), stream.bytes().size(), frames, consumed); CHECK_EQ(frames.size(), static_cast(1)); CHECK(frames[0].id == PacketId::ChunkData); PacketReader r(frames[0].payload.data(), frames[0].payload.size()); ChunkData back; CHECK(back.decode(r)); World client; ClientChunkSync clientSync; clientSync.applyFull(back, client); CHECK(chunksEqual(server, client, coord)); }