From a40169a2513febebefceaa7baa05f877989d56dd Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Mon, 26 Oct 2026 20:33:41 -0500 Subject: [PATCH] fix: keep the scene map of version 31 and older raws These files have a (resolution + 1)^2 scene map, kept in sceneMapLegacy. Version 31 uses its first resolution rows and columns; older versions are all scene 0, as in the client. Co-Authored-By: Claude Opus 5.5 --- dZoneManager/Raw.cpp | 34 ++++++------------- dZoneManager/Raw.h | 3 ++ .../dGameTests/dZoneManagerTests/RawTests.cpp | 14 +++++--- .../dZoneManagerTests/TerrainTestWriter.h | 11 +++--- 4 files changed, 28 insertions(+), 34 deletions(-) diff --git a/dZoneManager/Raw.cpp b/dZoneManager/Raw.cpp index 28e302d0d..9851cdd4f 100644 --- a/dZoneManager/Raw.cpp +++ b/dZoneManager/Raw.cpp @@ -40,19 +40,6 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { return NiPoint3(offsetX + static_cast(i) * scaleFactor, y, offsetZ + static_cast(j) * scaleFactor); } - /** - * @brief Skip bytes, such as the byte version 30 and older files have in place of a scene map - */ - static bool Skip(std::istream& stream, size_t bytes) { - if (bytes > kMaxBlobBytes) { - LOG("Raw block of %zu bytes exceeds maximum %zu bytes", bytes, kMaxBlobBytes); - return false; - } - // ignore, not seekg: the asset streams don't stop a seek at the end of the data - stream.ignore(static_cast(bytes)); - return static_cast(stream.gcount()) == bytes && !stream.fail(); - } - /** * @brief Read flair attributes from stream */ @@ -295,22 +282,23 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { } chunk.sceneMap.resize(sceneMapSize); stream.read(reinterpret_cast(chunk.sceneMap.data()), static_cast(sceneMapSize)); - } else if (version == 31) { + } else { const size_t sceneMapCells = static_cast(chunk.colorMapResolution + 1) * (chunk.colorMapResolution + 1); if (sceneMapCells > kMaxBlobBytes) { - LOG("Chunk v31 sceneMap size %zu exceeds maximum %zu bytes", sceneMapCells, kMaxBlobBytes); + LOG("Chunk legacy sceneMap size %zu exceeds maximum %zu bytes", sceneMapCells, kMaxBlobBytes); return false; } - std::vector rawSceneMap(sceneMapCells); - stream.read(reinterpret_cast(rawSceneMap.data()), static_cast(sceneMapCells)); - chunk.sceneMap.resize(static_cast(chunk.colorMapResolution) * chunk.colorMapResolution); - for (uint32_t row = 0; row < chunk.colorMapResolution; ++row) { - for (uint32_t col = 0; col < chunk.colorMapResolution; ++col) { - chunk.sceneMap[row * chunk.colorMapResolution + col] = rawSceneMap[row * (chunk.colorMapResolution + 1) + col]; + chunk.sceneMapLegacy.resize(sceneMapCells); + stream.read(reinterpret_cast(chunk.sceneMapLegacy.data()), static_cast(sceneMapCells)); + // Like the client: version 31 uses the first colorMapResolution rows and columns, older versions are all scene 0 + chunk.sceneMap.assign(static_cast(chunk.colorMapResolution) * chunk.colorMapResolution, 0); + if (version == 31) { + for (uint32_t row = 0; row < chunk.colorMapResolution; ++row) { + for (uint32_t col = 0; col < chunk.colorMapResolution; ++col) { + chunk.sceneMap[row * chunk.colorMapResolution + col] = chunk.sceneMapLegacy[row * (chunk.colorMapResolution + 1) + col]; + } } } - } else if (!Skip(stream, 1)) { - return false; } if (stream.fail()) { diff --git a/dZoneManager/Raw.h b/dZoneManager/Raw.h index eb83d8a90..100d61732 100644 --- a/dZoneManager/Raw.h +++ b/dZoneManager/Raw.h @@ -98,6 +98,9 @@ struct Chunk { // The scene map is colorMapResolution * colorMapResolution scene IDs, indexed [x cell * resolution + z cell] std::vector sceneMap; + // Version 31 and older: the file's (colorMapResolution + 1)^2 scene map. In version 31 sceneMap is its first + // colorMapResolution rows and columns; older versions don't use it and every cell is scene 0 + std::vector sceneMapLegacy; // Mesh data (version 32+) uint32_t vertSize = 0; diff --git a/tests/dGameTests/dZoneManagerTests/RawTests.cpp b/tests/dGameTests/dZoneManagerTests/RawTests.cpp index 4050f0d4d..cf614594c 100644 --- a/tests/dGameTests/dZoneManagerTests/RawTests.cpp +++ b/tests/dGameTests/dZoneManagerTests/RawTests.cpp @@ -173,6 +173,7 @@ TEST_F(RawTest, ReadsVersion31SceneMapWithoutTheLastRowAndColumn) { ASSERT_EQ(raw.chunks.size(), 1); EXPECT_EQ(raw.chunks[0].colorMapResolution, 2); EXPECT_EQ(raw.chunks[0].sceneMap, (std::vector{ 1, 2, 3, 4 })); + EXPECT_EQ(raw.chunks[0].sceneMapLegacy, chunk.sceneMap); EXPECT_EQ(raw.chunks[0].shaderId, 77); EXPECT_EQ(raw.chunks[0].textureIds, (std::vector{ 100, 101, 102, 103 })); EXPECT_EQ(raw.chunks[0].colorMap.size(), 3 * 3 * 4); // width^2 pixels @@ -182,8 +183,12 @@ TEST_F(RawTest, ReadsVersion31SceneMapWithoutTheLastRowAndColumn) { EXPECT_TRUE(raw.chunks[0].meshTri.empty()); } -TEST_F(RawTest, ReadsVersion30WithoutASceneMap) { +TEST_F(RawTest, Version30SceneMapIsReadButEveryCellIsSceneZero) { TestChunk chunk; + chunk.width = 3; + chunk.height = 3; + // resolution is width - 1 = 2; the file has a 3x3 scene map the client doesn't use + chunk.sceneMap = { 7, 7, 7, 7, 7, 7, 7, 7, 7 }; const auto data = WriteRaw({ chunk, chunk }, 30, 1, 2); std::istringstream stream(data); @@ -191,9 +196,10 @@ TEST_F(RawTest, ReadsVersion30WithoutASceneMap) { ASSERT_TRUE(Raw::ReadRaw(stream, raw)); EXPECT_EQ(stream.tellg(), static_cast(data.size())); ASSERT_EQ(raw.chunks.size(), 2); - EXPECT_TRUE(raw.chunks[1].sceneMap.empty()); - EXPECT_FALSE(raw.chunks[1].IsValidForSceneLookup()); - EXPECT_EQ(raw.GetSceneIDAt(0.5f, 0.5f), std::nullopt); + EXPECT_EQ(raw.chunks[1].sceneMapLegacy, chunk.sceneMap); + EXPECT_EQ(raw.chunks[1].sceneMap, (std::vector{ 0, 0, 0, 0 })); + EXPECT_TRUE(raw.chunks[1].IsValidForSceneLookup()); + EXPECT_EQ(raw.GetSceneIDAt(0.5f, 0.5f), 0); } TEST_F(RawTest, DevFileHasNoChunks) { diff --git a/tests/dGameTests/dZoneManagerTests/TerrainTestWriter.h b/tests/dGameTests/dZoneManagerTests/TerrainTestWriter.h index d9ea5a117..7350d1144 100644 --- a/tests/dGameTests/dZoneManagerTests/TerrainTestWriter.h +++ b/tests/dGameTests/dZoneManagerTests/TerrainTestWriter.h @@ -33,7 +33,7 @@ namespace TerrainTestWriter { float scale = 1.0f; // Version 32+: the color map resolution; older versions use width - 1 uint32_t colorMapResolution = 2; - // colorMapResolution^2 scene IDs ((resolution + 1)^2 for version 31); empty = all 0 + // colorMapResolution^2 scene IDs ((resolution + 1)^2 for version 31 and older); empty = all 0 std::vector sceneMap; // width * height heights; empty = the point index std::vector heights; @@ -86,12 +86,9 @@ namespace TerrainTestWriter { for (const uint8_t channel : { 10, 20, 30, 40 }) Write(stream, channel); } - if (version >= 31) { - const size_t cells = version >= 32 ? static_cast(resolution) * resolution : static_cast(resolution + 1) * (resolution + 1); - for (size_t i = 0; i < cells; ++i) Write(stream, chunk.sceneMap.empty() ? 0 : chunk.sceneMap[i]); - } else { - Write(stream, 0); - } + // version 31 and older: one more row and column + const size_t cells = version >= 32 ? static_cast(resolution) * resolution : static_cast(resolution + 1) * (resolution + 1); + for (size_t i = 0; i < cells; ++i) Write(stream, chunk.sceneMap.empty() ? 0 : chunk.sceneMap[i]); if (version < 32) return; Write(stream, chunk.vertSize);