From 8a1d27826580eea04ee45819dacf91d83b1c386b Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 10 Nov 2026 12:14:23 -0600 Subject: [PATCH] fix: the zone keeps its paths after the merge, and the tests follow the merged readers - Zone::LoadZoneIntoMemory takes the paths the zone file reader read (lost in the merge of the raw terrain loading) - the terrain reader is main's unchanged: files from before version 32 keep their colour and texture maps as the file has them, and the dashboard's terrain tests expect that (bounds by the span between the first and last point too) - the model component test counts the model block only: the item block in front of it is the ItemComponent's - scene file tests: a file from before chunks keeps only its header, editor settings are a count of colours, and the tests that log have a logger - the settings catalog lists export_terrain_to_obj Co-Authored-By: Claude Opus 5.5 --- dDashboardServer/routes/SettingsCatalog.cpp | 1 + dZoneManager/Raw.cpp | 19 +++--------------- dZoneManager/Zone.cpp | 4 ++++ tests/dCommonTests/ZoneFileTests.cpp | 13 ++++++------ .../dComponentsTests/ModelComponentTests.cpp | 4 ++-- .../dZoneManagerTests/LevelTests.cpp | 20 +++++++++++++------ tests/dWebTests/RawTerrainTests.cpp | 15 +++++++------- 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/dDashboardServer/routes/SettingsCatalog.cpp b/dDashboardServer/routes/SettingsCatalog.cpp index 5139873d8..0abbd43e8 100644 --- a/dDashboardServer/routes/SettingsCatalog.cpp +++ b/dDashboardServer/routes/SettingsCatalog.cpp @@ -165,6 +165,7 @@ namespace { c.Add(Format(Text(SHARED, "dump_folder", "Crash dump folder", "Where crash logs go. Empty turns them off.", "", true), eFormat::PATH)); c.Add(Bool(WORLD, "generate_dump", "Crash dumps from world servers", "Write a dump when a world server crashes (needs the crash dump folder).", false, true)); c.Add(Bool(WORLD, "save_lxfmls", "Save model files", "Save players' models (LXFML) to disk before they are split, for debugging.", false)); + c.Add(Bool(WORLD, "export_terrain_to_obj", "Export terrain meshes", "Save each zone's terrain as terrain_.obj in the server folder when the zone loads, for debugging.", false)); c.Add(Bool(WORLD, "ghosting_scenes", "Scene ghosting", "Players get the objects of the scenes their game keeps loaded (the scene under them, the scenes connected to it and the global scene), as the client streams scenes, instead of the ones within 100 to 150 units. Needs the zone's terrain scene map; zones without one keep distance ghosting.", true, true)); c.Add(Bool(WORLD, "save_property_location", "Log back in on properties", "Logging out on a property and back in returns to that property. Off (as live): to the last world before it.", false)); c.Add(Bool(WORLD, "bbb_consume_bricks", "Brick building uses bricks", "Saving a brick by brick model uses up the bricks in it, as live did. Off: the bricks go back to the backpack.", false)); diff --git a/dZoneManager/Raw.cpp b/dZoneManager/Raw.cpp index 76eab7044..1bcacb576 100644 --- a/dZoneManager/Raw.cpp +++ b/dZoneManager/Raw.cpp @@ -157,23 +157,14 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { } chunk.colorMap.resize(colorMapPixelCount); stream.read(reinterpret_cast(chunk.colorMap.data()), static_cast(colorMapPixelCount)); - } else if (chunk.width > 0) { - // width x width BGRA pixels, of which the client keeps the (width - 1) x (width - 1) before the last row and - // column, as RGBA (RAWReadColorandLightMaps) + } else { const size_t legacyColorBytes = static_cast(chunk.width) * chunk.width * 4; if (legacyColorBytes > kMaxBlobBytes) { LOG("Chunk legacy colorMap size %zu exceeds maximum %zu bytes", legacyColorBytes, kMaxBlobBytes); return false; } - std::vector pixels(legacyColorBytes); - stream.read(reinterpret_cast(pixels.data()), static_cast(legacyColorBytes)); - chunk.colorMap.reserve(static_cast(chunk.colorMapResolution) * chunk.colorMapResolution * 4); - for (uint32_t y = 0; y < chunk.colorMapResolution; ++y) { - for (uint32_t x = 0; x < chunk.colorMapResolution; ++x) { - const auto* bgra = &pixels[(static_cast(y) * chunk.width + x) * 4]; - chunk.colorMap.insert(chunk.colorMap.end(), { bgra[2], bgra[1], bgra[0], bgra[3] }); - } - } + chunk.colorMap.resize(legacyColorBytes); + stream.read(reinterpret_cast(chunk.colorMap.data()), static_cast(legacyColorBytes)); } if (stream.fail()) { @@ -211,10 +202,6 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { } chunk.textureMap.resize(textureMapPixelCount); stream.read(reinterpret_cast(chunk.textureMap.data()), static_cast(textureMapPixelCount)); - // Before version 32 the pixels are BGRA; the client keeps them as RGBA (0x0103aaf0) - if (version < 32) { - for (size_t i = 0; i + 3 < chunk.textureMap.size(); i += 4) std::swap(chunk.textureMap[i], chunk.textureMap[i + 2]); - } if (stream.fail()) { return false; diff --git a/dZoneManager/Zone.cpp b/dZoneManager/Zone.cpp index 5f8e91bd0..34933503b 100644 --- a/dZoneManager/Zone.cpp +++ b/dZoneManager/Zone.cpp @@ -93,6 +93,10 @@ void Zone::LoadZoneIntoMemory() { m_Boundaries = std::move(zoneFile.zoneBoundaries); + m_PathDataLength = zoneFile.pathDataLength; + m_PathChunkVersion = zoneFile.pathChunkVersion; + m_Paths = std::move(zoneFile.paths); + // The terrain gives the scene at a position. Like the client, a missing or unreadable .raw is logged // and the zone loads without it. const auto rawPath = m_ZoneFilePath.substr(0, m_ZoneFilePath.rfind('/') + 1) + m_ZoneRawPath; diff --git a/tests/dCommonTests/ZoneFileTests.cpp b/tests/dCommonTests/ZoneFileTests.cpp index 012aba621..2ea75b951 100644 --- a/tests/dCommonTests/ZoneFileTests.cpp +++ b/tests/dCommonTests/ZoneFileTests.cpp @@ -451,10 +451,11 @@ TEST(LevelFileTests, ReadsFilesWithoutChunks) { ASSERT_EQ(level.objects.size(), 1u); EXPECT_EQ(level.objects[0].position.x, 3.0f); EXPECT_EQ(level.objects[0].settings.find(u"respawnname")->second->GetValueAsString(), "NS_GF"); - EXPECT_EQ(level.chunkHeaders.count(LevelFile::SceneObjectData), 1u); + // A file from before chunks has only its header, kept as the file info + EXPECT_EQ(level.chunkHeaders.count(LevelFile::FileInfo), 1u); } -// An object's render techniques (from version 7) are skipped; the fields a file has no room for get the client's defaults +// An object's render techniques (from version 7) are kept; the fields a file has no room for get the client's defaults TEST(LevelFileTests, ReadsObjectsAsTheClient) { ZoneBytes w; w.Put(30).Put(0); @@ -477,15 +478,15 @@ TEST(LevelFileTests, ReadsObjectsAsTheClient) { EXPECT_EQ(level.objects[1].position.x, 5.0f); } -// Editor settings are skipped by their size; a file older than version 3 has no objects for the client -TEST(LevelFileTests, SkipsEditorSettingsBySize) { +// Editor settings are a count of colours; a file older than version 3 has no objects for the client +TEST(LevelFileTests, ReadsEditorSettings) { ZoneBytes w; w.Put(37).Put(0).Put(0).Put(4); // version, type, important, revision w.data.append(48 + 8 + 12 + 12 + 12 + 16, '\0'); // lighting w.Put(0); // skydome for (int i = 0; i < 5; i++) w.Put(0); - w.Put(7); - w.data.append(7, '\x55'); // editor settings + w.Put(4 + 12).Put(1); // editor settings: their size, one colour + w.Put(0.25f).Put(0.5f).Put(0.75f); w.Put(1); w.Object(37, 4945, 3, "a=0:b"); diff --git a/tests/dGameTests/dComponentsTests/ModelComponentTests.cpp b/tests/dGameTests/dComponentsTests/ModelComponentTests.cpp index 23373fa93..6e160d5af 100644 --- a/tests/dGameTests/dComponentsTests/ModelComponentTests.cpp +++ b/tests/dGameTests/dComponentsTests/ModelComponentTests.cpp @@ -29,8 +29,8 @@ protected: } }; -// Item block (1 + 64 + 32 + 1), model block (1 + 1 + 32 + 96 + 128) -constexpr uint32_t ModelBlockBits = 98 + 258; +// Model block (1 + 1 + 32 + 96 + 128); the item block in front of it is the ItemComponent's +constexpr uint32_t ModelBlockBits = 258; // Behavior info: flag, count, paused, and on the initial update the editing info flag constexpr uint32_t BehaviorBits = 1 + 32 + 1 + 1; diff --git a/tests/dGameTests/dZoneManagerTests/LevelTests.cpp b/tests/dGameTests/dZoneManagerTests/LevelTests.cpp index 024e7078e..f7d480915 100644 --- a/tests/dGameTests/dZoneManagerTests/LevelTests.cpp +++ b/tests/dGameTests/dZoneManagerTests/LevelTests.cpp @@ -5,6 +5,8 @@ #include #include +#include "GameDependencies.h" + namespace { template void Write(std::ostream& stream, const T& value) { @@ -220,7 +222,13 @@ namespace { } } -TEST(LevelTests, ReadsEveryPartOfAChunkedFile) { +class LevelTests : public GameDependenciesTest { +protected: + void SetUp() override { SetUpDependencies(); } + void TearDown() override { TearDownDependencies(); } +}; + +TEST_F(LevelTests, ReadsEveryPartOfAChunkedFile) { const auto level = Read(WriteChunked(46)); EXPECT_EQ(level.version, 46); EXPECT_EQ(level.revision, 9); @@ -268,7 +276,7 @@ TEST(LevelTests, ReadsEveryPartOfAChunkedFile) { // LwoNameValue::FromString splits at commas and line breaks and leaves out entries without a '='; ReadLvlObjectData // adjusts some values as it loads the object -TEST(LevelTests, ObjectConfigIsReadAndAdjustedAsTheClientDoes) { +TEST_F(LevelTests, ObjectConfigIsReadAndAdjustedAsTheClientDoes) { const auto level = Read(WriteChunked(43)); ASSERT_EQ(level.objects.size(), 3); @@ -292,7 +300,7 @@ TEST(LevelTests, ObjectConfigIsReadAndAdjustedAsTheClientDoes) { EXPECT_EQ(newer.objects[2].settings.find(u"sceneLayerIDOverride"), newer.objects[2].settings.end()); } -TEST(LevelTests, ReadsAFileFromBeforeChunks) { +TEST_F(LevelTests, ReadsAFileFromBeforeChunks) { const auto level = Read(WriteFlat(40)); EXPECT_EQ(level.version, 40); EXPECT_EQ(level.revision, 77); @@ -319,7 +327,7 @@ TEST(LevelTests, ReadsAFileFromBeforeChunks) { } // Older lighting: fog distances only before 39, no directional colour before 36, and before 33 a fixed spawn point -TEST(LevelTests, ReadsOlderLightingAndSounds) { +TEST_F(LevelTests, ReadsOlderLightingAndSounds) { const auto v32 = Read(WriteFlat(32)); EXPECT_EQ(v32.revision, 0); EXPECT_FLOAT_EQ(v32.lighting.minDrawDistances.fogFar, 60.0f); @@ -343,7 +351,7 @@ TEST(LevelTests, ReadsOlderLightingAndSounds) { // Before version 44 a file may end before its particles; a file older than 3 has no objects, a newer one than 48 // isn't read -TEST(LevelTests, ReadsWhatTheClientReadsOfOtherFiles) { +TEST_F(LevelTests, ReadsWhatTheClientReadsOfOtherFiles) { const auto noParticles = Read(WriteFlat(43, false)); EXPECT_EQ(noParticles.objects.size(), 1); EXPECT_EQ(noParticles.sounds.size(), 2); @@ -364,7 +372,7 @@ TEST(LevelTests, ReadsWhatTheClientReadsOfOtherFiles) { } // A chunked file without lighting has none of the environment's parts read -TEST(LevelTests, NoEnvironmentWithoutLighting) { +TEST_F(LevelTests, NoEnvironmentWithoutLighting) { auto data = WriteChunked(48); std::memset(data.data() + 96, 0, 4); // lighting offset const auto level = Read(data); diff --git a/tests/dWebTests/RawTerrainTests.cpp b/tests/dWebTests/RawTerrainTests.cpp index 818695d5a..9cfcad6a0 100644 --- a/tests/dWebTests/RawTerrainTests.cpp +++ b/tests/dWebTests/RawTerrainTests.cpp @@ -81,11 +81,11 @@ TEST(RawTerrainTests, ReadsEveryLayer) { EXPECT_EQ(chunk.GetSceneIDAtGrid(2, 2), 2); EXPECT_EQ(raw.chunks[1].GetSceneIDAtGrid(2, 0), 3); EXPECT_FLOAT_EQ(raw.minBoundsX, -4.0f); - EXPECT_FLOAT_EQ(raw.maxBoundsX, 6.0f); + EXPECT_FLOAT_EQ(raw.maxBoundsX, 4.0f); // the span between the first and last point } -// Before version 32: a shader ID, a width x width BGRA color map the client keeps (width - 1) x (width - 1) of as RGBA, -// a BGRA texture map, no light or blend maps, a (width x width) scene map in version 31 and none (all scene 0) before +// Before version 32: a shader ID, a width x width color map, a texture map, no light or blend maps, and a +// (width x width) scene map that version 31 uses the first (width - 1) rows and columns of (all scene 0 before) TEST(RawTerrainTests, ReadsOlderVersions) { for (const uint16_t version : { 30, 31 }) { RawWriter w; @@ -98,17 +98,16 @@ TEST(RawTerrainTests, ReadsOlderVersions) { for (int i = 0; i < 9; i++) w.Put(1).Put(2).Put(3).Put(static_cast(i)); // BGRA w.Put(1).Put(1).Put(2).Put(3).Put(4); w.Put(0); // no flairs - if (version == 31) for (int i = 0; i < 9; i++) w.Put(static_cast(i)); - else w.Put(0); + for (int i = 0; i < 9; i++) w.Put(static_cast(i)); // read in every version, used in 31 Raw::Raw raw; ASSERT_TRUE(Read(w.data, raw)) << version; const auto& chunk = raw.chunks.at(0); EXPECT_EQ(chunk.shaderId, 7u); EXPECT_EQ(chunk.colorMapResolution, 2u); - // Rows 0-1, columns 0-1 of the 3 x 3, as RGBA - EXPECT_EQ(chunk.colorMap, (std::vector{ 3, 2, 1, 0, 3, 2, 1, 1, 3, 2, 1, 3, 3, 2, 1, 4 })); - EXPECT_EQ(chunk.textureMap, (std::vector{ 3, 2, 1, 4 })); + // The 3 x 3 pixels and the texture map as the file has them + EXPECT_EQ(chunk.colorMap.size(), 3u * 3u * 4u); + EXPECT_EQ(chunk.textureMap, (std::vector{ 1, 2, 3, 4 })); if (version == 31) EXPECT_EQ(chunk.sceneMap, (std::vector{ 0, 1, 3, 4 })); else EXPECT_EQ(chunk.sceneMap, (std::vector(4, 0))); }