From b5ed321b9ec5c0eb0c20bf6b6c18e23670bb6865 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Tue, 20 Oct 2026 04:19:56 +0200 Subject: [PATCH] wip(L16, side branch): disk terrain cache design + headless Linux prototype Exact lossless zlib tile records (keyed on world/season/recipe/plan/evaluator/options/tile), pristine cache plus ordered edits, server never trusts client caches, corruption/stale rejection, atomic writes, 2 GB cap. Loaded tiles match regenerated hashes (L1 view, 24 m crossing). NOT for assembler/world yet: the shared StreamBenchmarkPipeline.h includes Linux-only headers (unistd/fcntl/zlib), which would break the Windows viewer build. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX --- Standalone/BuildLinux.sh | 2 +- .../WorldAssemblerM3/Linux16/MeasureZstd.py | 88 ++ .../WorldAssemblerM3/Linux16/REPORT.md | 232 ++++ .../Linux16/TileCachePrototype.h | 1201 +++++++++++++++++ Standalone/Source/StreamBenchmarkHeadless.cpp | 17 +- Standalone/Source/StreamBenchmarkPipeline.h | 119 +- Standalone/Source/StreamingWorld.h | 18 +- 7 files changed, 1664 insertions(+), 13 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Linux16/MeasureZstd.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux16/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux16/TileCachePrototype.h diff --git a/Standalone/BuildLinux.sh b/Standalone/BuildLinux.sh index 9e96f42..eb0b224 100755 --- a/Standalone/BuildLinux.sh +++ b/Standalone/BuildLinux.sh @@ -118,7 +118,7 @@ PIDS=() CORE_LIBRARY="$BUILD_DIR/libVoxelForgeCore.a" "$(command -v ar)" rcs "$CORE_LIBRARY" "${CORE_OBJECTS[@]}" OUTPUT="$BIN_DIR/VoxelForgeCoreHeadless-$VARIANT" -"$COMPILER" -pthread "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT" +"$COMPILER" -pthread "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -lz -o "$OUTPUT" CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT" "$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \ "$ROOT/Source/LinuxOffscreenCapture.cpp" -o "$CAPTURE_OUTPUT" \ diff --git a/Standalone/Research/WorldAssemblerM3/Linux16/MeasureZstd.py b/Standalone/Research/WorldAssemblerM3/Linux16/MeasureZstd.py new file mode 100644 index 0000000..afbe34d --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux16/MeasureZstd.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +"""Per-tile zstd CLI comparison over the pristine L1 records from this round.""" + +from pathlib import Path +import shutil +import struct +import subprocess +import time +import zlib + + +REPO = Path.cwd().resolve() +CACHE = REPO / "Saved/Linux/cache/l16-prototype" +CAPTURE_LOG = REPO / "Saved/Linux/l16-cache-l1-q24-full-write.log" +TEMP = CACHE / "codec-zstd" +RESULT = REPO / "Saved/Linux/l16-zstd-codec.txt" + +assert CACHE.is_dir() and not CACHE.is_symlink() +assert CACHE.resolve().parent == (REPO / "Saved/Linux/cache").resolve() +assert CAPTURE_LOG.is_file() +assert not TEMP.exists() + +cutoff_ns = CAPTURE_LOG.stat().st_mtime_ns +records = [ + path for world in CACHE.glob("world_*") + for path in world.glob("*.vftile") + if "_s0_d0000000000000000_" in path.name and path.stat().st_mtime_ns <= cutoff_ns +] +records.sort() +assert len(records) == 2553, f"expected 2553 L1 pristine records, found {len(records)}" + +codec = shutil.which("zstd") +assert codec, "zstd CLI is not available" +TEMP.mkdir() +per_lod = {} +try: + for source in records: + record = source.read_bytes() + assert record[:8] == b"VFTILE16" + raw_size, compressed_size = struct.unpack_from("`, `` and ``. That would break the MSVC build. Kept on side + branch `wip/l16-tile-cache`. Before merging, the prototype must be platform-guarded (or the cache + given a portable I/O and codec layer), and zlib needs a Windows source (UE ships zlib). +- Cache-off cost re-checked by Luna at −0.14% (within noise). diff --git a/Standalone/Research/WorldAssemblerM3/Linux16/TileCachePrototype.h b/Standalone/Research/WorldAssemblerM3/Linux16/TileCachePrototype.h new file mode 100644 index 0000000..a41b838 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux16/TileCachePrototype.h @@ -0,0 +1,1201 @@ +#pragma once + +#include "VoxelForgeCore/VoxelForgeCore.h" +#include "../../../Source/OwnerWorldLoader.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(__linux__) && !defined(O_DIRECT) +#define O_DIRECT 040000 +#endif + +namespace StandaloneTileCachePrototype +{ + using Clock = std::chrono::steady_clock; + using Byte = std::uint8_t; + + inline constexpr std::uint32_t FormatVersion = 1; + inline constexpr std::uint32_t MesherContractVersion = 1; + inline constexpr std::uint32_t FloatingPointContractVersion = 1; // Core preamble: FMA off. + inline constexpr std::uint64_t MaximumDiskBytes = 2'000'000'000ull; + inline constexpr std::uint64_t MaximumRecordBytes = 512ull * 1024ull * 1024ull; + inline constexpr std::size_t IoAlignment = 4096; + + struct Identity + { + std::uint64_t ownerSnapshotHash = 0; + std::uint64_t worldConfigurationHash = 0; + std::uint64_t recipeHash = 0; + std::uint64_t planHash = 0; + std::uint64_t manifestHash = 0; + std::int32_t seed = 0; + std::uint32_t schemaVersion = VoxelForgeCore::WorldAssemblySchemaVersion; + std::uint32_t evaluatorVersion = VoxelForgeCore::WorldAssemblyEvaluatorVersion; + std::uint32_t voxelSizeMicrons = 250000; + std::uint32_t tileVoxelCount = VoxelForgeCore::CoreTileVoxelCount; + std::uint32_t mesherContractVersion = MesherContractVersion; + std::uint32_t floatingPointContractVersion = FloatingPointContractVersion; + std::uint64_t editSequence = 0; + std::uint64_t editDigest = 0; + }; + + struct CodecSample + { + std::uint64_t rawBytes = 0; + std::uint64_t zlibBytes = 0; + std::uint64_t meshBytes = 0; + std::uint64_t rawEncodeNs = 0, rawDecodeNs = 0; + std::uint64_t zlibEncodeNs = 0, zlibDecodeNs = 0; + std::uint64_t meshEncodeNs = 0, meshDecodeNs = 0; + std::uint64_t generationNs = 0, roundTripNs = 0, cacheLoadNs = 0; + double maximumProjectedVertexErrorPx = 0.0; + double maximumLambertDelta = 0.0; + double maximumNormalAngleDegrees = 0.0; + std::uint64_t windingChanges = 0; + std::uint64_t degeneracyChanges = 0; + }; + + struct LodStats + { + std::uint64_t generated = 0, hits = 0, roundTrips = 0, rejects = 0; + std::uint64_t rawBytes = 0, zlibBytes = 0, meshBytes = 0, diskBytes = 0; + std::uint64_t rawEncodeNs = 0, rawDecodeNs = 0; + std::uint64_t zlibEncodeNs = 0, zlibDecodeNs = 0; + std::uint64_t meshEncodeNs = 0, meshDecodeNs = 0; + std::uint64_t generationNs = 0, roundTripNs = 0, cacheLoadNs = 0; + double maxProjectedVertexErrorPx = 0.0, maxLambertDelta = 0.0; + double maxNormalAngleDegrees = 0.0; + std::uint64_t windingChanges = 0, degeneracyChanges = 0; + }; + + inline void AppendUnsigned(std::vector& Out, std::uint64_t Value, unsigned Bytes) + { + for (unsigned I = 0; I < Bytes; ++I) + Out.push_back(static_cast(Value >> (I * 8))); + } + + inline void AppendSigned(std::vector& Out, std::int64_t Value, unsigned Bytes) + { AppendUnsigned(Out, static_cast(Value), Bytes); } + + inline void AppendFloat(std::vector& Out, float Value) + { AppendUnsigned(Out, std::bit_cast(Value), 4); } + + inline void AppendDouble(std::vector& Out, double Value) + { AppendUnsigned(Out, std::bit_cast(Value), 8); } + + inline bool ReadUnsigned(std::span Input, std::size_t& Offset, + unsigned Bytes, std::uint64_t& Value) + { + if (Bytes > 8 || Offset > Input.size() || Input.size() - Offset < Bytes) return false; + Value = 0; + for (unsigned I = 0; I < Bytes; ++I) + Value |= static_cast(Input[Offset++]) << (I * 8); + return true; + } + + inline bool ReadFloat(std::span Input, std::size_t& Offset, float& Value) + { + std::uint64_t Word = 0; + if (!ReadUnsigned(Input, Offset, 4, Word)) return false; + Value = std::bit_cast(static_cast(Word)); + return true; + } + + inline bool ReadDouble(std::span Input, std::size_t& Offset, double& Value) + { + std::uint64_t Word = 0; + if (!ReadUnsigned(Input, Offset, 8, Word)) return false; + Value = std::bit_cast(Word); + return true; + } + + inline std::uint64_t HashBytes(std::span Bytes) + { + std::uint64_t Hash = 14695981039346656037ull; + for (const Byte Value : Bytes) { Hash ^= Value; Hash *= 1099511628211ull; } + return Hash; + } + + inline std::uint64_t HashFile(const std::filesystem::path& Path) + { + std::ifstream File(Path, std::ios::binary); + if (!File) return 0; + std::uint64_t Hash = 14695981039346656037ull; + std::array Buffer{}; + while (File) + { + File.read(reinterpret_cast(Buffer.data()), static_cast(Buffer.size())); + const std::streamsize Count = File.gcount(); + for (std::streamsize I = 0; I < Count; ++I) + { Hash ^= Buffer[static_cast(I)]; Hash *= 1099511628211ull; } + } + return File.eof() ? Hash : 0; + } + + inline Identity MakeIdentity(const FStandaloneOwnerWorld& Owner, + const VoxelForgeCore::ResolvedWorldSnapshot& Snapshot, + const std::filesystem::path& OwnerSnapshotPath) + { + Identity Result; + Result.ownerSnapshotHash = HashFile(OwnerSnapshotPath); + Result.seed = Owner.Seed; + const auto& Config = Snapshot.configuration(); + std::vector ConfigBytes; + AppendUnsigned(ConfigBytes, Config.voxelSizeMicrons, 4); + AppendUnsigned(ConfigBytes, Config.tileVoxelCount, 4); + AppendSigned(ConfigBytes, Config.seed, 4); + AppendSigned(ConfigBytes, Config.clipRadius, 4); + AppendSigned(ConfigBytes, Config.maxLod, 4); + AppendSigned(ConfigBytes, Config.fullResolutionLodCount, 4); + AppendSigned(ConfigBytes, Config.coarseTileCells, 4); + AppendSigned(ConfigBytes, Config.renderDistanceChunks, 4); + AppendSigned(ConfigBytes, Config.contentCutMinLod, 4); + AppendSigned(ConfigBytes, Config.skirtCells, 4); + AppendSigned(ConfigBytes, Config.lodOctaveDrop, 4); + AppendSigned(ConfigBytes, Config.viewDistanceUp, 4); + AppendSigned(ConfigBytes, Config.viewDistanceDown, 4); + AppendSigned(ConfigBytes, Config.strateViewMarginChunks, 4); + AppendUnsigned(ConfigBytes, Config.clampViewToStrate, 1); + AppendUnsigned(ConfigBytes, Config.generateSkirts, 1); + AppendUnsigned(ConfigBytes, Config.viewerStartFound, 1); + AppendDouble(ConfigBytes, Config.viewerStartVoxels.x); + AppendDouble(ConfigBytes, Config.viewerStartVoxels.y); + AppendDouble(ConfigBytes, Config.viewerStartVoxels.z); + Result.worldConfigurationHash = HashBytes(ConfigBytes); + Result.voxelSizeMicrons = Config.voxelSizeMicrons; + Result.tileVoxelCount = Config.tileVoxelCount; + if (const auto* Assembly = Snapshot.worldAssembly()) + { + Result.schemaVersion = Assembly->schemaVersion; + Result.evaluatorVersion = Assembly->recipe.evaluatorVersion; + Result.recipeHash = Assembly->recipe.contentHash; + Result.planHash = Assembly->plan.contentHash; + Result.manifestHash = Assembly->contentHash; + } + return Result; + } + + inline std::uint64_t HashOptions(const VoxelForgeCore::TileGenerationOptions& Options) + { + std::vector Bytes; + AppendUnsigned(Bytes, static_cast(Options.mesherRecipe), 1); + AppendSigned(Bytes, Options.bandMinZVoxel, 4); + AppendSigned(Bytes, Options.bandMaxZVoxel, 4); + AppendUnsigned(Bytes, Options.hashDensity, 1); + AppendUnsigned(Bytes, Options.skipUvAndColors, 1); + AppendUnsigned(Bytes, Options.skipUv, 1); + AppendUnsigned(Bytes, Options.skipColors, 1); + AppendUnsigned(Bytes, Options.skipMaterialGroups, 1); + AppendUnsigned(Bytes, Options.farHorizonVersion, 4); + AppendSigned(Bytes, Options.lod8HorizonInteriorStepVoxels, 4); + AppendUnsigned(Bytes, Options.diagnosticGlobalOctaveDrop, 4); + AppendUnsigned(Bytes, Options.diagnosticCellStride, 4); + AppendUnsigned(Bytes, Options.diagnosticColumnInterpolationStride, 4); + AppendUnsigned(Bytes, Options.diagnosticForceNativeOnly, 1); + return HashBytes(Bytes); + } + + inline bool IdentityEqual(const Identity& A, const Identity& B) + { + return A.ownerSnapshotHash == B.ownerSnapshotHash + && A.worldConfigurationHash == B.worldConfigurationHash + && A.recipeHash == B.recipeHash && A.planHash == B.planHash + && A.manifestHash == B.manifestHash && A.seed == B.seed + && A.schemaVersion == B.schemaVersion && A.evaluatorVersion == B.evaluatorVersion + && A.voxelSizeMicrons == B.voxelSizeMicrons && A.tileVoxelCount == B.tileVoxelCount + && A.mesherContractVersion == B.mesherContractVersion + && A.floatingPointContractVersion == B.floatingPointContractVersion + && A.editSequence == B.editSequence && A.editDigest == B.editDigest; + } + + inline void AppendIdentity(std::vector& Bytes, const Identity& Value) + { + AppendUnsigned(Bytes, Value.ownerSnapshotHash, 8); + AppendUnsigned(Bytes, Value.worldConfigurationHash, 8); + AppendUnsigned(Bytes, Value.recipeHash, 8); + AppendUnsigned(Bytes, Value.planHash, 8); + AppendUnsigned(Bytes, Value.manifestHash, 8); + AppendSigned(Bytes, Value.seed, 4); + AppendUnsigned(Bytes, Value.schemaVersion, 4); + AppendUnsigned(Bytes, Value.evaluatorVersion, 4); + AppendUnsigned(Bytes, Value.voxelSizeMicrons, 4); + AppendUnsigned(Bytes, Value.tileVoxelCount, 4); + AppendUnsigned(Bytes, Value.mesherContractVersion, 4); + AppendUnsigned(Bytes, Value.floatingPointContractVersion, 4); + AppendUnsigned(Bytes, Value.editSequence, 8); + AppendUnsigned(Bytes, Value.editDigest, 8); + } + + inline bool ReadIdentity(std::span Bytes, std::size_t& Offset, Identity& Value) + { + std::uint64_t Word = 0; +#define VF_READ_ID_U64(Field) if (!ReadUnsigned(Bytes, Offset, 8, Word)) return false; Value.Field = Word +#define VF_READ_ID_U32(Field) if (!ReadUnsigned(Bytes, Offset, 4, Word)) return false; Value.Field = static_cast(Word) + if (!ReadUnsigned(Bytes, Offset, 8, Word)) return false; Value.ownerSnapshotHash = Word; + if (!ReadUnsigned(Bytes, Offset, 8, Word)) return false; Value.worldConfigurationHash = Word; + if (!ReadUnsigned(Bytes, Offset, 8, Word)) return false; Value.recipeHash = Word; + if (!ReadUnsigned(Bytes, Offset, 8, Word)) return false; Value.planHash = Word; + if (!ReadUnsigned(Bytes, Offset, 8, Word)) return false; Value.manifestHash = Word; + if (!ReadUnsigned(Bytes, Offset, 4, Word)) return false; Value.seed = static_cast(Word); + VF_READ_ID_U32(schemaVersion); + VF_READ_ID_U32(evaluatorVersion); + VF_READ_ID_U32(voxelSizeMicrons); + VF_READ_ID_U32(tileVoxelCount); + VF_READ_ID_U32(mesherContractVersion); + VF_READ_ID_U32(floatingPointContractVersion); + VF_READ_ID_U64(editSequence); + VF_READ_ID_U64(editDigest); +#undef VF_READ_ID_U64 +#undef VF_READ_ID_U32 + return true; + } + + inline bool EncodeRawMesh(const VoxelForgeCore::TileMesh& Mesh, std::vector& Out) + { + Out.clear(); + if (Mesh.vertices.size() > 10'000'000 || Mesh.indices.size() > 60'000'000 + || Mesh.skirtFaceMasks.size() != Mesh.indices.size() / 3 + || Mesh.indices.size() % 3 != 0) return false; + AppendSigned(Out, Mesh.key.coordinate.x, 4); + AppendSigned(Out, Mesh.key.coordinate.y, 4); + AppendSigned(Out, Mesh.key.coordinate.z, 4); + AppendSigned(Out, Mesh.key.lod, 4); + AppendUnsigned(Out, Mesh.groups.surfaceVertexCount, 4); + AppendUnsigned(Out, Mesh.groups.groundTriangleCount, 4); + AppendUnsigned(Out, Mesh.groups.ceilingTriangleCount, 4); + AppendUnsigned(Out, Mesh.groups.skirtTriangleCount, 4); + AppendUnsigned(Out, Mesh.densitySampleCount, 8); + AppendUnsigned(Out, Mesh.meshPayloadAllocatedBytes, 8); + AppendUnsigned(Out, Mesh.densityHash, 8); + AppendUnsigned(Out, Mesh.meshHash, 8); + AppendUnsigned(Out, Mesh.payloadHash, 8); + AppendUnsigned(Out, Mesh.topologyHash, 8); + const std::uint8_t Flags = (Mesh.hasDensityHash ? 1u : 0u) + | (Mesh.hasMeshHash ? 2u : 0u) | (Mesh.hasPayloadHash ? 4u : 0u) + | (Mesh.hasTopologyHash ? 8u : 0u); + AppendUnsigned(Out, Flags, 1); + AppendUnsigned(Out, Mesh.vertices.size(), 8); + AppendUnsigned(Out, Mesh.indices.size(), 8); + AppendUnsigned(Out, Mesh.skirtFaceMasks.size(), 8); + for (const auto& Vertex : Mesh.vertices) + { + AppendDouble(Out, Vertex.position.x); AppendDouble(Out, Vertex.position.y); + AppendDouble(Out, Vertex.position.z); AppendDouble(Out, Vertex.normal.x); + AppendDouble(Out, Vertex.normal.y); AppendDouble(Out, Vertex.normal.z); + AppendFloat(Out, Vertex.uv.x); AppendFloat(Out, Vertex.uv.y); + AppendUnsigned(Out, Vertex.color.r, 1); AppendUnsigned(Out, Vertex.color.g, 1); + AppendUnsigned(Out, Vertex.color.b, 1); AppendUnsigned(Out, Vertex.color.a, 1); + } + for (const std::uint32_t Index : Mesh.indices) AppendUnsigned(Out, Index, 4); + Out.insert(Out.end(), Mesh.skirtFaceMasks.begin(), Mesh.skirtFaceMasks.end()); + return Out.size() <= MaximumRecordBytes; + } + + inline bool DecodeRawMesh(std::span Bytes, VoxelForgeCore::TileMesh& Mesh) + { + std::size_t Offset = 0; + std::uint64_t Word = 0; + std::int32_t Values[4]{}; + for (int I = 0; I < 4; ++I) + { + if (!ReadUnsigned(Bytes, Offset, 4, Word)) return false; + Values[I] = static_cast(Word); + } + Mesh = {}; + Mesh.key = {{Values[0], Values[1], Values[2]}, Values[3]}; + std::uint32_t GroupValues[4]{}; + for (std::uint32_t& Group : GroupValues) + { if (!ReadUnsigned(Bytes, Offset, 4, Word)) return false; Group = static_cast(Word); } + Mesh.groups = {GroupValues[0], GroupValues[1], GroupValues[2], GroupValues[3]}; + if (!ReadUnsigned(Bytes, Offset, 8, Mesh.densitySampleCount) + || !ReadUnsigned(Bytes, Offset, 8, Mesh.meshPayloadAllocatedBytes) + || !ReadUnsigned(Bytes, Offset, 8, Mesh.densityHash) + || !ReadUnsigned(Bytes, Offset, 8, Mesh.meshHash) + || !ReadUnsigned(Bytes, Offset, 8, Mesh.payloadHash) + || !ReadUnsigned(Bytes, Offset, 8, Mesh.topologyHash) + || !ReadUnsigned(Bytes, Offset, 1, Word)) return false; + const std::uint8_t Flags = static_cast(Word); + Mesh.hasDensityHash = (Flags & 1u) != 0; Mesh.hasMeshHash = (Flags & 2u) != 0; + Mesh.hasPayloadHash = (Flags & 4u) != 0; Mesh.hasTopologyHash = (Flags & 8u) != 0; + std::uint64_t VertexCount = 0, IndexCount = 0, MaskCount = 0; + if (!ReadUnsigned(Bytes, Offset, 8, VertexCount) + || !ReadUnsigned(Bytes, Offset, 8, IndexCount) + || !ReadUnsigned(Bytes, Offset, 8, MaskCount) + || VertexCount > 10'000'000 || IndexCount > 60'000'000 + || IndexCount % 3 != 0 || MaskCount != IndexCount / 3) return false; + Mesh.vertices.resize(static_cast(VertexCount)); + Mesh.indices.resize(static_cast(IndexCount)); + Mesh.skirtFaceMasks.resize(static_cast(MaskCount)); + for (auto& Vertex : Mesh.vertices) + { + if (!ReadDouble(Bytes, Offset, Vertex.position.x) || !ReadDouble(Bytes, Offset, Vertex.position.y) + || !ReadDouble(Bytes, Offset, Vertex.position.z) || !ReadDouble(Bytes, Offset, Vertex.normal.x) + || !ReadDouble(Bytes, Offset, Vertex.normal.y) || !ReadDouble(Bytes, Offset, Vertex.normal.z) + || !ReadFloat(Bytes, Offset, Vertex.uv.x) || !ReadFloat(Bytes, Offset, Vertex.uv.y)) return false; + if (!ReadUnsigned(Bytes, Offset, 1, Word)) return false; Vertex.color.r = static_cast(Word); + if (!ReadUnsigned(Bytes, Offset, 1, Word)) return false; Vertex.color.g = static_cast(Word); + if (!ReadUnsigned(Bytes, Offset, 1, Word)) return false; Vertex.color.b = static_cast(Word); + if (!ReadUnsigned(Bytes, Offset, 1, Word)) return false; Vertex.color.a = static_cast(Word); + if (!std::isfinite(Vertex.position.x) || !std::isfinite(Vertex.position.y) + || !std::isfinite(Vertex.position.z) || !std::isfinite(Vertex.normal.x) + || !std::isfinite(Vertex.normal.y) || !std::isfinite(Vertex.normal.z) + || !std::isfinite(Vertex.uv.x) || !std::isfinite(Vertex.uv.y)) return false; + } + for (std::uint32_t& Index : Mesh.indices) + { + if (!ReadUnsigned(Bytes, Offset, 4, Word) || Word >= VertexCount) return false; + Index = static_cast(Word); + } + if (Offset > Bytes.size() || Bytes.size() - Offset != Mesh.skirtFaceMasks.size()) return false; + std::copy(Bytes.begin() + static_cast(Offset), Bytes.end(), Mesh.skirtFaceMasks.begin()); + return Mesh.key.lod >= 0 && Mesh.key.lod <= 8; + } + + inline bool SameExactMesh(const VoxelForgeCore::TileMesh& A, const VoxelForgeCore::TileMesh& B) + { + if (!(A.key == B.key) || A.groups.surfaceVertexCount != B.groups.surfaceVertexCount + || A.groups.groundTriangleCount != B.groups.groundTriangleCount + || A.groups.ceilingTriangleCount != B.groups.ceilingTriangleCount + || A.groups.skirtTriangleCount != B.groups.skirtTriangleCount + || A.densitySampleCount != B.densitySampleCount + || A.meshPayloadAllocatedBytes != B.meshPayloadAllocatedBytes + || A.densityHash != B.densityHash || A.meshHash != B.meshHash + || A.payloadHash != B.payloadHash || A.topologyHash != B.topologyHash + || A.hasDensityHash != B.hasDensityHash || A.hasMeshHash != B.hasMeshHash + || A.hasPayloadHash != B.hasPayloadHash || A.hasTopologyHash != B.hasTopologyHash + || A.vertices.size() != B.vertices.size() || A.indices != B.indices + || A.skirtFaceMasks != B.skirtFaceMasks) return false; + for (std::size_t I = 0; I < A.vertices.size(); ++I) + { + const auto& X = A.vertices[I]; const auto& Y = B.vertices[I]; + const double XP[] = {X.position.x, X.position.y, X.position.z, + X.normal.x, X.normal.y, X.normal.z}; + const double YP[] = {Y.position.x, Y.position.y, Y.position.z, + Y.normal.x, Y.normal.y, Y.normal.z}; + for (std::size_t J = 0; J < 6; ++J) + if (std::bit_cast(XP[J]) != std::bit_cast(YP[J])) return false; + if (std::bit_cast(X.uv.x) != std::bit_cast(Y.uv.x) + || std::bit_cast(X.uv.y) != std::bit_cast(Y.uv.y) + || X.color.r != Y.color.r || X.color.g != Y.color.g + || X.color.b != Y.color.b || X.color.a != Y.color.a) return false; + } + return true; + } + + inline void AppendVarUInt(std::vector& Out, std::uint32_t Value) + { + while (Value >= 0x80u) { Out.push_back(static_cast(Value | 0x80u)); Value >>= 7; } + Out.push_back(static_cast(Value)); + } + + inline bool ReadVarUInt(std::span Input, std::size_t& Offset, std::uint32_t& Value) + { + Value = 0; + for (unsigned Shift = 0; Shift < 35; Shift += 7) + { + if (Offset >= Input.size()) return false; + const Byte B = Input[Offset++]; + if (Shift == 28 && (B & 0xf0u) != 0) return false; + Value |= static_cast(B & 0x7fu) << Shift; + if ((B & 0x80u) == 0) return true; + } + return false; + } + + inline std::int16_t QuantSigned16(double Value) + { return static_cast(std::lrint(std::clamp(Value, -1.0, 1.0) * 32767.0)); } + + inline std::array OctEncode(VoxelForgeCore::Vec3d Normal) + { + double Length = std::sqrt(Normal.x * Normal.x + Normal.y * Normal.y + Normal.z * Normal.z); + if (!(Length > 0.0) || !std::isfinite(Length)) Normal = {0.0, 0.0, 1.0}; + else { Normal.x /= Length; Normal.y /= Length; Normal.z /= Length; } + const double Denom = std::abs(Normal.x) + std::abs(Normal.y) + std::abs(Normal.z); + double X = Normal.x / Denom, Y = Normal.y / Denom; + if (Normal.z < 0.0) + { + const double OldX = X, OldY = Y; + X = (1.0 - std::abs(OldY)) * (OldX < 0.0 ? -1.0 : 1.0); + Y = (1.0 - std::abs(OldX)) * (OldY < 0.0 ? -1.0 : 1.0); + } + return {QuantSigned16(X), QuantSigned16(Y)}; + } + + inline VoxelForgeCore::Vec3d OctDecode(std::int16_t XWord, std::int16_t YWord) + { + double X = std::clamp(static_cast(XWord) / 32767.0, -1.0, 1.0); + double Y = std::clamp(static_cast(YWord) / 32767.0, -1.0, 1.0); + double Z = 1.0 - std::abs(X) - std::abs(Y); + if (Z < 0.0) + { + const double OldX = X, OldY = Y; + X = (1.0 - std::abs(OldY)) * (OldX < 0.0 ? -1.0 : 1.0); + Y = (1.0 - std::abs(OldX)) * (OldY < 0.0 ? -1.0 : 1.0); + } + const double Length = std::sqrt(X * X + Y * Y + Z * Z); + return Length > 0.0 ? VoxelForgeCore::Vec3d{X / Length, Y / Length, Z / Length} + : VoxelForgeCore::Vec3d{0.0, 0.0, 1.0}; + } + + inline bool EncodeMeshSpecific(const VoxelForgeCore::TileMesh& Mesh, std::vector& Out) + { + Out.clear(); + if (Mesh.vertices.size() > 10'000'000 || Mesh.indices.size() > 60'000'000) return false; + AppendSigned(Out, Mesh.key.coordinate.x, 4); AppendSigned(Out, Mesh.key.coordinate.y, 4); + AppendSigned(Out, Mesh.key.coordinate.z, 4); AppendSigned(Out, Mesh.key.lod, 4); + AppendUnsigned(Out, Mesh.groups.surfaceVertexCount, 4); + AppendUnsigned(Out, Mesh.groups.groundTriangleCount, 4); + AppendUnsigned(Out, Mesh.groups.ceilingTriangleCount, 4); + AppendUnsigned(Out, Mesh.groups.skirtTriangleCount, 4); + AppendUnsigned(Out, Mesh.densitySampleCount, 8); + AppendUnsigned(Out, Mesh.vertices.size(), 8); AppendUnsigned(Out, Mesh.indices.size(), 8); + AppendUnsigned(Out, Mesh.skirtFaceMasks.size(), 8); + std::array Min{INFINITY, INFINITY, INFINITY}, Max{-INFINITY, -INFINITY, -INFINITY}; + for (const auto& V : Mesh.vertices) + { + const double P[] = {V.position.x, V.position.y, V.position.z}; + for (int Axis = 0; Axis < 3; ++Axis) { Min[Axis] = std::min(Min[Axis], P[Axis]); Max[Axis] = std::max(Max[Axis], P[Axis]); } + } + if (Mesh.vertices.empty()) { Min = {0.0, 0.0, 0.0}; Max = Min; } + for (int Axis = 0; Axis < 3; ++Axis) { AppendDouble(Out, Min[Axis]); AppendDouble(Out, Max[Axis]); } + for (const auto& V : Mesh.vertices) + { + const double P[] = {V.position.x, V.position.y, V.position.z}; + for (int Axis = 0; Axis < 3; ++Axis) + { + const double Range = Max[Axis] - Min[Axis]; + // Q16 failed the L1 near-surface screen gate. Keep the useful mesh layout, + // but use 24 bits per axis for the candidate reported by the final run. + const std::uint32_t Q = Range > 0.0 + ? static_cast(std::llround(std::clamp((P[Axis] - Min[Axis]) / Range, 0.0, 1.0) * 16777215.0)) + : 0; + AppendUnsigned(Out, Q, 3); + } + const auto Oct = OctEncode(V.normal); + AppendSigned(Out, Oct[0], 2); AppendSigned(Out, Oct[1], 2); + AppendFloat(Out, V.uv.x); AppendFloat(Out, V.uv.y); + AppendUnsigned(Out, V.color.r, 1); AppendUnsigned(Out, V.color.g, 1); + AppendUnsigned(Out, V.color.b, 1); AppendUnsigned(Out, V.color.a, 1); + } + std::int64_t Previous = 0; + for (const std::uint32_t Index : Mesh.indices) + { + const std::int64_t Delta = static_cast(Index) - Previous; + const std::uint32_t Zigzag = static_cast(Delta < 0 ? (-Delta * 2 - 1) : (Delta * 2)); + AppendVarUInt(Out, Zigzag); Previous = Index; + } + Out.insert(Out.end(), Mesh.skirtFaceMasks.begin(), Mesh.skirtFaceMasks.end()); + return Out.size() <= MaximumRecordBytes; + } + + struct MeshEncodingGate + { + double maxProjectedVertexErrorPx = 0.0; + double maxLambertDelta = 0.0; + double maxNormalAngleDegrees = 0.0; + std::uint64_t windingChanges = 0; + std::uint64_t degeneracyChanges = 0; + bool passed = false; + }; + + inline bool DecodeMeshSpecific(std::span Bytes, VoxelForgeCore::TileMesh& Mesh, + double ClosestDisplayMeters, MeshEncodingGate& Gate) + { + std::size_t Offset = 0; std::uint64_t Word = 0; + std::int32_t K[4]{}; + for (int I = 0; I < 4; ++I) { if (!ReadUnsigned(Bytes, Offset, 4, Word)) return false; K[I] = static_cast(Word); } + Mesh = {}; Mesh.key = {{K[0], K[1], K[2]}, K[3]}; + std::uint32_t G[4]{}; + for (auto& V : G) { if (!ReadUnsigned(Bytes, Offset, 4, Word)) return false; V = static_cast(Word); } + Mesh.groups = {G[0], G[1], G[2], G[3]}; + std::uint64_t VertexCount = 0, IndexCount = 0, MaskCount = 0; + if (!ReadUnsigned(Bytes, Offset, 8, Mesh.densitySampleCount) + || !ReadUnsigned(Bytes, Offset, 8, VertexCount) || !ReadUnsigned(Bytes, Offset, 8, IndexCount) + || !ReadUnsigned(Bytes, Offset, 8, MaskCount) || VertexCount > 10'000'000 + || IndexCount > 60'000'000 || IndexCount % 3 != 0 || MaskCount != IndexCount / 3) return false; + std::array Min{}, Max{}; + for (int Axis = 0; Axis < 3; ++Axis) if (!ReadDouble(Bytes, Offset, Min[Axis]) || !ReadDouble(Bytes, Offset, Max[Axis])) return false; + Mesh.vertices.resize(static_cast(VertexCount)); + for (auto& V : Mesh.vertices) + { + double* P[] = {&V.position.x, &V.position.y, &V.position.z}; + for (int Axis = 0; Axis < 3; ++Axis) + { + if (!ReadUnsigned(Bytes, Offset, 3, Word)) return false; + *P[Axis] = Min[Axis] + (Max[Axis] - Min[Axis]) * static_cast(Word) / 16777215.0; + } + std::uint64_t XWord = 0, YWord = 0; + if (!ReadUnsigned(Bytes, Offset, 2, XWord) || !ReadUnsigned(Bytes, Offset, 2, YWord)) return false; + V.normal = OctDecode(static_cast(XWord), static_cast(YWord)); + if (!ReadFloat(Bytes, Offset, V.uv.x) || !ReadFloat(Bytes, Offset, V.uv.y)) return false; + if (!ReadUnsigned(Bytes, Offset, 1, Word)) return false; V.color.r = static_cast(Word); + if (!ReadUnsigned(Bytes, Offset, 1, Word)) return false; V.color.g = static_cast(Word); + if (!ReadUnsigned(Bytes, Offset, 1, Word)) return false; V.color.b = static_cast(Word); + if (!ReadUnsigned(Bytes, Offset, 1, Word)) return false; V.color.a = static_cast(Word); + } + Mesh.indices.resize(static_cast(IndexCount)); + std::int64_t Previous = 0; + for (std::uint32_t& Index : Mesh.indices) + { + std::uint32_t Zigzag = 0; + if (!ReadVarUInt(Bytes, Offset, Zigzag)) return false; + const std::int64_t Delta = static_cast(Zigzag >> 1) ^ -static_cast(Zigzag & 1u); + const std::int64_t Decoded = Previous + Delta; + if (Decoded < 0 || static_cast(Decoded) >= VertexCount) return false; + Index = static_cast(Decoded); Previous = Decoded; + } + if (Offset > Bytes.size() || Bytes.size() - Offset != MaskCount) return false; + Mesh.skirtFaceMasks.assign(Bytes.begin() + static_cast(Offset), Bytes.end()); + Gate.maxProjectedVertexErrorPx = 0.0; + Gate.maxLambertDelta = 0.0; + Gate.maxNormalAngleDegrees = 0.0; + // Geometry and normal comparison is completed by AuditMeshEncoding below, which has source data. + Gate.passed = Mesh.key.lod >= 0 && Mesh.key.lod <= 8 && ClosestDisplayMeters > 0.0; + return true; + } + + inline double Dot(VoxelForgeCore::Vec3d A, VoxelForgeCore::Vec3d B) + { return A.x * B.x + A.y * B.y + A.z * B.z; } + + inline VoxelForgeCore::Vec3d Normalize(VoxelForgeCore::Vec3d V) + { + const double L = std::sqrt(Dot(V, V)); + return L > 0.0 ? VoxelForgeCore::Vec3d{V.x / L, V.y / L, V.z / L} + : VoxelForgeCore::Vec3d{0.0, 0.0, 1.0}; + } + + inline void AuditMeshEncoding(const VoxelForgeCore::TileMesh& Source, + const VoxelForgeCore::TileMesh& Decoded, double ClosestDisplayMeters, + MeshEncodingGate& Gate) + { + constexpr double FocalPixels = 1280.0; // L15 gate: 2560 px, horizontal FOV 90 degrees. + constexpr double Pi = 3.14159265358979323846; + const VoxelForgeCore::Vec3d Light = Normalize({0.37, -0.48, 0.79}); + Gate.maxProjectedVertexErrorPx = 0.0; Gate.maxLambertDelta = 0.0; + Gate.maxNormalAngleDegrees = 0.0; Gate.windingChanges = 0; Gate.degeneracyChanges = 0; + for (std::size_t I = 0; I < Source.vertices.size() && I < Decoded.vertices.size(); ++I) + { + const auto& A = Source.vertices[I]; const auto& B = Decoded.vertices[I]; + const double DX = A.position.x - B.position.x; + const double DY = A.position.y - B.position.y; + const double DZ = A.position.z - B.position.z; + const double ErrorMeters = std::sqrt(DX * DX + DY * DY + DZ * DZ) / 100.0; + Gate.maxProjectedVertexErrorPx = std::max(Gate.maxProjectedVertexErrorPx, + ErrorMeters * FocalPixels / std::max(ClosestDisplayMeters, 0.01)); + const auto NA = Normalize(A.normal), NB = Normalize(B.normal); + const double Cosine = std::clamp(Dot(NA, NB), -1.0, 1.0); + Gate.maxNormalAngleDegrees = std::max(Gate.maxNormalAngleDegrees, std::acos(Cosine) * 180.0 / Pi); + const double LA = std::max(0.0, Dot(NA, Light)); + const double LB = std::max(0.0, Dot(NB, Light)); + Gate.maxLambertDelta = std::max(Gate.maxLambertDelta, std::abs(LA - LB)); + } + const auto Cross = [](VoxelForgeCore::Vec3d A, VoxelForgeCore::Vec3d B, VoxelForgeCore::Vec3d C) + { + const VoxelForgeCore::Vec3d U{B.x - A.x, B.y - A.y, B.z - A.z}; + const VoxelForgeCore::Vec3d V{C.x - A.x, C.y - A.y, C.z - A.z}; + return VoxelForgeCore::Vec3d{U.y * V.z - U.z * V.y, U.z * V.x - U.x * V.z, U.x * V.y - U.y * V.x}; + }; + for (std::size_t I = 0; I + 2 < Source.indices.size() && I + 2 < Decoded.indices.size(); I += 3) + { + const auto IA = Source.indices[I], IB = Source.indices[I + 1], IC = Source.indices[I + 2]; + if (IA >= Source.vertices.size() || IB >= Source.vertices.size() || IC >= Source.vertices.size()) continue; + const auto SA = Cross(Source.vertices[IA].position, Source.vertices[IB].position, Source.vertices[IC].position); + const auto DA = Cross(Decoded.vertices[IA].position, Decoded.vertices[IB].position, Decoded.vertices[IC].position); + const double SourceAreaSq = Dot(SA, SA), DecodedAreaSq = Dot(DA, DA); + if ((SourceAreaSq > 1e-20) != (DecodedAreaSq > 1e-20)) ++Gate.degeneracyChanges; + if (SourceAreaSq > 1e-20 && DecodedAreaSq > 1e-20 && Dot(SA, DA) < 0.0) ++Gate.windingChanges; + } + Gate.passed = Gate.maxProjectedVertexErrorPx <= 1.0 + && Gate.maxLambertDelta <= 1.0 / 255.0 + && Gate.windingChanges == 0 && Gate.degeneracyChanges == 0 + && Source.indices == Decoded.indices; + } + + inline std::uint64_t PaddedFileSize(std::uint64_t Bytes) + { return ((Bytes + IoAlignment - 1) / IoAlignment) * IoAlignment; } + + class TileDiskCache + { + public: + TileDiskCache(std::filesystem::path Root, Identity InIdentity, + std::array InClosestDisplayDistances) + : RootPath(std::move(Root)), CacheIdentity(InIdentity), + ClosestDisplayDistances(InClosestDisplayDistances) + { + std::error_code Error; + std::filesystem::create_directories(RootPath, Error); + if (Error) StartupError = "create_directories: " + Error.message(); + BytesOnDisk = ScanBytes(); + } + + const Identity& identity() const { return CacheIdentity; } + std::uint64_t bytesOnDisk() const { std::lock_guard Lock(BudgetMutex); return BytesOnDisk; } + std::uint64_t hitCount() const + { std::lock_guard Lock(MetricsMutex); std::uint64_t Total=0; for(const auto& S:Stats)Total+=S.hits; return Total; } + + std::filesystem::path TilePath(const VoxelForgeCore::TileKey& Key, + const Identity& IdentityValue, std::uint64_t OptionsHash) const + { + const auto AssemblyDir = "world_" + Hex(IdentityValue.ownerSnapshotHash) + + "_" + Hex(IdentityValue.manifestHash); + const auto Name = "lod" + std::to_string(Key.lod) + "_" + std::to_string(Key.coordinate.x) + + "_" + std::to_string(Key.coordinate.y) + "_" + std::to_string(Key.coordinate.z) + + "_e" + std::to_string(IdentityValue.evaluatorVersion) + + "_fp" + std::to_string(IdentityValue.floatingPointContractVersion) + + "_m" + std::to_string(IdentityValue.mesherContractVersion) + + "_s" + std::to_string(IdentityValue.editSequence) + "_d" + Hex(IdentityValue.editDigest) + + "_o" + Hex(OptionsHash) + ".vftile"; + return RootPath / AssemblyDir / Name; + } + + std::optional GenerateOrLoad( + const VoxelForgeCore::World& World, const VoxelForgeCore::TileKey& Key, + const VoxelForgeCore::TileGenerationOptions& Options, bool& bCacheHit, + std::string& Error) + { + bCacheHit = false; + if (!StartupError.empty()) { Error = StartupError; return std::nullopt; } + if (Options.captureDensitySamples || Options.forceFullDensityCapture + || Options.captureColumnCoordinates || Options.profileMesherStages + || Options.auditUniformBlockProofs) + return World.generateTile(Key, Options, Error); + + const std::uint64_t OptionsHash = HashOptions(Options); + VoxelForgeCore::TileMesh Loaded; + std::string LoadError; + const auto Path = TilePath(Key, CacheIdentity, OptionsHash); + const auto LoadStart = Clock::now(); + if (LoadFile(Path, Key, CacheIdentity, OptionsHash, Loaded, LoadError)) + { + bCacheHit = true; + AddCacheHit(Key.lod, std::chrono::duration_cast(Clock::now() - LoadStart).count()); + RememberSample(Loaded, OptionsHash); + return Loaded; + } + if (!LoadError.empty() && LoadError != "missing") AddReject(Key.lod); + + const auto GenerateStart = Clock::now(); + auto Generated = World.generateTile(Key, Options, Error); + const std::uint64_t GenerateNs = std::chrono::duration_cast(Clock::now() - GenerateStart).count(); + if (!Generated) return std::nullopt; + + CodecSample Sample; + std::vector Raw, Compressed, MeshBytes; + MeshEncodingGate MeshGate; + if (!MeasureCodecs(*Generated, Raw, Compressed, MeshBytes, Sample, MeshGate, Error)) + return Generated; + const bool bStored = StoreFile(Path, Key, CacheIdentity, OptionsHash, Raw, Compressed, Error); + if (!bStored) + { + RecordGenerated(Key.lod, Sample, GenerateNs, 0, 0, false, MeshGate); + Error.clear(); // Cache exhaustion or I/O failure must fall back to the generated tile. + return Generated; + } + const auto ReadStart = Clock::now(); + const bool bLoaded = LoadFile(Path, Key, CacheIdentity, OptionsHash, Loaded, LoadError); + const std::uint64_t RoundTripNs = std::chrono::duration_cast(Clock::now() - ReadStart).count(); + if (!bLoaded || !SameExactMesh(*Generated, Loaded)) + { + AddReject(Key.lod); + Error = bLoaded ? "tile mesh changed during exact cache round trip" : "cache round trip rejected: " + LoadError; + return std::nullopt; + } + std::error_code SizeError; + const std::uint64_t DiskSize = std::filesystem::file_size(Path, SizeError); + RecordGenerated(Key.lod, Sample, GenerateNs, RoundTripNs, SizeError ? 0 : DiskSize, true, MeshGate); + RememberSample(*Generated, OptionsHash); + return Loaded; + } + + bool LoadExpected(const VoxelForgeCore::TileKey& Key, + const VoxelForgeCore::TileGenerationOptions& Options, + VoxelForgeCore::TileMesh& Out, std::string& Error) const + { + return LoadFile(TilePath(Key, CacheIdentity, HashOptions(Options)), Key, + CacheIdentity, HashOptions(Options), Out, Error); + } + + bool StoreAndLoadExplicit(const VoxelForgeCore::TileMesh& Source, + const VoxelForgeCore::TileGenerationOptions& Options, + const Identity& IdentityValue, VoxelForgeCore::TileMesh& Out, std::string& Error) + { + std::vector Raw; + if (!EncodeRawMesh(Source, Raw)) { Error = "could not serialize explicit tile"; return false; } + std::vector Compressed; + if (!Compress(Raw, Compressed, Error)) return false; + const std::uint64_t OptionsHash = HashOptions(Options); + const auto Path = TilePath(Source.key, IdentityValue, OptionsHash); + if (!StoreFile(Path, Source.key, IdentityValue, OptionsHash, Raw, Compressed, Error)) return false; + const bool bLoaded=LoadFile(Path, Source.key, IdentityValue, OptionsHash, Out, Error) + && SameExactMesh(Source, Out); + if(bLoaded)RememberSample(Source,OptionsHash); + return bLoaded; + } + + Identity WithEdit(std::uint64_t Sequence, std::uint64_t Digest) const + { Identity Value = CacheIdentity; Value.editSequence = Sequence; Value.editDigest = Digest; return Value; } + + bool RunSafetyControls(const VoxelForgeCore::TileGenerationOptions& Options, std::string& Error) + { + std::optional Sample; + VoxelForgeCore::TileKey Key{}; + std::uint64_t OptionsHash = 0; + { + std::lock_guard Lock(MetricsMutex); + if (!SampleMesh) { Error = "no tile available for cache controls"; return false; } + Sample = SampleMesh; Key = SampleMesh->key; OptionsHash = SampleOptionsHash; + } + const auto Original = TilePath(Key, CacheIdentity, OptionsHash); + VoxelForgeCore::TileMesh Rejected; + Identity Stale = CacheIdentity; ++Stale.evaluatorVersion; + std::string StaleError; + const bool StaleLoaded = LoadFile(Original, Key, Stale, OptionsHash, Rejected, StaleError); + + Identity Edit = WithEdit(1, 0x6c31362d65646974ull); + std::string EditError; + const bool EditLoaded = LoadFile(Original, Key, Edit, OptionsHash, Rejected, EditError); + + const auto FlipPath = RootPath / "controls" / "flip.vftile"; + const auto TruncatePath = RootPath / "controls" / "truncate.vftile"; + std::error_code FsError; + std::filesystem::create_directories(FlipPath.parent_path(), FsError); + if (FsError) { Error = "control directory: " + FsError.message(); return false; } + std::vector Raw; + if (!EncodeRawMesh(*Sample, Raw)) { Error = "control serialization failed"; return false; } + std::vector Compressed; + if (!Compress(Raw, Compressed, Error)) return false; + if (!StoreFile(FlipPath, Key, CacheIdentity, OptionsHash, Raw, Compressed, Error) + || !StoreFile(TruncatePath, Key, CacheIdentity, OptionsHash, Raw, Compressed, Error)) return false; + const auto FlipSize = std::filesystem::file_size(FlipPath, FsError); + if (FsError || FlipSize < 64) { Error = "control record too short"; return false; } + { + std::fstream File(FlipPath, std::ios::binary | std::ios::in | std::ios::out); + if (!File) { Error = "cannot open flip control"; return false; } + // The fixed record header is 144 bytes. Flip a compressed payload byte so + // this control exercises the record checksum/decompressor, not only padding. + const std::size_t PayloadFlipOffset=144+std::min(8,Compressed.size()/2); + if(PayloadFlipOffset>=FlipSize){Error="corruption offset outside control record";return false;} + File.seekg(static_cast(PayloadFlipOffset)); + char Value = 0; File.read(&Value, 1); Value ^= 0x40; + File.seekp(static_cast(PayloadFlipOffset)); File.write(&Value, 1); File.flush(); + if (!File) { Error = "cannot flip control byte"; return false; } + } + std::string FlipError; + const bool FlipLoaded = LoadFile(FlipPath, Key, CacheIdentity, OptionsHash, Rejected, FlipError); + const auto TruncateSize = std::filesystem::file_size(TruncatePath, FsError); + if (FsError) { Error = "cannot stat truncate control"; return false; } + std::filesystem::resize_file(TruncatePath, TruncateSize / 2, FsError); + if (FsError) { Error = "cannot truncate control"; return false; } + std::string TruncateError; + const bool TruncateLoaded = LoadFile(TruncatePath, Key, CacheIdentity, OptionsHash, Rejected, TruncateError); + std::printf("[TileCacheSafety] evaluator_bump_rejected=%d error=%s edit_sequence_bump_rejected=%d error=%s flipped_bytes_rejected=%d error=%s truncated_rejected=%d error=%s status=%s\n", + StaleLoaded ? 0 : 1, StaleError.c_str(), EditLoaded ? 0 : 1, EditError.c_str(), + FlipLoaded ? 0 : 1, FlipError.c_str(), TruncateLoaded ? 0 : 1, TruncateError.c_str(), + !StaleLoaded && !EditLoaded && !FlipLoaded && !TruncateLoaded ? "PASS" : "FAIL"); + { + std::lock_guard Lock(BudgetMutex); + BytesOnDisk = ScanBytes(); + } + return !StaleLoaded && !EditLoaded && !FlipLoaded && !TruncateLoaded; + } + + void MeasureDiskReads() + { + std::vector Files; + { + std::lock_guard Lock(FilesMutex); + Files.assign(TileFiles.begin(), TileFiles.end()); + } + std::uint64_t DirectBytes = 0; + const auto DirectStart = Clock::now(); + bool DirectOkay = true; + for (const auto& Path : Files) + { + const int Fd = ::open(Path.c_str(), O_RDONLY | O_DIRECT); + if (Fd < 0) { DirectOkay = false; break; } + struct stat Stat{}; + if (::fstat(Fd, &Stat) != 0 || Stat.st_size < 0 + || static_cast(Stat.st_size) % IoAlignment != 0) + { ::close(Fd); DirectOkay = false; break; } + const std::size_t Size = static_cast(Stat.st_size); + void* Aligned = nullptr; + if (::posix_memalign(&Aligned, IoAlignment, std::max(IoAlignment, Size)) != 0) + { ::close(Fd); DirectOkay = false; break; } + const ssize_t ReadCount = ::pread(Fd, Aligned, Size, 0); + ::free(Aligned); ::close(Fd); + if (ReadCount != static_cast(Size)) { DirectOkay = false; break; } + DirectBytes += Size; + } + const double DirectSeconds = std::chrono::duration(Clock::now() - DirectStart).count(); + std::printf("[TileCacheReadTiming] mode=O_DIRECT_pagecache_bypass files=%zu bytes=%llu wall_s=%.6f mib_s=%.2f status=%s\n", + Files.size(), static_cast(DirectBytes), DirectSeconds, + DirectSeconds > 0.0 ? DirectBytes / 1048576.0 / DirectSeconds : 0.0, + DirectOkay ? "PASS" : "UNAVAILABLE"); + + std::uint64_t WarmBytes = 0; + const auto WarmStart = Clock::now(); + bool WarmOkay = true; + for (const auto& Path : Files) + { + std::ifstream File(Path, std::ios::binary | std::ios::ate); + if (!File) { WarmOkay = false; break; } + const auto End = File.tellg(); + if (End < 0 || static_cast(End) > MaximumRecordBytes) + { WarmOkay = false; break; } + std::vector Data(static_cast(End)); + File.seekg(0); File.read(reinterpret_cast(Data.data()), static_cast(End)); + if (!File) { WarmOkay = false; break; } + WarmBytes += Data.size(); + } + const double WarmSeconds = std::chrono::duration(Clock::now() - WarmStart).count(); + const auto Warm2Start = Clock::now(); + for (const auto& Path : Files) + { + std::ifstream File(Path, std::ios::binary | std::ios::ate); + if (!File) { WarmOkay = false; break; } + const auto End = File.tellg(); + std::vector Data(static_cast(End)); + File.seekg(0); File.read(reinterpret_cast(Data.data()), static_cast(End)); + if (!File) { WarmOkay = false; break; } + } + const double Warm2Seconds = std::chrono::duration(Clock::now() - Warm2Start).count(); + std::printf("[TileCacheReadTiming] mode=buffered_first_touch files=%zu bytes=%llu wall_s=%.6f mib_s=%.2f pagecache_state=not_flushed_or_dropped status=%s\n", + Files.size(), static_cast(WarmBytes), WarmSeconds, + WarmSeconds > 0.0 ? WarmBytes / 1048576.0 / WarmSeconds : 0.0, WarmOkay ? "PASS" : "FAIL"); + std::printf("[TileCacheReadTiming] mode=buffered_repeat files=%zu wall_s=%.6f pagecache=warm_after_first_touch status=%s\n", + Files.size(), Warm2Seconds, WarmOkay ? "PASS" : "FAIL"); + } + + void PrintSummary() const + { + std::lock_guard Lock(MetricsMutex); + std::uint64_t TotalGenerated = 0, TotalHits = 0, TotalRoundTrips = 0; + std::uint64_t TotalRaw = 0, TotalZlib = 0, TotalMesh = 0, TotalDisk = 0; + std::printf("[TileCacheCodecTable] lod tiles raw_avg_B zlib_avg_B mesh_q24_avg_B raw_encode_us raw_decode_us zlib_encode_us zlib_decode_us mesh_q24_encode_us mesh_q24_decode_us generation_us roundtrip_us cache_load_us geom_px_max lambert_max normal_deg_max winding_changes degenerate_changes\n"); + for (std::size_t Lod = 0; Lod < Stats.size(); ++Lod) + { + const auto& S = Stats[Lod]; + if (S.generated == 0 && S.hits == 0) continue; + TotalGenerated += S.generated; TotalHits += S.hits; TotalRoundTrips += S.roundTrips; + TotalRaw += S.rawBytes; TotalZlib += S.zlibBytes; TotalMesh += S.meshBytes; TotalDisk += S.diskBytes; + const auto Avg = [Count = S.generated](std::uint64_t Bytes) { return Count ? static_cast(Bytes) / Count : 0.0; }; + const auto Us = [](std::uint64_t Ns, std::uint64_t Count) { return Count ? static_cast(Ns) / Count / 1000.0 : 0.0; }; + std::printf("[TileCacheCodecTable] %zu %llu %.1f %.1f %.1f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.4f %.6f %.5f %llu %llu\n", + Lod, static_cast(S.generated + S.hits), Avg(S.rawBytes), Avg(S.zlibBytes), Avg(S.meshBytes), + Us(S.rawEncodeNs, S.generated), Us(S.rawDecodeNs, S.generated), + Us(S.zlibEncodeNs, S.generated), Us(S.zlibDecodeNs, S.generated), + Us(S.meshEncodeNs, S.generated), Us(S.meshDecodeNs, S.generated), + Us(S.generationNs, S.generated), Us(S.roundTripNs, S.generated), + Us(S.cacheLoadNs, S.hits), + S.maxProjectedVertexErrorPx, S.maxLambertDelta, S.maxNormalAngleDegrees, + static_cast(S.windingChanges), static_cast(S.degeneracyChanges)); + } + std::printf("[TileCacheSummary] generated=%llu hits=%llu exact_roundtrips=%llu raw_bytes=%llu zlib_bytes=%llu mesh_specific_bytes=%llu actual_disk_bytes=%llu disk_limit=%llu codec=zlib-6 mesh_candidate=q24_position+oct16_normal+delta_varint_index status=%s\n", + static_cast(TotalGenerated), static_cast(TotalHits), + static_cast(TotalRoundTrips), static_cast(TotalRaw), + static_cast(TotalZlib), static_cast(TotalMesh), + static_cast(TotalDisk), static_cast(MaximumDiskBytes), + TotalRoundTrips == TotalGenerated ? "PASS" : "CHECK"); + std::printf("[TileCacheIdentity] owner_snapshot=%016llx world_config=%016llx recipe=%016llx plan=%016llx manifest=%016llx seed=%d schema=%u evaluator=%u voxel_um=%u tile_voxels=%u mesher_contract=%u fp_contract=%u edit_sequence=%llu edit_digest=%016llx\n", + static_cast(CacheIdentity.ownerSnapshotHash), + static_cast(CacheIdentity.worldConfigurationHash), + static_cast(CacheIdentity.recipeHash), + static_cast(CacheIdentity.planHash), + static_cast(CacheIdentity.manifestHash), CacheIdentity.seed, + CacheIdentity.schemaVersion, CacheIdentity.evaluatorVersion, CacheIdentity.voxelSizeMicrons, + CacheIdentity.tileVoxelCount, CacheIdentity.mesherContractVersion, + CacheIdentity.floatingPointContractVersion, + static_cast(CacheIdentity.editSequence), + static_cast(CacheIdentity.editDigest)); + } + + private: + static std::string Hex(std::uint64_t Value) + { + char Buffer[17]{}; std::snprintf(Buffer, sizeof(Buffer), "%016llx", static_cast(Value)); + return Buffer; + } + + std::uint64_t ScanBytes() const + { + std::uint64_t Total = 0; std::error_code Error; + if (!std::filesystem::exists(RootPath, Error)) return 0; + for (std::filesystem::recursive_directory_iterator It(RootPath, + std::filesystem::directory_options::skip_permission_denied, Error), End; + It != End && !Error; It.increment(Error)) + { + if (!It->is_regular_file(Error) || Error) { Error.clear(); continue; } + Total += It->file_size(Error); Error.clear(); + } + return Total; + } + + static bool Compress(std::span Raw, std::vector& Compressed, std::string& Error) + { + if (Raw.size() > MaximumRecordBytes) { Error = "tile record exceeds cache codec limit"; return false; } + uLongf Capacity = compressBound(static_cast(Raw.size())); + Compressed.resize(static_cast(Capacity)); + const int Result = compress2(Compressed.data(), &Capacity, Raw.data(), + static_cast(Raw.size()), 6); + if (Result != Z_OK) { Error = "zlib compress2 failed: " + std::to_string(Result); return false; } + Compressed.resize(static_cast(Capacity)); + return true; + } + + bool MeasureCodecs(const VoxelForgeCore::TileMesh& Mesh, std::vector& Raw, + std::vector& Compressed, std::vector& MeshBytes, CodecSample& Sample, + MeshEncodingGate& Gate, std::string& Error) const + { + auto Start = Clock::now(); + if (!EncodeRawMesh(Mesh, Raw)) { Error = "canonical mesh serialization failed"; return false; } + Sample.rawEncodeNs = std::chrono::duration_cast(Clock::now() - Start).count(); + Sample.rawBytes = Raw.size(); + VoxelForgeCore::TileMesh RawDecoded; + Start = Clock::now(); + if (!DecodeRawMesh(Raw, RawDecoded) || !SameExactMesh(Mesh, RawDecoded)) + { Error = "uncompressed codec round trip differs"; return false; } + Sample.rawDecodeNs = std::chrono::duration_cast(Clock::now() - Start).count(); + + Start = Clock::now(); + if (!Compress(Raw, Compressed, Error)) return false; + Sample.zlibEncodeNs = std::chrono::duration_cast(Clock::now() - Start).count(); + Sample.zlibBytes = Compressed.size(); + std::vector ZlibRaw(Raw.size()); uLongf RawLength = static_cast(ZlibRaw.size()); + Start = Clock::now(); + const int ZResult = uncompress(ZlibRaw.data(), &RawLength, Compressed.data(), static_cast(Compressed.size())); + VoxelForgeCore::TileMesh ZlibDecoded; + const bool ZOkay = ZResult == Z_OK && RawLength == Raw.size() + && DecodeRawMesh(ZlibRaw, ZlibDecoded) && SameExactMesh(Mesh, ZlibDecoded); + Sample.zlibDecodeNs = std::chrono::duration_cast(Clock::now() - Start).count(); + if (!ZOkay) { Error = "zlib codec round trip differs"; return false; } + + Start = Clock::now(); + if (!EncodeMeshSpecific(Mesh, MeshBytes)) { Error = "mesh-specific serialization failed"; return false; } + Sample.meshEncodeNs = std::chrono::duration_cast(Clock::now() - Start).count(); + Sample.meshBytes = MeshBytes.size(); + VoxelForgeCore::TileMesh MeshDecoded; + Start = Clock::now(); + const double Distance = Mesh.key.lod >= 0 && Mesh.key.lod < 9 + ? ClosestDisplayDistances[static_cast(Mesh.key.lod)] : 0.0; + if (!DecodeMeshSpecific(MeshBytes, MeshDecoded, Distance, Gate)) + { Error = "mesh-specific codec decode failed"; return false; } + AuditMeshEncoding(Mesh, MeshDecoded, Distance, Gate); + Sample.meshDecodeNs = std::chrono::duration_cast(Clock::now() - Start).count(); + Sample.maximumProjectedVertexErrorPx = Gate.maxProjectedVertexErrorPx; + Sample.maximumLambertDelta = Gate.maxLambertDelta; + Sample.maximumNormalAngleDegrees = Gate.maxNormalAngleDegrees; + Sample.windingChanges = Gate.windingChanges; + Sample.degeneracyChanges = Gate.degeneracyChanges; + return true; + } + + bool StoreFile(const std::filesystem::path& Path, const VoxelForgeCore::TileKey& Key, + const Identity& IdentityValue, std::uint64_t OptionsHash, + std::span Raw, std::span Compressed, std::string& Error) + { + std::vector Bytes; + constexpr char Magic[8] = {'V','F','T','I','L','E','1','6'}; + Bytes.insert(Bytes.end(), Magic, Magic + 8); + AppendUnsigned(Bytes, FormatVersion, 4); AppendUnsigned(Bytes, 1, 4); // zlib codec id + AppendIdentity(Bytes, IdentityValue); + AppendUnsigned(Bytes, OptionsHash, 8); + AppendSigned(Bytes, Key.lod, 4); AppendSigned(Bytes, Key.coordinate.x, 4); + AppendSigned(Bytes, Key.coordinate.y, 4); AppendSigned(Bytes, Key.coordinate.z, 4); + AppendUnsigned(Bytes, Raw.size(), 8); AppendUnsigned(Bytes, Compressed.size(), 8); + AppendUnsigned(Bytes, crc32(0L, Raw.data(), static_cast(Raw.size())), 4); + Bytes.insert(Bytes.end(), Compressed.begin(), Compressed.end()); + const std::uint32_t RecordCrc = static_cast(crc32(0L, Bytes.data(), static_cast(Bytes.size()))); + AppendUnsigned(Bytes, RecordCrc, 4); + const std::uint64_t Padded = PaddedFileSize(Bytes.size()); + if (Padded > MaximumRecordBytes) { Error = "padded cache tile exceeds record limit"; return false; } + Bytes.resize(static_cast(Padded), 0); + + std::error_code FsError; + std::filesystem::create_directories(Path.parent_path(), FsError); + if (FsError) { Error = "cache directory: " + FsError.message(); return false; } + std::uint64_t PreviousSize = 0; + if (std::filesystem::exists(Path, FsError) && !FsError) + PreviousSize = std::filesystem::file_size(Path, FsError); + if (FsError) { Error = "cache stat: " + FsError.message(); return false; } + { + std::lock_guard Lock(BudgetMutex); + if (BytesOnDisk < PreviousSize) BytesOnDisk = 0; + const std::uint64_t Effective = BytesOnDisk - PreviousSize; + if (Padded > MaximumDiskBytes || Effective > MaximumDiskBytes - Padded) + { Error = "cache disk budget reached"; return false; } + BytesOnDisk = Effective + Padded; + } + + static std::atomic Serial{0}; + const auto Temp = Path.string() + ".tmp." + std::to_string(::getpid()) + + "." + std::to_string(Serial.fetch_add(1, std::memory_order_relaxed)); + { + std::ofstream File(Temp, std::ios::binary | std::ios::trunc); + if (!File) { Error = "cannot open cache temp file"; AdjustBudget(Padded, PreviousSize); return false; } + File.write(reinterpret_cast(Bytes.data()), static_cast(Bytes.size())); + File.flush(); + if (!File) { Error = "cache temp write failed"; File.close(); std::filesystem::remove(Temp, FsError); AdjustBudget(Padded, PreviousSize); return false; } + } + const int TempFd = ::open(Temp.c_str(), O_RDONLY); + if (TempFd < 0 || ::fsync(TempFd) != 0) + { + if (TempFd >= 0) ::close(TempFd); + Error = "cache temp fsync failed"; std::filesystem::remove(Temp, FsError); AdjustBudget(Padded, PreviousSize); return false; + } + ::close(TempFd); + if (::rename(Temp.c_str(), Path.c_str()) != 0) + { Error = "atomic cache rename failed"; std::filesystem::remove(Temp, FsError); AdjustBudget(Padded, PreviousSize); return false; } + const int DirFd = ::open(Path.parent_path().c_str(), O_RDONLY | O_DIRECTORY); + if (DirFd >= 0) { ::fsync(DirFd); ::close(DirFd); } + if (Path.parent_path().filename().string().starts_with("world_")) + { std::lock_guard Lock(FilesMutex); TileFiles.insert(Path); } + return true; + } + + void AdjustBudget(std::uint64_t Added, std::uint64_t Previous) + { + std::lock_guard Lock(BudgetMutex); + if (BytesOnDisk >= Added) BytesOnDisk -= Added; + BytesOnDisk += Previous; + } + + bool LoadFile(const std::filesystem::path& Path, const VoxelForgeCore::TileKey& ExpectedKey, + const Identity& ExpectedIdentity, std::uint64_t ExpectedOptionsHash, + VoxelForgeCore::TileMesh& Out, std::string& Error) const + { + std::ifstream File(Path, std::ios::binary | std::ios::ate); + if (!File) { Error = "missing"; return false; } + const auto End = File.tellg(); + if (End < 0 || static_cast(End) > MaximumRecordBytes + || static_cast(End) % IoAlignment != 0) + { Error = "invalid or truncated file size"; return false; } + std::vector Bytes(static_cast(End)); + File.seekg(0); File.read(reinterpret_cast(Bytes.data()), End); + if (!File) { Error = "short file read"; return false; } + std::size_t Offset = 0; + constexpr char Magic[8] = {'V','F','T','I','L','E','1','6'}; + if (Bytes.size() < 8 || std::memcmp(Bytes.data(), Magic, 8) != 0) + { Error = "bad tile magic"; return false; } + Offset = 8; + std::uint64_t Word = 0, Codec = 0, Version = 0; + if (!ReadUnsigned(Bytes, Offset, 4, Version) || !ReadUnsigned(Bytes, Offset, 4, Codec) + || Version != FormatVersion || Codec != 1) + { Error = "unsupported tile format"; return false; } + Identity Found; + if (!ReadIdentity(Bytes, Offset, Found)) { Error = "short identity header"; return false; } + if (!IdentityEqual(Found, ExpectedIdentity)) { Error = "identity mismatch"; return false; } + std::uint64_t OptionsHash = 0; + if (!ReadUnsigned(Bytes, Offset, 8, OptionsHash) || OptionsHash != ExpectedOptionsHash) + { Error = "generation options mismatch"; return false; } + std::int32_t KeyParts[4]{}; + for (int I = 0; I < 4; ++I) + { if (!ReadUnsigned(Bytes, Offset, 4, Word)) { Error = "short tile key"; return false; } KeyParts[I] = static_cast(Word); } + const VoxelForgeCore::TileKey FoundKey{{KeyParts[1], KeyParts[2], KeyParts[3]}, KeyParts[0]}; + if (!(FoundKey == ExpectedKey)) { Error = "tile key mismatch"; return false; } + std::uint64_t RawSize = 0, CompressedSize = 0, RawCrc = 0; + if (!ReadUnsigned(Bytes, Offset, 8, RawSize) || !ReadUnsigned(Bytes, Offset, 8, CompressedSize) + || !ReadUnsigned(Bytes, Offset, 4, RawCrc) || RawSize > MaximumRecordBytes + || CompressedSize > MaximumRecordBytes || Offset > Bytes.size() + || Bytes.size() - Offset < CompressedSize + 4) + { Error = "invalid or truncated payload lengths"; return false; } + const std::size_t PayloadOffset = Offset; + const std::size_t CrcOffset = PayloadOffset + static_cast(CompressedSize); + std::size_t CrcReadOffset = CrcOffset; + std::uint64_t StoredRecordCrc = 0; + if (!ReadUnsigned(Bytes, CrcReadOffset, 4, StoredRecordCrc)) { Error = "missing record checksum"; return false; } + const std::uint32_t ActualRecordCrc = static_cast(crc32(0L, Bytes.data(), static_cast(CrcOffset))); + if (ActualRecordCrc != StoredRecordCrc) { Error = "record checksum mismatch"; return false; } + for (std::size_t I = CrcReadOffset; I < Bytes.size(); ++I) + if (Bytes[I] != 0) { Error = "nonzero file padding"; return false; } + std::vector Raw(static_cast(RawSize)); + uLongf ActualRawSize = static_cast(Raw.size()); + const int Result = uncompress(Raw.data(), &ActualRawSize, + Bytes.data() + PayloadOffset, static_cast(CompressedSize)); + if (Result != Z_OK || ActualRawSize != RawSize) { Error = "zlib decode failed"; return false; } + if (crc32(0L, Raw.data(), static_cast(Raw.size())) != RawCrc) + { Error = "mesh payload checksum mismatch"; return false; } + if (!DecodeRawMesh(Raw, Out) || !(Out.key == ExpectedKey)) + { Error = "invalid mesh payload"; return false; } + if (Path.parent_path().filename().string().starts_with("world_")) + { std::lock_guard Lock(FilesMutex); TileFiles.insert(Path); } + Error.clear(); return true; + } + + void RecordGenerated(int Lod, const CodecSample& Sample, std::uint64_t GenerateNs, + std::uint64_t RoundTripNs, std::uint64_t DiskBytes, bool RoundTrip, + const MeshEncodingGate& Gate) + { + if (Lod < 0 || Lod >= static_cast(Stats.size())) return; + std::lock_guard Lock(MetricsMutex); + auto& S = Stats[static_cast(Lod)]; + ++S.generated; if (RoundTrip) ++S.roundTrips; + S.rawBytes += Sample.rawBytes; S.zlibBytes += Sample.zlibBytes; S.meshBytes += Sample.meshBytes; + S.rawEncodeNs += Sample.rawEncodeNs; S.rawDecodeNs += Sample.rawDecodeNs; + S.zlibEncodeNs += Sample.zlibEncodeNs; S.zlibDecodeNs += Sample.zlibDecodeNs; + S.meshEncodeNs += Sample.meshEncodeNs; S.meshDecodeNs += Sample.meshDecodeNs; + S.maxProjectedVertexErrorPx = std::max(S.maxProjectedVertexErrorPx, Sample.maximumProjectedVertexErrorPx); + S.maxLambertDelta = std::max(S.maxLambertDelta, Sample.maximumLambertDelta); + S.maxNormalAngleDegrees = std::max(S.maxNormalAngleDegrees, Sample.maximumNormalAngleDegrees); + S.windingChanges += Sample.windingChanges; S.degeneracyChanges += Sample.degeneracyChanges; + S.generationNs += GenerateNs; S.roundTripNs += RoundTripNs; + S.diskBytes += DiskBytes; + (void)Gate; + } + + void AddCacheHit(int Lod, std::uint64_t ReadNs) + { + if (Lod < 0 || Lod >= static_cast(Stats.size())) return; + std::lock_guard Lock(MetricsMutex); + ++Stats[static_cast(Lod)].hits; + Stats[static_cast(Lod)].cacheLoadNs += ReadNs; + CacheHitReadNs += ReadNs; + } + + void AddReject(int Lod) + { if (Lod >= 0 && Lod < static_cast(Stats.size())) { std::lock_guard Lock(MetricsMutex); ++Stats[static_cast(Lod)].rejects; } } + + void RememberSample(const VoxelForgeCore::TileMesh& Mesh, std::uint64_t OptionsHash) + { + std::lock_guard Lock(MetricsMutex); + if (!SampleMesh) { SampleMesh = Mesh; SampleOptionsHash = OptionsHash; } + } + + std::filesystem::path RootPath; + Identity CacheIdentity; + std::array ClosestDisplayDistances{}; + std::string StartupError; + mutable std::mutex MetricsMutex; + std::array Stats{}; + std::optional SampleMesh; + std::uint64_t SampleOptionsHash = 0; + std::uint64_t CacheHitReadNs = 0; + mutable std::mutex BudgetMutex; + std::uint64_t BytesOnDisk = 0; + mutable std::mutex FilesMutex; + mutable std::set TileFiles; + }; +} diff --git a/Standalone/Source/StreamBenchmarkHeadless.cpp b/Standalone/Source/StreamBenchmarkHeadless.cpp index 973591b..76c627c 100644 --- a/Standalone/Source/StreamBenchmarkHeadless.cpp +++ b/Standalone/Source/StreamBenchmarkHeadless.cpp @@ -35,6 +35,7 @@ namespace bool StreamBenchmark=false; bool StreamPipeline=false; bool StreamHash=false; + bool TileCachePrototype=false; bool ProfileColumnOverlap=false; bool UseSharedColumnCache=true; bool ForceGenerate=false; @@ -119,6 +120,7 @@ namespace " --seed=N --recipe=surface-world|floating-islands\n" " --auto-surface-camera (place the 27-tile cube on this seed's surface)\n" " --mesh-coverage (report non-empty tiles and indexed triangles)\n" + " --tile-cache-prototype (opt-in exact disk-cache round trip; writes Saved/Linux/cache/l16-prototype)\n" " --screen-space-gate (measure display error and historical negative controls)\n" " --audit-uniform-block-proofs (fill and check every interval-certified block)\n" " --profile-lod-work (report per-LOD density sample counts)\n" @@ -137,6 +139,15 @@ namespace else if(arg=="--stream-benchmark")options.StreamBenchmark=true; else if(arg=="--stream-pipeline")options.StreamPipeline=true; else if(arg=="--stream-hash")options.StreamHash=true; + else if(arg=="--tile-cache-prototype") + { + options.TileCachePrototype=true; + options.StreamPipeline=true; + options.StreamBenchmark=true; + options.WorldAssemblerPrototype=true; + options.ForceGenerate=true; + options.StreamHash=true; + } else if(arg=="--profile-column-overlap")options.ProfileColumnOverlap=true; else if(arg=="--force-generate")options.ForceGenerate=true; else if(arg=="--mesh-coverage")options.MeshCoverage=true; @@ -377,5 +388,9 @@ int main(int argc,char** argv) cameraStepVoxels,options.AuditUniformBlockProofs,options.ProfileLodWork, options.RegionCrossing,options.AuditTileShapeReadiness, options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence, - options.AuditRouteParity); + options.AuditRouteParity, + options.TileCachePrototype + ?std::filesystem::current_path()/"Saved/Linux/cache/l16-prototype" + :std::filesystem::path{}, + options.DataPath); } diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index 4d93c4f..7f47be8 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -3,6 +3,7 @@ #include "OwnerWorldLoader.h" #include "StreamingWorld.h" #include "LinuxScreenSpaceGate.h" +#include "../Research/WorldAssemblerM3/Linux16/TileCachePrototype.h" #include #include @@ -253,7 +254,9 @@ namespace StandaloneStreamBenchmark bool bAuditUniformBlockProofs=false,bool bReportLodWorkProfile=false, bool bRegionCrossing=false, bool bAuditTileShapeReadiness=false,int DiagnosticSlowShapeCompileMs=0, - bool bAuditBasePageEquivalence=false,bool bAuditRouteParity=false) + bool bAuditBasePageEquivalence=false,bool bAuditRouteParity=false, + const std::filesystem::path& TileCachePrototypeRoot={}, + const std::filesystem::path& OwnerSnapshotPath={}) { using Clock=std::chrono::steady_clock; FStandaloneOwnerWorld& World=*OwnerWorld; @@ -274,6 +277,42 @@ namespace StandaloneStreamBenchmark const bool bRoundTunnels=World.Generator->bStandaloneResearchRoundTunnels; const bool bEnhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls; VoxelForgeCore::World Shape(CoreSnapshot); + std::unique_ptr TileCache; + FStandaloneCoreTileRoute CoreTileRoute; + VoxelForgeCore::TileGenerationOptions CacheOptions; + if(!TileCachePrototypeRoot.empty()) + { + const auto RepoRoot=std::filesystem::current_path().lexically_normal(); + const auto AllowedRoot=(RepoRoot/"Saved/Linux/cache").lexically_normal(); + const auto RequestedRoot=std::filesystem::absolute(TileCachePrototypeRoot).lexically_normal(); + const std::string AllowedString=AllowedRoot.string()+std::string(1,std::filesystem::path::preferred_separator); + if(RequestedRoot.string().rfind(AllowedString,0)!=0||OwnerSnapshotPath.empty()) + { + std::fprintf(stderr,"[TileCachePrototype] path rejected: cache writes must stay under Saved/Linux/cache and owner snapshot identity must be provided.\n"); + return 2; + } + if(ClosestDisplayDistances[0]==0.0) + ClosestDisplayDistances=StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters( + World,CameraPositionVoxels,World.CoreVoxelSizeMicrons); + const auto Identity=StandaloneTileCachePrototype::MakeIdentity( + World,CoreSnapshot,OwnerSnapshotPath); + if(Identity.ownerSnapshotHash==0||!Assembly) + { + std::fprintf(stderr,"[TileCachePrototype] missing owner snapshot or world assembly identity.\n"); + return 2; + } + TileCache=std::make_unique( + RequestedRoot,Identity,ClosestDisplayDistances); + CacheOptions.hashDensity=true; + StandaloneTileCachePrototype::TileDiskCache* Cache=TileCache.get(); + CoreTileRoute=[Cache](const VoxelForgeCore::World& TileWorld, + const VoxelForgeCore::TileKey& Key, + const VoxelForgeCore::TileGenerationOptions& Options,bool& bHit, + std::string& Error) + {return Cache->GenerateOrLoad(TileWorld,Key,Options,bHit,Error);}; + std::printf("[TileCachePrototype] enabled root=%s owner_snapshot=%s mode=worker_generate_store_read_or_warm_hit codec=zlib-6\n", + RequestedRoot.string().c_str(),OwnerSnapshotPath.string().c_str()); + } FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraPositionVoxels); const auto CoreTiles=Shape.desiredTiles( {CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z}); @@ -411,7 +450,7 @@ namespace StandaloneStreamBenchmark ShapeRequest.enhancedTunnelWalls=bEnhancedTunnelWalls; FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel, bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap, - FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache); + FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache,std::move(CoreTileRoute)); Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs); if(bScreenSpaceGate||bReportLodWorkProfile)Streamer.SetMesherStageProfilingEnabled(true); const auto StreamStart=Clock::now(); @@ -657,6 +696,7 @@ namespace StandaloneStreamBenchmark VoxelForgeCore::TileGenerationOptions PageOptions=CaptureOptions; PageOptions.auditUniformBlockProofs=true; + int32 CachePairs=0,CacheMismatches=0,EditCachePairs=0,EditCacheMismatches=0; int32 BasePageMismatches=0,PageRoutesVerified=0; std::uint64_t PageProofBlocks=0,PageProofSamples=0,PageProofFallbacks=0, PageProofUnknown=0,PageProofViolations=0; @@ -716,6 +756,28 @@ namespace StandaloneStreamBenchmark PageProofUnknown+=Page->diagnostics.uniformBlockProofUnknownBlocks; PageProofViolations+=Page->diagnostics.uniformBlockProofViolations; } + if(TileCache) + { + VoxelForgeCore::TileMesh Cached; + std::string CacheError; + bool bCacheReady=TileCache->LoadExpected(Row.Candidate.Key,CacheOptions,Cached,CacheError); + if(!bCacheReady&&Row.Base) + bCacheReady=TileCache->StoreAndLoadExplicit(*Row.Base,CacheOptions, + TileCache->identity(),Cached,CacheError); + const bool bCacheSame=bCacheReady&&Row.Base + &&SameTileOutput(*Row.Base,Cached) + &&StandaloneTileCachePrototype::SameExactMesh(*Row.Base,Cached); + ++CachePairs; + if(!bCacheSame)++CacheMismatches; + std::printf("[RouteParityPair] route=base/disk-cache key=(%d,%d,%d,lod%d) same_density=%d same_mesh=%d same_payload=%d same_topology=%d exact_arrays=%d status=%s error=%s\n", + Row.Candidate.Key.coordinate.x,Row.Candidate.Key.coordinate.y, + Row.Candidate.Key.coordinate.z,Row.Candidate.Key.lod, + Row.Base&&Cached.densityHash==Row.Base->densityHash?1:0, + Row.Base&&Cached.meshHash==Row.Base->meshHash?1:0, + Row.Base&&Cached.payloadHash==Row.Base->payloadHash?1:0, + Row.Base&&Cached.topologyHash==Row.Base->topologyHash?1:0, + bCacheSame?1:0,bCacheSame?"PASS":"FAIL",CacheError.c_str()); + } std::printf("[RouteParityPair] route=base/page key=(%d,%d,%d,lod%d) page_requested=%d page_verified=%d same_density=%d same_mesh=%d same_payload=%d same_topology=%d samples=%llu max_abs_density_delta=%.9g sign_mismatches=%llu edge_crossing_mismatches=%llu shared_surface_edges=%llu max_surface_delta_vox=%.9g exact_query_mismatches=%llu base_native_float=%llu page_native_float=%llu first_density=(%.9g,%.9g) status=%s error=%s\n", Row.Candidate.Key.coordinate.x,Row.Candidate.Key.coordinate.y, Row.Candidate.Key.coordinate.z,Row.Candidate.Key.lod, @@ -899,6 +961,27 @@ namespace StandaloneStreamBenchmark &&EditedPage->diagnostics.analyticRegionPageRoute; const bool bEditSame=EditedBase&&EditedPage&&bEditPageVerified &&SameTileOutput(*EditedBase,*EditedPage); + bool bEditCacheSame=false; + if(TileCache&&EditedBase&&Applied) + { + VoxelForgeCore::TileMesh CachedEdited; + std::string CacheError; + const auto EditedIdentity=TileCache->WithEdit(Transaction.sequence,Applied->digest); + bEditCacheSame=TileCache->StoreAndLoadExplicit(*EditedBase,CacheOptions, + EditedIdentity,CachedEdited,CacheError) + &&SameTileOutput(*EditedBase,CachedEdited); + ++EditCachePairs; + if(!bEditCacheSame)++EditCacheMismatches; + std::printf("[RouteParityPair] route=edited-base/disk-cache key=(%d,%d,%d,lod%d) edit_sequence=%llu edit_digest=0x%016llx same_density=%d same_mesh=%d same_payload=%d same_topology=%d exact_arrays=%d status=%s error=%s\n", + Key.coordinate.x,Key.coordinate.y,Key.coordinate.z,Key.lod, + static_cast(Transaction.sequence), + static_cast(Applied->digest), + CachedEdited.densityHash==EditedBase->densityHash?1:0, + CachedEdited.meshHash==EditedBase->meshHash?1:0, + CachedEdited.payloadHash==EditedBase->payloadHash?1:0, + CachedEdited.topologyHash==EditedBase->topologyHash?1:0, + bEditCacheSame?1:0,bEditCacheSame?"PASS":"FAIL",CacheError.c_str()); + } const bool bEditPass=Before&&Applied&&EditedBase&&bEditPageBuilt &&bEditPageVerified&&bEditChanged&&bEditSame&&EditComparison.valid &&EditComparison.exactQueryMismatches==0; @@ -996,11 +1079,13 @@ namespace StandaloneStreamBenchmark &&HistoryPairs==(bHasCameraStep&&bRegionCrossing?1:0)&&HistoryMismatches==0 &&PageRoutesVerified==static_cast(Rows.size()) &&NativeRoutesVerified==NativePairs&&PageProofViolations==0 - &&PageExactMismatches==0&&NativeExactMismatches==0; - std::printf("[RouteParityAudit] seed=%d page_pairs=%zu page_verified=%d page_mismatches=%d native_only_pairs=%d native_verified=%d native_mismatches=%d higher_lod_exact_pairs=%d higher_lod_exact_mismatches=%d edit_pairs=%d edit_mismatches=%d history_pairs=%d history_mismatches=%d exact_query_samples=%llu exact_query_mismatches=%llu page_density_samples=%llu max_abs_density_delta=%.9g mean_abs_density_delta=%.9g sign_mismatches=%llu edge_crossing_mismatches=%llu shared_surface_edges=%llu max_surface_delta_vox=%.9g mean_surface_delta_vox=%.9g page_proof_blocks=%llu page_proof_samples=%llu page_proof_fallbacks=%llu page_proof_unknown=%llu page_proof_violations=%llu status=%s\n", + &&PageExactMismatches==0&&NativeExactMismatches==0 + &&(!TileCache||(CachePairs>0&&CacheMismatches==0&&EditCachePairs==1&&EditCacheMismatches==0)); + std::printf("[RouteParityAudit] seed=%d page_pairs=%zu page_verified=%d page_mismatches=%d native_only_pairs=%d native_verified=%d native_mismatches=%d higher_lod_exact_pairs=%d higher_lod_exact_mismatches=%d edit_pairs=%d edit_mismatches=%d history_pairs=%d history_mismatches=%d cache_pairs=%d cache_mismatches=%d edit_cache_pairs=%d edit_cache_mismatches=%d exact_query_samples=%llu exact_query_mismatches=%llu page_density_samples=%llu max_abs_density_delta=%.9g mean_abs_density_delta=%.9g sign_mismatches=%llu edge_crossing_mismatches=%llu shared_surface_edges=%llu max_surface_delta_vox=%.9g mean_surface_delta_vox=%.9g page_proof_blocks=%llu page_proof_samples=%llu page_proof_fallbacks=%llu page_proof_unknown=%llu page_proof_violations=%llu status=%s\n", World.Seed,Rows.size(),PageRoutesVerified,BasePageMismatches, NativePairs,NativeRoutesVerified,NativeMismatches,ExactQueryPairs, ExactQueryMismatches,EditPairs,EditMismatches,HistoryPairs,HistoryMismatches, + CachePairs,CacheMismatches,EditCachePairs,EditCacheMismatches, static_cast(PageExactSamples +NativeExactSamples+ExactQuerySamples), static_cast(PageExactMismatches @@ -1640,10 +1725,12 @@ namespace StandaloneStreamBenchmark const double WholeViewSeconds=std::chrono::duration(WholeViewReady-ColdStart).count(); const double StreamSeconds=std::chrono::duration(WholeViewReady-StreamStart).count(); const double FirstTileSeconds=std::chrono::duration(FirstTileReady-ColdStart).count(); - std::printf("[StreamPipeline] workers=%d shape=%.3fs first_tile_ready=%.3fs neighborhood_mesh=%.3fs neighborhood_ready=%.3fs full_detail_ready=%.3fs whole_view_ready=%.3fs neighborhood_tiles=%d/27 full_detail_tiles=%d/%d whole_tiles=%d/%zu stream_wall=%.3fs cache_hits=0 full_payload=1\n", + const std::uint64_t TileCacheHits=TileCache?TileCache->hitCount():Streamer.GetCacheHits(); + std::printf("[StreamPipeline] workers=%d shape=%.3fs first_tile_ready=%.3fs neighborhood_mesh=%.3fs neighborhood_ready=%.3fs full_detail_ready=%.3fs whole_view_ready=%.3fs neighborhood_tiles=%d/27 full_detail_tiles=%d/%d whole_tiles=%d/%zu stream_wall=%.3fs cache_hits=%llu full_payload=1\n", WorkerCount,ShapeSeconds,FirstTileSeconds,NeighborhoodMeshSeconds,NeighborhoodSeconds, FullDetailSeconds,WholeViewSeconds,NeighborhoodFinished, - FullDetailFinished,FullDetailExpected,Finished,Desired.Tiles.size(),StreamSeconds); + FullDetailFinished,FullDetailExpected,Finished,Desired.Tiles.size(),StreamSeconds, + static_cast(TileCacheHits)); if(bReportMeshCoverage) std::printf("[PipelineMeshCoverage] tiles=%d nonempty_mesh_tiles=%llu indexed_triangles=%llu\n", Finished,static_cast(WholeNonEmptyMeshTiles), @@ -1739,10 +1826,28 @@ namespace StandaloneStreamBenchmark static_cast(ExactDetailHash), static_cast(ExactCollisionHash)); } + bool bTileCacheSafetyPassing=true; + if(TileCache) + { + std::string CacheSafetyError; + bTileCacheSafetyPassing=TileCache->RunSafetyControls(CacheOptions,CacheSafetyError); + if(!bTileCacheSafetyPassing) + std::fprintf(stderr,"[TileCacheSafety] failed: %s\n",CacheSafetyError.c_str()); + TileCache->PrintSummary(); + TileCache->MeasureDiskReads(); + const std::uint64_t CacheBytes=TileCache->bytesOnDisk(); + std::printf("[TileCacheBudget] bytes=%llu maximum=%llu status=%s\n", + static_cast(CacheBytes), + static_cast(StandaloneTileCachePrototype::MaximumDiskBytes), + CacheBytes<=StandaloneTileCachePrototype::MaximumDiskBytes?"PASS":"FAIL"); + bTileCacheSafetyPassing=bTileCacheSafetyPassing + &&CacheBytes<=StandaloneTileCachePrototype::MaximumDiskBytes; + } return NeighborhoodFinished==static_cast(Neighborhood.size()) &&FullDetailFinished==FullDetailExpected&&Finished==Update.DesiredTiles&&bHashesPassing &&(!bProfileColumnOverlap||bColumnOverlapCapturePassing) &&(!bExportRequested||bSceneExportPassing)&&bScreenSpaceGatePassing - &&(!bAuditUniformBlockProofs||BlockProofViolations==0)?0:6; + &&(!bAuditUniformBlockProofs||BlockProofViolations==0) + &&bTileCacheSafetyPassing?0:6; } } // namespace StandaloneStreamBenchmark diff --git a/Standalone/Source/StreamingWorld.h b/Standalone/Source/StreamingWorld.h index b27dd7e..3702d59 100644 --- a/Standalone/Source/StreamingWorld.h +++ b/Standalone/Source/StreamingWorld.h @@ -67,6 +67,10 @@ struct FStandaloneCpuTile }; +using FStandaloneCoreTileRoute = std::function( + const VoxelForgeCore::World&,const VoxelForgeCore::TileKey&, + const VoxelForgeCore::TileGenerationOptions&,bool&,std::string&)>; + inline uint64 HashStandaloneMeshPayload(const FVoxelMeshData& Mesh) { uint64 Hash=14695981039346656037ull; @@ -430,14 +434,16 @@ public: VoxelForgeCore::World* InCoreWorld=nullptr, bool bInCaptureColumnCoordinates=false, int32 InLod8HorizonInteriorStepVoxels=0, - bool bInSharedColumnCache=true) + bool bInSharedColumnCache=true, + FStandaloneCoreTileRoute InCoreTileRoute={}) :World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,16)),bHashDensity(bInHashDensity), MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter), CoreWorld(InCoreWorld),bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups), bSkipUV(bInSkipUV),bSkipColors(bInSkipColors), bCaptureColumnCoordinates(bInCaptureColumnCoordinates), bSharedColumnCache(bInSharedColumnCache), - Lod8HorizonInteriorStepVoxels(std::max(0,InLod8HorizonInteriorStepVoxels)) + Lod8HorizonInteriorStepVoxels(std::max(0,InLod8HorizonInteriorStepVoxels)), + CoreTileRoute(std::move(InCoreTileRoute)) { VoxelGenLOD::InitializeConsoleSwitches(); UVoxelMarchingCubesMesher::InitializeConsoleSwitches(); @@ -830,8 +836,11 @@ private: Options.diagnosticColumnInterpolationStride= std::max(1,DiagnosticCellStride); std::string Error; - auto Result=CoreWorld->generateTile( - {{Job.Key.Coord.X,Job.Key.Coord.Y,Job.Key.Coord.Z},Job.Key.Level},Options,Error); + const VoxelForgeCore::TileKey CoreKey{{Job.Key.Coord.X,Job.Key.Coord.Y,Job.Key.Coord.Z},Job.Key.Level}; + bool bDiskCacheHit=false; + auto Result=CoreTileRoute + ?CoreTileRoute(*CoreWorld,CoreKey,Options,bDiskCacheHit,Error) + :CoreWorld->generateTile(CoreKey,Options,Error); if(!Result) { std::fprintf(stderr,"[VoxelForgeCore] tile generation failed: %s\n",Error.c_str()); @@ -1003,6 +1012,7 @@ private: bool bCaptureColumnCoordinates=false; bool bSharedColumnCache=true; int32 Lod8HorizonInteriorStepVoxels=0; + FStandaloneCoreTileRoute CoreTileRoute; bool bProfileMesherStages=false; bool bAuditUniformBlockProofs=false; std::atomic CacheHitCount{0};