Files
DarkflameServer/tests/dCommonTests/ZoneScenesTests.cpp
Aaron Kimbrell 2da96ada83 feat(dashboard): 3D views show a zone's scenes as the game loads them
A "Scenes" choice in the world and property 3D views: every scene (as
before), the scenes the game keeps loaded around the camera or the followed
player (the scene under it from the terrain's scene map, the scenes its
transitions connect to, and the global scene, following as it moves), or
scenes picked from a list (world view). The lighting blends to the lighting
of the scene under the focus, as the client blends between scenes.

The scenery manifest now carries each object's scene, the zone's scenes with
their neighbours and lighting, and the scene map as runs (37 KB for Avant
Gardens); scenery-core.js finds the scene at a point exactly as ZoneScenes
does (checked against it on 2000 points of Avant Gardens).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:31:17 -05:00

84 lines
3.4 KiB
C++

#include <gtest/gtest.h>
#include "ZoneScenes.h"
namespace {
// A chunk `cells` scene map cells wide covering (x, z) .. (x + 64, z + 64): 65 height samples, 1 unit apart
Raw::Chunk MakeChunk(float x, float z, uint32_t cells, std::vector<uint8_t> scenes) {
Raw::Chunk chunk{};
chunk.width = chunk.height = 65;
chunk.offsetX = x;
chunk.offsetZ = z;
chunk.scaleFactor = 1.0f;
chunk.heightMap.assign(65 * 65, 0.0f);
chunk.colorMapResolution = cells;
chunk.sceneMap = std::move(scenes);
return chunk;
}
SceneTransition Transition(uint32_t a, uint32_t b, uint32_t layer = 0) {
SceneTransition transition;
transition.points.push_back({ (static_cast<uint64_t>(layer) << 32) | a, {} });
transition.points.push_back({ b, {} });
return transition;
}
std::vector<ZoneScene> Scenes(std::initializer_list<uint32_t> ids) {
std::vector<ZoneScene> scenes;
for (const auto id : ids) {
ZoneScene scene;
scene.id = id;
scenes.push_back(scene);
}
return scenes;
}
}
// Cells are x major (index x * resolution + z) and a position takes the nearest cell, as the client's lookup does
TEST(ZoneScenesTests, FindsTheSceneUnderAPosition) {
Raw::Raw raw{};
// 2x2 cells, each 32 units: x = 0 has scenes 1 (z low) and 2, x = 1 has 3 and no scene
raw.chunks.push_back(MakeChunk(0, 0, 2, { 1, 2, 3, ZoneScenes::NO_SCENE }));
raw.chunks.push_back(MakeChunk(64, 0, 1, { 7 }));
const ZoneScenes::SceneMap map(raw);
ASSERT_FALSE(map.Empty());
EXPECT_EQ(map.SceneAt(1, 1), 1u);
EXPECT_EQ(map.SceneAt(1, 17), 2u); // past half a cell: the next cell
EXPECT_EQ(map.SceneAt(17, 1), 3u);
EXPECT_EQ(map.SceneAt(17, 17), ZoneScenes::GLOBAL_SCENE); // no scene there
EXPECT_EQ(map.SceneAt(100, 10), 7u);
// Off the terrain: clamped to its edge
EXPECT_EQ(map.SceneAt(-50, -50), 1u);
EXPECT_EQ(map.SceneAt(500, 10), 7u);
EXPECT_EQ(ZoneScenes::SceneMap().SceneAt(1, 1), ZoneScenes::GLOBAL_SCENE);
}
TEST(ZoneScenesTests, LoadsTheConnectedScenes) {
// 1-2 and 2-3 connect; 9 isn't in the zone, so its transition is dropped
const ZoneScenes::SceneGraph graph(Scenes({ 0, 1, 2, 3, 4 }), { Transition(1, 2), Transition(3, 2, 1), Transition(2, 9), Transition(4, 4) });
EXPECT_EQ(graph.Loaded(1), (std::set<uint32_t>{ 0, 1, 2 }));
EXPECT_EQ(graph.Loaded(2), (std::set<uint32_t>{ 0, 1, 2, 3 }));
EXPECT_EQ(graph.Loaded(4), (std::set<uint32_t>{ 0, 4 }));
EXPECT_EQ(graph.Loaded(ZoneScenes::GLOBAL_SCENE), (std::set<uint32_t>{ 0 }));
EXPECT_TRUE(graph.Neighbours(9).empty());
}
TEST(ZoneScenesTests, SendsSceneMapsAsRuns) {
std::vector<uint8_t> cells(300, 4);
cells[0] = 1;
cells[299] = 2;
// 1, then 298 fours (a run is at most 255 long), then 2; only the first `count` cells count
EXPECT_EQ(ZoneScenes::RunLengths(cells, 300), std::string("\x01\x01\xFF\x04\x2B\x04\x01\x02", 8));
EXPECT_EQ(ZoneScenes::RunLengths(cells, 1), std::string("\x01\x01", 2));
EXPECT_EQ(ZoneScenes::RunLengths(cells, 1000).size(), 8u);
}
TEST(ZoneScenesTests, GhostsObjectsByTheirScene) {
const std::set<uint32_t> loaded{ 0, 1, 2 };
EXPECT_TRUE(ZoneScenes::InLoadedScene(2, 7, loaded)); // placed in a loaded scene, wherever it is now
EXPECT_FALSE(ZoneScenes::InLoadedScene(3, 1, loaded)); // placed in one that isn't
EXPECT_TRUE(ZoneScenes::InLoadedScene(0, 9, loaded)); // the global scene's objects always
EXPECT_TRUE(ZoneScenes::InLoadedScene(-1, 1, loaded)); // spawned at run time: the scene under it
EXPECT_FALSE(ZoneScenes::InLoadedScene(-1, 5, loaded));
}