diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 2ad491c..b6caf5f 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -137,6 +137,7 @@ namespace VoxelForgeCore double marchingCubesGradientNormalsMilliseconds = 0.0; double marchingCubesEdgeInterpolationMilliseconds = 0.0; double marchingCubesVertexDedupStreamMilliseconds = 0.0; + double marchingCubesSkirtMilliseconds = 0.0; double marchingCubesOtherMilliseconds = 0.0; std::uint64_t marchingCubesGradientFallbackDensityCalls = 0; std::uint64_t marchingCubesEdgeMidpointDensityCalls = 0; @@ -146,11 +147,6 @@ namespace VoxelForgeCore double meshAssemblyMilliseconds = 0.0; double meshingMilliseconds = 0.0; double featureListMilliseconds = 0.0; - std::array visualDetailOctaveEvaluations{}; - std::array visualDetailOctaveBudgetEligible{}; - std::array visualDetailOctaveSkips{}; - std::array visualDetailOctaveMilliseconds{}; - std::array visualDetailOctaveEligibleMilliseconds{}; double farShellMilliseconds = 0.0; double farStratumShellMilliseconds = 0.0; double farConnectorShellMilliseconds = 0.0; @@ -585,8 +581,6 @@ namespace VoxelForgeCore struct FieldInterval; struct FieldDetailEnvelope; struct FieldBlockContract; - struct VisualDetailRecipeContractV1; - struct VisualDetailCutoffV1; class VFCORE_API FloatingIslandFieldV1 { @@ -609,7 +603,6 @@ namespace VoxelForgeCore std::int64_t maximumZSubvoxels) const noexcept; FloatingIslandInfluenceBounds influenceBounds() const noexcept; FieldDetailEnvelope detailEnvelope() const noexcept; - VisualDetailRecipeContractV1 visualDetailContract() const; FieldBlockContract contract() const noexcept; const FloatingIslandRecipeV1* recipe() const noexcept { return recipe_; } const FloatingIslandPlanV1* plan() const noexcept { return plan_; } @@ -659,12 +652,6 @@ namespace VoxelForgeCore std::uint64_t columnMisses = 0; std::uint64_t columnConflictMisses = 0; double columnComputeMilliseconds = 0.0; - std::array visualDetailOctaveEvaluations{}; - std::array visualDetailOctaveBudgetEligible{}; - std::array visualDetailOctaveSkips{}; - std::array visualDetailOctaveMilliseconds{}; - std::array visualDetailOctaveEligibleMilliseconds{}; - bool profileVisualDetailOctaves = false; bool captureCoordinates = false; bool coordinateCaptureComplete = true; std::vector> evaluatedCoordinates; @@ -684,44 +671,6 @@ namespace VoxelForgeCore std::int64_t minimumFeatureSizeSubvoxels = 1; }; - // A recipe may opt its display-only noise terms into this shared octave contract. - // A missing declaration keeps the exact evaluator. The per-call amplitude includes - // every downstream scale, so wavelength alone can never authorize dropping a term. - struct VisualDetailOctaveTermV1 - { - std::uint64_t stableId = 0; - std::int64_t baseWavelengthSubvoxels = 0; - std::int64_t maximumDisplacementSubvoxels = 0; - std::uint32_t octaveCount = 0; - std::uint32_t maximumNoiseAbsQ16 = 2u * 65536u; - }; - - inline constexpr std::uint32_t CurrentVisualDetailCutoffContractVersion = 2; - - struct VisualDetailRecipeContractV1 - { - std::uint32_t version = CurrentVisualDetailCutoffContractVersion; - std::vector terms; - }; - - struct VisualDetailCutoffV1 - { - std::int64_t pixelBudgetSubvoxels = 0; - std::int64_t sampleStepSubvoxels = 0; - std::uint32_t callBudgetDivisor = 64; - bool apply = false; - }; - - struct VisualDetailCutoffResultV1 - { - std::uint32_t firstSkippedOctave = 0; - std::int64_t maximumErrorSubvoxels = 0; - }; - - VFCORE_API VisualDetailCutoffResultV1 FindVisualDetailTailCutoffV1( - const VisualDetailOctaveTermV1& term, - const VisualDetailCutoffV1& cutoff) noexcept; - struct FieldBlockContract { std::uint32_t readsChannels = 0; @@ -756,6 +705,10 @@ namespace VoxelForgeCore std::int64_t maximumZSubvoxels, std::int32_t stepVoxels) const noexcept; static void resetThreadColumnCacheStats(bool captureCoordinates = false, bool clearCache = false, std::uint32_t octaveDrop = 0) noexcept; + static void setThreadColumnOctaveDrop(std::uint32_t octaveDrop) noexcept; + static std::uint32_t threadColumnOctaveDrop() noexcept; + static void setThreadColumnInterpolationStrideSubvoxels(std::int64_t stride) noexcept; + static std::int64_t threadColumnInterpolationStrideSubvoxels() noexcept; static void setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept; static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept; FieldInterval interval(std::int64_t minimumXSubvoxels, @@ -766,15 +719,6 @@ namespace VoxelForgeCore std::int64_t maximumZSubvoxels) const noexcept; FieldInfluenceBounds influenceBounds() const noexcept; FieldDetailEnvelope detailEnvelope() const noexcept; - VisualDetailRecipeContractV1 visualDetailContract() const; - static void setThreadVisualDetailCutoff(VisualDetailCutoffV1 cutoff) noexcept; - static VisualDetailCutoffV1 threadVisualDetailCutoff() noexcept; - static void setThreadColumnOctaveDrop(std::uint32_t octaveDrop) noexcept; - static std::uint32_t threadColumnOctaveDrop() noexcept; - static void setThreadVisualDetailProfileEnabled(bool enabled) noexcept; - static bool threadVisualDetailProfileEnabled() noexcept; - static void setThreadColumnInterpolationStrideSubvoxels(std::int64_t stride) noexcept; - static std::int64_t threadColumnInterpolationStrideSubvoxels() noexcept; FieldBlockContract contract() const noexcept; const WorldAssemblyRecipeV1* recipe() const noexcept { return recipe_; } const WorldAssemblyStratumPlanV1* plan() const noexcept { return plan_; } @@ -1111,15 +1055,7 @@ namespace VoxelForgeCore // Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version; // densite et hashes LOD0-3 restent inchanges. std::uint32_t farHorizonVersion = 3; - // Display mesh only. Zero preserves the exact field. Collision, queries and edits - // never set this option. - std::int64_t visualDetailPixelBudgetSubvoxels = 0; - std::int32_t visualDetailSampleStepVoxels = 0; - std::uint32_t visualDetailCallBudgetDivisor = 64; - bool profileVisualDetailOctaves = false; - bool visualDetailCutoffEnabled = false; - std::uint32_t visualDetailContractVersion = 0; - // Diagnostic controls used only by the Linux gate's historical negative controls. + // Linux research controls for reproducing L5 negative controls. Defaults preserve output. std::uint32_t diagnosticGlobalOctaveDrop = 0; std::uint32_t diagnosticCellStride = 1; std::uint32_t diagnosticColumnInterpolationStride = 1; diff --git a/Standalone/Core/src/FloatingIslandsInternal.h b/Standalone/Core/src/FloatingIslandsInternal.h index 0003056..3e54f35 100644 --- a/Standalone/Core/src/FloatingIslandsInternal.h +++ b/Standalone/Core/src/FloatingIslandsInternal.h @@ -1905,13 +1905,6 @@ FieldDetailEnvelope FloatingIslandFieldV1::detailEnvelope() const noexcept return result; } -VisualDetailRecipeContractV1 FloatingIslandFieldV1::visualDetailContract() const -{ - // Current island geometry uses shape and connector terms without an octave declaration. - // Returning an empty shared contract deliberately selects exact sampling. - return {}; -} - FieldBlockContract FloatingIslandFieldV1::contract() const noexcept { FieldBlockContract result; diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index 0c174a3..58763cf 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -220,13 +220,6 @@ namespace VoxelForgeCore return Hash; } - std::uint64_t SaltVisualDetailHash(std::uint64_t Hash,std::uint32_t Version) - { - AddWord(Hash,0x5649534445544149ull,8); - AddWord(Hash,Version,4); - return Hash; - } - FStandaloneShapeWorld::EDetailVariant ToNative(DetailVariant value) { switch(value) @@ -1337,25 +1330,6 @@ namespace VoxelForgeCore std::optional World::generateTile(const TileKey& key,const TileGenerationOptions& options, std::string& error) const { - struct FScopedHeightfieldVisualDetailState - { - VisualDetailCutoffV1 Cutoff=Heightfield2D::threadVisualDetailCutoff(); - std::uint32_t OctaveDrop=Heightfield2D::threadColumnOctaveDrop(); - bool bProfile=Heightfield2D::threadVisualDetailProfileEnabled(); - std::int64_t InterpolationStride= - Heightfield2D::threadColumnInterpolationStrideSubvoxels(); - ~FScopedHeightfieldVisualDetailState() - { - Heightfield2D::setThreadVisualDetailCutoff(Cutoff); - Heightfield2D::setThreadColumnOctaveDrop(OctaveDrop); - Heightfield2D::setThreadVisualDetailProfileEnabled(bProfile); - Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(InterpolationStride); - } - } VisualDetailState; - Heightfield2D::setThreadVisualDetailProfileEnabled(options.profileVisualDetailOctaves); - if(options.visualDetailCutoffEnabled - &&options.visualDetailContractVersion!=CurrentVisualDetailCutoffContractVersion) - {error="visual-detail recipe contract version is unsupported";return std::nullopt;} // Fine owner tiles had zero direct-map conflict evictions in the cold audit. Keep their // single-probe path and use associativity for the coarse coordinate families that collide. // Les tuiles proches n'avaient aucun conflit; garder leur acces simple et associer les LOD grossiers. @@ -1712,19 +1686,20 @@ namespace VoxelForgeCore out.diagnostics.featureListMilliseconds=std::chrono::duration( std::chrono::steady_clock::now()-FeatureListStart).count(); const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries); - VisualDetailCutoffV1 DisplayCutoff; - DisplayCutoff.pixelBudgetSubvoxels=options.visualDetailPixelBudgetSubvoxels; - DisplayCutoff.sampleStepSubvoxels=DisplayCutoff.pixelBudgetSubvoxels>0 - ?static_cast(MeshStep)*WorldAssemblySubvoxelsPerVoxel:0; - DisplayCutoff.callBudgetDivisor=std::max(1, - options.visualDetailCallBudgetDivisor); - DisplayCutoff.apply=options.visualDetailCutoffEnabled; - Heightfield2D::setThreadVisualDetailCutoff(DisplayCutoff); - Heightfield2D::setThreadColumnOctaveDrop(options.diagnosticGlobalOctaveDrop); - Heightfield2D::setThreadColumnInterpolationStrideSubvoxels( - options.diagnosticColumnInterpolationStride>1&&level>=3 - ?static_cast(MeshStep)*WorldAssemblySubvoxelsPerVoxel - *options.diagnosticColumnInterpolationStride:0); + const bool bDiagnosticColumnSampling=options.diagnosticGlobalOctaveDrop!=0 + ||options.diagnosticColumnInterpolationStride>1; + const std::uint32_t PreviousOctaveDrop=bDiagnosticColumnSampling + ?Heightfield2D::threadColumnOctaveDrop():0; + const std::int64_t PreviousInterpolationStride=bDiagnosticColumnSampling + ?Heightfield2D::threadColumnInterpolationStrideSubvoxels():0; + if(bDiagnosticColumnSampling) + { + Heightfield2D::setThreadColumnOctaveDrop(options.diagnosticGlobalOctaveDrop); + Heightfield2D::setThreadColumnInterpolationStrideSubvoxels( + options.diagnosticColumnInterpolationStride>1&&level>=3 + ?static_cast(MeshStep)*WorldAssemblySubvoxelsPerVoxel + *options.diagnosticColumnInterpolationStride:0); + } const double ColumnMillisecondsBeforeMeshing= VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; const auto MarchingCubesStart=std::chrono::steady_clock::now(); @@ -1732,6 +1707,11 @@ namespace VoxelForgeCore MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr, (options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr, options.profileMesherStages?&McStageTimings:nullptr); + if(bDiagnosticColumnSampling) + { + Heightfield2D::setThreadColumnOctaveDrop(PreviousOctaveDrop); + Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(PreviousInterpolationStride); + } const double MarchingCubesWallMilliseconds=std::chrono::duration( std::chrono::steady_clock::now()-MarchingCubesStart).count(); const double ColumnMillisecondsAfterMeshing= @@ -1742,6 +1722,10 @@ namespace VoxelForgeCore ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing); if(options.profileMesherStages) { + out.diagnostics.marchingCubesSkirtMilliseconds=McStageTimings.SkirtMilliseconds; + out.diagnostics.marchingCubesExclusiveMilliseconds=std::max(0.0, + out.diagnostics.marchingCubesExclusiveMilliseconds + -out.diagnostics.marchingCubesSkirtMilliseconds); out.diagnostics.marchingCubesDensityGridMilliseconds=McStageTimings.DensityGridMilliseconds; out.diagnostics.marchingCubesCellClassificationMilliseconds= McStageTimings.CellClassificationMilliseconds; @@ -1845,12 +1829,6 @@ namespace VoxelForgeCore out.diagnostics.columnCacheHits=Columns.columnHits; out.diagnostics.columnCacheMisses=Columns.columnMisses; out.diagnostics.columnCacheConflictMisses=Columns.columnConflictMisses; - out.diagnostics.visualDetailOctaveEvaluations=Columns.visualDetailOctaveEvaluations; - out.diagnostics.visualDetailOctaveBudgetEligible=Columns.visualDetailOctaveBudgetEligible; - out.diagnostics.visualDetailOctaveSkips=Columns.visualDetailOctaveSkips; - out.diagnostics.visualDetailOctaveMilliseconds=Columns.visualDetailOctaveMilliseconds; - out.diagnostics.visualDetailOctaveEligibleMilliseconds= - Columns.visualDetailOctaveEligibleMilliseconds; out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds; out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds; out.diagnostics.classificationMilliseconds=Counts.Milliseconds; @@ -1920,21 +1898,6 @@ namespace VoxelForgeCore out.payloadHash=SaltFarHorizonHash(out.payloadHash,options.farHorizonVersion); out.topologyHash=SaltFarHorizonHash(out.topologyHash,options.farHorizonVersion); } - if(key.lod>=3&&options.visualDetailCutoffEnabled - &&options.visualDetailPixelBudgetSubvoxels>0) - { - const auto& DetailStats=Heightfield2D::threadColumnCacheStats(); - const bool bVisualDetailChanged=std::any_of( - DetailStats.visualDetailOctaveSkips.begin(), - DetailStats.visualDetailOctaveSkips.end(), - [](std::uint64_t Skips){return Skips!=0;}); - if(bVisualDetailChanged) - { - out.meshHash=SaltVisualDetailHash(out.meshHash,options.visualDetailContractVersion); - out.payloadHash=SaltVisualDetailHash(out.payloadHash,options.visualDetailContractVersion); - out.topologyHash=SaltVisualDetailHash(out.topologyHash,options.visualDetailContractVersion); - } - } out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true; } out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration( diff --git a/Standalone/Core/src/WorldAssemblerInternal.h b/Standalone/Core/src/WorldAssemblerInternal.h index a49924b..5ce67d4 100644 --- a/Standalone/Core/src/WorldAssemblerInternal.h +++ b/Standalone/Core/src/WorldAssemblerInternal.h @@ -1031,91 +1031,19 @@ namespace world_assembly_detail // Audit-only override; normal tile generation resets this to zero before evaluating a recipe. // Surcharge reservee aux mesures; la generation remet zero avant d'evaluer une recette. inline thread_local std::uint32_t HeightfieldColumnOctaveDrop = 0; - inline thread_local VisualDetailCutoffV1 HeightfieldVisualDetailCutoff{}; - inline thread_local bool HeightfieldVisualDetailProfileEnabled = false; inline thread_local std::int64_t HeightfieldColumnInterpolationStrideSubvoxels = 0; - inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{}; inline std::int32_t FractalNoiseQ16(std::int64_t xQ16, std::int64_t yQ16, - std::int64_t zQ16, std::uint32_t octaves, bool ridged = false, - std::int64_t baseWavelengthSubvoxels = 0, - std::int64_t maximumDisplacementSubvoxels = 0) + std::int64_t zQ16, std::uint32_t octaves, bool ridged = false) { - if(HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels<=0 - &&HeightfieldColumnOctaveDrop==0&&!HeightfieldVisualDetailProfileEnabled) - { - std::int64_t ExactTotal=0,ExactWeightSum=0,ExactWeight=Q16,ExactRidgeWeight=Q16; - for(std::uint32_t Octave=0;Octave(Value*2,0,Q16); - } - ExactTotal+=MulQ16(Value,ExactWeight); - ExactWeightSum+=ExactWeight; - ExactWeight/=2; - } - if(ExactWeightSum==0)return 0; - const std::int64_t ExactAverage=(ExactTotal*Q16)/ExactWeightSum; - return static_cast(ridged?ExactAverage*2-Q16:ExactAverage); - } std::int64_t total = 0, weightSum = 0, weight = Q16, ridgeWeight = Q16; std::uint32_t effectiveOctaves=octaves; if(HeightfieldColumnOctaveDrop!=0&&octaves!=0) effectiveOctaves=std::max(1u,octaves-std::min(octaves-1,HeightfieldColumnOctaveDrop)); - VisualDetailCutoffResultV1 DetailCutoff; - const bool bDeclaredVisualTerm=baseWavelengthSubvoxels>0 - &&maximumDisplacementSubvoxels>0; - if(HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels>0 - &&HeightfieldColumnOctaveDrop==0&&baseWavelengthSubvoxels>0 - &&maximumDisplacementSubvoxels>0) - { - const VisualDetailOctaveTermV1 Term{0,baseWavelengthSubvoxels, - maximumDisplacementSubvoxels,octaves,2u*static_cast(Q16)}; - DetailCutoff=FindVisualDetailTailCutoffV1(Term,HeightfieldVisualDetailCutoff); - if(DetailCutoff.firstSkippedOctave>0&&HeightfieldVisualDetailCutoff.apply) - effectiveOctaves=std::min(effectiveOctaves,DetailCutoff.firstSkippedOctave); - } - if(HeightfieldVisualDetailProfileEnabled&&bDeclaredVisualTerm - &&DetailCutoff.firstSkippedOctave>0) - for(std::uint32_t octave=DetailCutoff.firstSkippedOctave; - octave0&&HeightfieldVisualDetailCutoff.apply) - { - std::int64_t fullWeight=Q16; - for(std::uint32_t octave=0;octave( - std::chrono::steady_clock::now()-OctaveStart).count(); - ++HeightfieldColumnStats.visualDetailOctaveEvaluations[octave]; - HeightfieldColumnStats.visualDetailOctaveMilliseconds[octave]+=OctaveMilliseconds; - if(DetailCutoff.firstSkippedOctave>0&&octave>=DetailCutoff.firstSkippedOctave) - HeightfieldColumnStats.visualDetailOctaveEligibleMilliseconds[octave] - +=OctaveMilliseconds; - } std::int64_t value = noise; if (ridged) { @@ -1129,24 +1057,17 @@ namespace world_assembly_detail weight /= 2; } if (weightSum == 0) return 0; - // The cutoff retains the original denominator: omitted zero-mean terms do not - // renormalize the surviving terrain. The historical diagnostic drop above keeps - // its old renormalized path so the quality gate can reproduce S13. - const std::int64_t denominator=HeightfieldColumnOctaveDrop==0 - &&DetailCutoff.firstSkippedOctave>0&&HeightfieldVisualDetailCutoff.apply - ?fullWeightSum:weightSum; - const std::int64_t average = (total * Q16) / denominator; + const std::int64_t average = (total * Q16) / weightSum; return static_cast(ridged ? average * 2 - Q16 : average); } inline std::int32_t ReliefQ16(std::uint32_t seed, std::int64_t x, - std::int64_t y, std::int64_t wavelength, std::int64_t contrastQ16, - std::int64_t maximumDisplacementSubvoxels=0) + std::int64_t y, std::int64_t wavelength, std::int64_t contrastQ16) { const std::int32_t noise = FractalNoiseQ16( NoiseCoordinateQ16(x,wavelength)+SeedOffsetQ16(seed,730), NoiseCoordinateQ16(y,wavelength)+SeedOffsetQ16(seed,210), - SeedOffsetQ16(seed,50),2,false,wavelength,maximumDisplacementSubvoxels); + SeedOffsetQ16(seed,50),2); const std::int64_t raw = Q16/2 + noise/2; const std::int64_t contrasted = Q16/2 + MulQ16(raw-Q16/2,contrastQ16); return static_cast(SmoothQ16(static_cast( @@ -1182,22 +1103,15 @@ namespace world_assembly_detail qx+=WarpDisplacement(wx,biome.heightWarpStrengthSubvoxels); qy+=WarpDisplacement(wy,biome.heightWarpStrengthSubvoxels); } - const bool bDetailContractSafe=biome.cliffStrengthQ16==0; - const std::int64_t reliefInfluence=(std::abs(biome.mountainAmplitudeSubvoxels) - +std::abs(biome.continentAmplitudeSubvoxels)*11/40) - *std::clamp(biome.reliefStrengthQ16,0,Q16) - *std::max(0,biome.reliefContrastQ16)*3/(4*Q16*Q16); const std::int32_t relief=ReliefQ16(seed,x,y,biome.reliefWavelengthSubvoxels, - biome.reliefContrastQ16,bDetailContractSafe?reliefInfluence:0); + biome.reliefContrastQ16); const std::int32_t mountainous=static_cast(Q16 +MulQ16(relief-Q16,biome.reliefStrengthQ16)); if(outRelief)*outRelief=mountainous; const std::int64_t contX=NoiseCoordinateQ16(qx,biome.continentWavelengthSubvoxels)+SeedOffsetQ16(seed,310); const std::int64_t contY=NoiseCoordinateQ16(qy,biome.continentWavelengthSubvoxels)+SeedOffsetQ16(seed,570); const std::int64_t contZ=SeedOffsetQ16(seed,70); - const std::int64_t continentInfluence=(std::abs(biome.continentAmplitudeSubvoxels)*775+999)/1000; - const std::int32_t continent=FractalNoiseQ16(contX,contY,contZ,4,false, - biome.continentWavelengthSubvoxels,bDetailContractSafe?continentInfluence:0); + const std::int32_t continent=FractalNoiseQ16(contX,contY,contZ,4); const std::int64_t continentScale=Q16*45/100+MulQ16(mountainous,Q16*55/100); std::int64_t height=biome.baseHeightSubvoxels +MulQ16(MulQ16(continent,biome.continentAmplitudeSubvoxels),continentScale)/2; @@ -1206,19 +1120,14 @@ namespace world_assembly_detail const std::int64_t mountainX=NoiseCoordinateQ16(qx,biome.mountainWavelengthSubvoxels)+SeedOffsetQ16(seed,990); const std::int64_t mountainY=NoiseCoordinateQ16(qy,biome.mountainWavelengthSubvoxels)+SeedOffsetQ16(seed,770); const std::int64_t mountainZ=SeedOffsetQ16(seed,90); - const std::int32_t ridge=FractalNoiseQ16(mountainX,mountainY,mountainZ,4,true, - biome.mountainWavelengthSubvoxels, - bDetailContractSafe?std::abs(biome.mountainAmplitudeSubvoxels)*2:0); + const std::int32_t ridge=FractalNoiseQ16(mountainX,mountainY,mountainZ,4,true); const std::int64_t ridgeHeight=std::clamp((ridge+Q16)/2,0,Q16); height+=MulQ16(MulQ16(ridgeHeight,biome.mountainAmplitudeSubvoxels),mountainous); } const std::int64_t detailX=NoiseCoordinateQ16(x,biome.detailWavelengthSubvoxels)+11*Q16; const std::int64_t detailY=NoiseCoordinateQ16(y,biome.detailWavelengthSubvoxels)+22*Q16; const std::int64_t detailZ=SeedOffsetQ16(seed,130); - const std::int32_t detailNoise=FractalNoiseQ16(detailX,detailY,detailZ,3,false, - biome.detailWavelengthSubvoxels, - bDetailContractSafe?std::abs(biome.detailAmplitudeSubvoxels):0); - height+=MulQ16(detailNoise,biome.detailAmplitudeSubvoxels); + height+=MulQ16(FractalNoiseQ16(detailX,detailY,detailZ,3),biome.detailAmplitudeSubvoxels); return height; } @@ -1415,24 +1324,21 @@ namespace world_assembly_detail { const std::int64_t nx=NoiseCoordinateQ16(qx,swellWave)+SeedOffsetQ16(recipe.seed,190); const std::int64_t ny=NoiseCoordinateQ16(qy,swellWave)+13*Q16; - const std::int32_t swell=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,50),3, - false,swellWave,(std::abs(undulation)*5+3)/4); + const std::int32_t swell=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,50),3); cap+=WarpDisplacement(swell,undulation); } if(detail>0) { const std::int64_t nx=NoiseCoordinateQ16(x,detailWave)+5*Q16; const std::int64_t ny=NoiseCoordinateQ16(y,detailWave)+6*Q16; - const std::int32_t noise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,210),3, - false,detailWave,(std::abs(detail)*5+3)/4); + const std::int32_t noise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,210),3); cap-=WarpDisplacement(std::abs(noise),detail); } if(ridge>0) { const std::int64_t nx=NoiseCoordinateQ16(qx,ridgeWave)+31*Q16; const std::int64_t ny=NoiseCoordinateQ16(qy,ridgeWave)+47*Q16; - const std::int32_t ridgeNoise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,110),4, - true,ridgeWave,std::abs(ridge)*2); + const std::int32_t ridgeNoise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,110),4,true); const std::int64_t hang=std::clamp((ridgeNoise+Q16)/2,0,Q16); cap-=MulQ16(hang,ridge); } @@ -1455,10 +1361,6 @@ namespace world_assembly_detail const WorldAssemblyStratumPlanV1* plan = nullptr; std::uint64_t recipeHash = 0, planHash = 0; std::int64_t x = 0, y = 0; - std::int64_t detailBudgetSubvoxels = 0,detailStepSubvoxels = 0; - std::uint32_t detailCallBudgetDivisor = 0; - bool detailApply = false; - std::uint32_t octaveDrop = 0; HeightfieldPointSample column{}; }; // Keep 65,536 entries in each worker cache; four ways tolerate coordinate families that @@ -1471,6 +1373,7 @@ namespace world_assembly_detail std::array> HeightfieldColumnCache; inline thread_local VoxelForgeCoreThreadLocal::FLazy< std::array> HeightfieldColumnCacheNextWay; + inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{}; inline thread_local bool HeightfieldColumnCacheLegacyDirectMapped=false; inline std::size_t HeightfieldColumnCacheSet(const WorldAssemblyRecipeV1& recipe, @@ -1480,19 +1383,9 @@ namespace world_assembly_detail // entropy from widely spaced coarse LOD coordinates in its low 16 bits. // Melanger tous les bits de coordonnee avant le masque; l'ancien hash perdait de // l'entropie sur les coordonnees espacees des LOD grossiers. - const std::uint64_t DetailContext=HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels>0 - ?static_cast(HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels) - ^std::rotl(static_cast( - HeightfieldVisualDetailCutoff.sampleStepSubvoxels),11) - ^(static_cast( - HeightfieldVisualDetailCutoff.callBudgetDivisor)<<19) - ^(static_cast(HeightfieldVisualDetailCutoff.apply)<<53) - :0; std::uint64_t mixed=static_cast(x)*0x9E3779B185EBCA87ull ^std::rotl(static_cast(y)*0xC2B2AE3D27D4EB4Full,29) - ^recipe.contentHash^std::rotl(plan.contentHash,17) - ^DetailContext - ^(static_cast(HeightfieldColumnOctaveDrop)<<37); + ^recipe.contentHash^std::rotl(plan.contentHash,17); mixed^=mixed>>30; mixed*=0xBF58476D1CE4E5B9ull; mixed^=mixed>>27; @@ -1538,8 +1431,7 @@ namespace world_assembly_detail const std::int64_t nx=NoiseCoordinateQ16(x,wave)+SeedOffsetQ16(featureSeed,1700); const std::int64_t ny=NoiseCoordinateQ16(y,wave)+SeedOffsetQ16(featureSeed,2900); const std::int32_t noise=FractalNoiseQ16(nx,ny, - SeedOffsetQ16(featureSeed,4100),3,false,wave, - std::abs(feature.parametersSubvoxels[0])); + SeedOffsetQ16(featureSeed,4100),3); cap+=MulQ16(MulQ16(noise,feature.parametersSubvoxels[0]),fade); } } @@ -1650,14 +1542,8 @@ namespace world_assembly_detail C11.capHeightSubvoxels); return Out; } - const std::int64_t DetailBudget=HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels; - const bool bVisualContextActive=DetailBudget>0; - const std::int64_t DetailStep=DetailBudget>0 - ?HeightfieldVisualDetailCutoff.sampleStepSubvoxels:0; - const std::uint32_t DetailDivisor=DetailBudget>0 - ?HeightfieldVisualDetailCutoff.callBudgetDivisor:0; - const bool DetailApply=DetailBudget>0&&HeightfieldVisualDetailCutoff.apply; - const std::uint32_t OctaveDrop=HeightfieldColumnOctaveDrop; + if(HeightfieldColumnOctaveDrop!=0) + return EvaluateColumn(recipe,plan,x,y); auto& cache=HeightfieldColumnCache.Get(); auto& nextWay=HeightfieldColumnCacheNextWay.Get(); HeightfieldColumnCacheEntry* replacement=nullptr; @@ -1668,23 +1554,11 @@ namespace world_assembly_detail const std::uint64_t mixed=(static_cast(x)*0x9E3779B185EBCA87ull) ^std::rotl(static_cast(y)*0xC2B2AE3D27D4EB4Full,23) ^static_cast(reinterpret_cast(&recipe)) - ^std::rotl(static_cast(reinterpret_cast(&plan)),41) - ^static_cast(DetailBudget) - ^std::rotl(static_cast(DetailStep),11) - ^(static_cast(DetailDivisor)<<19) - ^(static_cast(DetailApply)<<53) - ^(static_cast(OctaveDrop)<<37); + ^std::rotl(static_cast(reinterpret_cast(&plan)),41); replacement=&cache[static_cast(mixed)&(cache.size()-1)]; if(replacement->recipe==&recipe&&replacement->plan==&plan &&replacement->recipeHash==recipe.contentHash - &&replacement->planHash==plan.contentHash&&replacement->x==x&&replacement->y==y - &&replacement->octaveDrop==OctaveDrop - &&(bVisualContextActive - ?replacement->detailBudgetSubvoxels==DetailBudget - &&replacement->detailStepSubvoxels==DetailStep - &&replacement->detailCallBudgetDivisor==DetailDivisor - &&replacement->detailApply==DetailApply - :replacement->detailBudgetSubvoxels==0)) + &&replacement->planHash==plan.contentHash&&replacement->x==x&&replacement->y==y) {++HeightfieldColumnStats.columnHits;return replacement->column;} if(replacement->recipe!=nullptr)++HeightfieldColumnStats.columnConflictMisses; } @@ -1698,14 +1572,7 @@ namespace world_assembly_detail auto& candidate=cache[first+way]; if(candidate.recipe==&recipe&&candidate.plan==&plan &&candidate.recipeHash==recipe.contentHash&&candidate.planHash==plan.contentHash - &&candidate.x==x&&candidate.y==y - &&candidate.octaveDrop==OctaveDrop - &&(bVisualContextActive - ?candidate.detailBudgetSubvoxels==DetailBudget - &&candidate.detailStepSubvoxels==DetailStep - &&candidate.detailCallBudgetDivisor==DetailDivisor - &&candidate.detailApply==DetailApply - :candidate.detailBudgetSubvoxels==0)) + &&candidate.x==x&&candidate.y==y) {++HeightfieldColumnStats.columnHits;return candidate.column;} if(candidate.recipe==nullptr&&replacementWay==HeightfieldColumnCacheWays) replacementWay=way; @@ -1736,8 +1603,7 @@ namespace world_assembly_detail HeightfieldColumnStats.coordinateCaptureComplete=false; } } - entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y, - DetailBudget,DetailStep,DetailDivisor,DetailApply,OctaveDrop,column}; + entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y,column}; return column; } @@ -1827,48 +1693,6 @@ namespace detail_world_assembly } } -VisualDetailCutoffResultV1 FindVisualDetailTailCutoffV1( - const VisualDetailOctaveTermV1& Term,const VisualDetailCutoffV1& Cutoff) noexcept -{ - VisualDetailCutoffResultV1 Result; - if(Term.octaveCount<2||Term.baseWavelengthSubvoxels<=0 - ||Term.maximumDisplacementSubvoxels<=0||Cutoff.pixelBudgetSubvoxels<=0 - ||Cutoff.sampleStepSubvoxels<=0||Cutoff.callBudgetDivisor==0)return Result; - std::int64_t TotalWeight=0,Weight=65536; - for(std::uint32_t Octave=0;Octave(1, - Cutoff.pixelBudgetSubvoxels/Cutoff.callBudgetDivisor); - for(std::uint32_t First=1;First=First) - { - const std::int64_t Wave=Term.baseWavelengthSubvoxels - /static_cast(1ull<(Octave,62)); - bSubSampled=bSubSampled&&Wave<=2*Cutoff.sampleStepSubvoxels; - TailWeight+=Weight; - } - Weight/=2; - } - if(!bSubSampled)continue; - const long double Bound=static_cast(Term.maximumDisplacementSubvoxels) - *Term.maximumNoiseAbsQ16*TailWeight - /(65536.0L*TotalWeight); - const std::int64_t Error=static_cast(std::ceil(Bound)); - if(Error<=PerCallBudget) - { - Result.firstSkippedOctave=First; - Result.maximumErrorSubvoxels=Error; - break; - } - } - return Result; -} - std::optional WorldAssemblyManifest::FromJson( std::string_view json, std::string& error) { @@ -2264,11 +2088,6 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl Stats.columnMisses=0; Stats.columnConflictMisses=0; Stats.columnComputeMilliseconds=0.0; - Stats.visualDetailOctaveEvaluations.fill(0); - Stats.visualDetailOctaveBudgetEligible.fill(0); - Stats.visualDetailOctaveSkips.fill(0); - Stats.visualDetailOctaveMilliseconds.fill(0.0); - Stats.visualDetailOctaveEligibleMilliseconds.fill(0.0); Stats.captureCoordinates=captureCoordinates; Stats.coordinateCaptureComplete=true; Stats.evaluatedCoordinates.clear(); @@ -2277,32 +2096,18 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl void Heightfield2D::setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept { world_assembly_detail::HeightfieldColumnCacheLegacyDirectMapped=enabled; } -void Heightfield2D::setThreadVisualDetailCutoff(VisualDetailCutoffV1 cutoff) noexcept -{world_assembly_detail::HeightfieldVisualDetailCutoff=cutoff;} - -VisualDetailCutoffV1 Heightfield2D::threadVisualDetailCutoff() noexcept -{return world_assembly_detail::HeightfieldVisualDetailCutoff;} - void Heightfield2D::setThreadColumnOctaveDrop(std::uint32_t octaveDrop) noexcept -{world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop;} +{ world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop; } std::uint32_t Heightfield2D::threadColumnOctaveDrop() noexcept -{return world_assembly_detail::HeightfieldColumnOctaveDrop;} - -void Heightfield2D::setThreadVisualDetailProfileEnabled(bool enabled) noexcept -{ - world_assembly_detail::HeightfieldVisualDetailProfileEnabled=enabled; - world_assembly_detail::HeightfieldColumnStats.profileVisualDetailOctaves=enabled; -} - -bool Heightfield2D::threadVisualDetailProfileEnabled() noexcept -{return world_assembly_detail::HeightfieldVisualDetailProfileEnabled;} +{ return world_assembly_detail::HeightfieldColumnOctaveDrop; } void Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(std::int64_t stride) noexcept -{world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels=std::max(0,stride);} +{ world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels=std::max(0,stride); } std::int64_t Heightfield2D::threadColumnInterpolationStrideSubvoxels() noexcept -{return world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels;} +{ return world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels; } + const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept { return world_assembly_detail::HeightfieldColumnStats; } @@ -2500,64 +2305,6 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept return out; } -VisualDetailRecipeContractV1 Heightfield2D::visualDetailContract() const -{ - VisualDetailRecipeContractV1 Contract; - if(!isValid()||recipe_->evaluatorVersion<2)return Contract; - const auto Add=[&](std::uint64_t Id,std::int64_t Wavelength, - std::int64_t Amplitude,std::uint32_t Octaves) - { - if(Wavelength>0&&Amplitude>0) - Contract.terms.push_back({Id,Wavelength,Amplitude,Octaves, - 2u*static_cast(world_assembly_detail::Q16)}); - }; - for(const auto& Biome:recipe_->biomes) - { - if(Biome.cliffStrengthQ16==0) - { - Add((static_cast(Biome.stableId)<<8)|1u, - Biome.continentWavelengthSubvoxels, - (std::abs(Biome.continentAmplitudeSubvoxels)*775+999)/1000,4); - Add((static_cast(Biome.stableId)<<8)|2u, - Biome.mountainWavelengthSubvoxels, - std::abs(Biome.mountainAmplitudeSubvoxels)*2,4); - Add((static_cast(Biome.stableId)<<8)|3u, - Biome.detailWavelengthSubvoxels, - std::abs(Biome.detailAmplitudeSubvoxels),3); - const std::int64_t ReliefInfluence=(std::abs(Biome.mountainAmplitudeSubvoxels) - +std::abs(Biome.continentAmplitudeSubvoxels)*11/40) - *std::clamp(Biome.reliefStrengthQ16,0,world_assembly_detail::Q16) - *std::max(0,Biome.reliefContrastQ16)*3 - /(4*world_assembly_detail::Q16*world_assembly_detail::Q16); - Add((static_cast(Biome.stableId)<<8)|4u, - Biome.reliefWavelengthSubvoxels,ReliefInfluence,2); - } - const std::int64_t CapUndulation=Biome.hasCapProfile - ?Biome.capUndulationSubvoxels:plan_->capUndulationSubvoxels; - const std::int64_t CapWave=Biome.hasCapProfile - ?Biome.capWavelengthSubvoxels:plan_->capWavelengthSubvoxels; - const std::int64_t CapDetail=Biome.hasCapProfile - ?Biome.capDetailAmplitudeSubvoxels:plan_->capDetailAmplitudeSubvoxels; - const std::int64_t CapDetailWave=Biome.hasCapProfile - ?Biome.capDetailWavelengthSubvoxels:plan_->capDetailWavelengthSubvoxels; - const std::int64_t CapRidge=Biome.hasCapProfile - ?Biome.capRidgeAmplitudeSubvoxels:plan_->capRidgeAmplitudeSubvoxels; - const std::int64_t CapRidgeWave=Biome.hasCapProfile - ?Biome.capRidgeWavelengthSubvoxels:plan_->capRidgeWavelengthSubvoxels; - Add((static_cast(Biome.stableId)<<8)|5u,CapWave, - (std::abs(CapUndulation)*5+3)/4,3); - Add((static_cast(Biome.stableId)<<8)|6u,CapDetailWave, - (std::abs(CapDetail)*5+3)/4,3); - Add((static_cast(Biome.stableId)<<8)|7u,CapRidgeWave, - std::abs(CapRidge)*2,4); - } - for(const auto& Feature:plan_->features) - if(Feature.type==3) - Add(Feature.stableId,Feature.parametersSubvoxels[1], - std::abs(Feature.parametersSubvoxels[0]),3); - return Contract; -} - FieldBlockContract Heightfield2D::contract() const noexcept { return {ChannelBit(FieldChannel::biome), diff --git a/Standalone/Research/WorldAssemblerM3/Linux6/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux6/REPORT.md new file mode 100644 index 0000000..1000d52 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux6/REPORT.md @@ -0,0 +1,102 @@ +# Linux6 report + +## Result + +The screen-space gate rejects today’s far meshes at every sampled LOD3–8: the largest measured same-field error is 92.0 px, and the largest sampled swap pop is 70.3 px at LOD6→5. Same-LOD regeneration is exact, the reported swaps have no cracks or floaters, and no display mesh changed in this round. + +Source base: `wip/l5-far-cutoff` at `b21a19d`; L4 reference: `7770074`. + +## Screen-space method + +For one surface tile nearest the L1 camera at each LOD, the gate rasterizes triangles on a 65×65 XY grid, excluding skirts. It compares the mesh hit to direct `Heightfield2D` ground/cap roots; slopes use ±1-voxel finite differences. For vertical residual `Δz` and slopes `sx,sy`, it computes normal distance `|Δz|/sqrt(1+sx²+sy²)`, projects that normal onto the camera’s screen plane, then divides by the one-pixel world distance at that LOD’s closest display distance. The view uses 90° horizontal FOV, 2560 px, yaw 0°, pitch −22°; one pixel is 0.000613592 rad. Same-LOD regeneration is the mesh reference for swaps; direct field roots are the reference for true surface error. + +The known failure control is a 1 m cell on an 89.427° plane, shifted sideways by 5 mm (0.005 cell): its vertical metric says 8.488 px, while its true normal distance is 5 mm and projected screen error is 0.0849 px. This confirms that vertical height can badly overstate visible displacement. + +| LOD | Closest distance (m) | Field error p50 / p99 / max (px) | Max normal distance (m) | +|---:|---:|---:|---:| +| 3 | 96 | 0.81 / 4.16 / 6.00 | 0.423 | +| 4 | 192 | 1.01 / 5.12 / 7.93 | 0.952 | +| 5 | 384 | 0.79 / 16.37 / 92.02 | 25.122 | +| 6 | 768 | 0.51 / 9.75 / 70.34 | 37.644 | +| 7 | 1,536 | 0.29 / 9.04 / 42.36 | 42.714 | +| 8 | 3,072 | 0.83 / 18.03 / 47.18 | 93.175 | + +| Swap | Distance (m) | Pop p50 / p99 / max (px) | Max normal distance (m) | +|---|---:|---:|---:| +| 3→2 | 96 | 0.61 / 3.77 / 6.05 | 0.367 | +| 4→3 | 192 | 0.75 / 4.63 / 7.51 | 0.896 | +| 5→4 | 384 | 0.00 / 9.58 / 31.78 | 8.139 | +| 6→5 | 768 | 0.00 / 9.75 / 70.34 | 37.644 | +| 7→6 | 1,536 | 0.00 / 9.04 / 42.36 | 42.714 | +| 8→7 | 3,072 | 0.83 / 18.03 / 47.18 | 93.175 | + +The tails are not confined to cliffs. The field maximum slopes at LOD5–8 are 8.34°, 14.09°, 6.76°, and 9.33°; LOD5’s p99 is 19.8 px below 30° and 21.1 px at 45–60°. The sampled LOD6→5 maximum is at `(-472, -1432, -463.151) m`; [the marked offscreen capture](captures/lod6_to_5_max_pop.jpg) shows that location at the 768 m swap distance. This is a sampled-tile result, not a whole-world supremum. + +All same-LOD mesh comparisons at LOD3–8 pass with zero error. The S13 octave-drop and corrected stride-2 column-interpolation controls are rejected at every LOD3–8, as are missing displayed surface, 2 m floating surface, and missing fine-ring injections. The measured swap rows have zero cracks and floaters; they reject solely on displacement. + +## Far worker time, W12 + +The L1 view has 2553 tiles. Clean-run phase totals for LOD3–8 are shown below. A separate temporary timer measured complete `FractalNoiseQ16` calls; “column outside” is clean column time minus that subtotal. The timer added 3–6% to worker time, so the split between those two column subphases is approximate; it has been removed from the final tree. + +| LOD | Worker (s) | Column outside noise (ms) | Fractal/noise calls (ms) | MC (ms) | Ground refine (ms) | Cap refine (ms) | Skirts (ms) | Assembly (ms) | +|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| 3 | 1.084 | 171 | 276 | 473 | 0 | 0 | 118 | 33 | +| 4 | 2.581 | 1,000 | 534 | 440 | 0 | 304 | 196 | 102 | +| 5 | 1.829 | 658 | 358 | 100 | 464 | 22 | 161 | 70 | +| 6 | 2.761 | 1,608 | 284 | 59 | 385 | 58 | 162 | 70 | +| 7 | 1.192 | 234 | 198 | 32 | 275 | 202 | 153 | 96 | +| 8 | 2.224 | 264 | 469 | 256 | 424 | 299 | 333 | 177 | +| **Total** | **11.671** | **3,934** | **2,119** | **1,360** | **1,548** | **885** | **1,123** | **548** | + +Column work outside the fractal calls is the largest single phase (33.7% of far worker time); all column work is 51.9%. Ground and cap refinement total 2.43 worker-s, skirts 1.12 worker-s, MC 1.36 worker-s, and assembly 0.55 worker-s. The earlier 7–13% profile timed individual Perlin calls; the L6 subtotal times complete fractal calls, including their octave accumulation. + +The listed subphases total 11.52 worker-s; about 0.15 worker-s remains in timer and per-tile residuals. + +The overlap scan counted about 411,000 cross-worker neighboring duplicate XY evaluations at LOD3–8 (about 166,000 at LOD4 alone). Exact column reuse is the largest plausible lever because it targets the largest phase. I did not build it: no candidate has yet shown a screen-gated, greater-than-3% whole-view win. Stride-2 interpolation is not a safe substitute; its control fails at every far LOD. + +## Cost with the feature off + +Three interleaved W12 runs per binary, same L1 camera and forced generation. `whole_view_ready` is the comparison metric; the six whole-view hashes matched exactly. + +| Binary | Runs (s) | Median | +|---|---|---:| +| L4 reference | 2.082, 2.180, 2.183 | 2.180 s | +| L6 cleaned tree | 2.127, 2.185, 2.223 | 2.185 s (+0.2%) | + +Run-to-run spread was about 0.10 s, so the 5 ms median difference is within noise. The no-op visual cutoff contract, plan, and CLI flag were removed. The opt-in research gate retains its negative controls; skirt timing uses the existing optional stage-timing pointer, which is null on the default path. Governor stayed `powersave`; runs used CPUs 0–11, W12, 6 GiB address-space limits, and normal priority. Peak runtime RSS was 403 MiB; peak build process RSS was 874 MiB (GCC). + +## Determinism and hashes + +Clang 19 `-ffp-contract=off` W1/W12 and GCC default W1/W12 produced the same hashes. LOD0–2, exact queries, and all whole-view hashes equal L4; no recipe bump is needed. + +- Recipe / plan / manifest: `83b616a3f4fe781d` / `1ca84619c8600ee5` / `e55991385bf0ca29` (recipe version 1). +- Whole view (density / mesh / payload / topology): `c10697a8f48380c7` / `e75c9119a1498cd5` / `49a9a8d1835a6c8c` / `edcb23288b5e8724`. +- Full detail (density / mesh / payload / topology): `0ee0579bd15cbbe4` / `32e5d4964d610fee` / `1932b96ee4b1d910` / `69743c6bad3447dc`. +- Exact queries (density / unclamped / detail / collision): `e4d4a8eb95b35933` / `e4d4a8eb95b35933` / `efcba20c0c70c415` / `e604823a249029bf`. +- SHA-256 L4 binary / Clang L6 binary / GCC L6 binary: `6efb8f19a41ca3cd23aca9294ceee1e9cdd1d3dc8da23b3ee61ef8ae943aa763` / `3f5a4f88b0d911e8e66af9ef72e294926a5507a73d44fcfe868df27c666bf01e` / `de872e8fdaa9f40e8a6c096700fd998bcf35f232b06f2b4e35a374b4b0e9fa07`. +- Capture JPEG SHA-256: `483b434f3ad752dd09b82cd1c78fe81ee8cd31803ecf4dcd3c2918a8ae5d66b1` (156 KiB). + +## Failed or retried runs + +- First Clang build failed on a missing view argument in `LinuxScreenSpaceGate.h`; fixed and rebuilt successfully. +- First gate pass paired extra mesh hits incorrectly and reported same-LOD floaters at LOD5–8. The reference pairing was corrected; the final gate reports zero same-LOD displacement and no floaters. +- A `/usr/bin/time -v` wrapper attempt exited 127 because that binary is unavailable; the runs were retried with `/proc` RSS monitoring. +- The final quality gates exit 6 by design because measured displacement exceeds the 1 px rule. Builds, captures, determinism runs, and cost runs completed successfully. EGL could not write `~/.cache` in this environment, disabled shader caching, and rendered successfully on llvmpipe. + +## Note for the owner + +Today’s `assembler/world` remains at L4; this sampled view measures up to 92.0 px from the field and a 70.3 px LOD6→5 pop at the tested display distances, with the largest point marked in the capture. This round changes no default mesh or recipe output, removes the no-op cutoff, and leaves W12 cost within run-to-run noise. + +## Orchestrator review (Claude) + +- Cost re-run by me (interleaved W12, L1 view): L4 2.098/2.138/2.171 s vs L6 2.116/2.150/2.197 s + (median +0.6%, within noise), density `c10697a8f48380c7`; the L6 binary SHA matches the report. + Merged into `assembler/world` with L5 squashed in. +- The marked LOD6→5 maximum sits on the **rim of a flat-topped hill (mesa edge)**, not on open gentle + ground: the "8–14° slope" is the field slope at the sample, on the plateau top beside the drop. The + coarse mesh rounds the rim by about a cell (16–32 m at LOD5–6), so the pop is a real **silhouette snap + at sharp rims** (tens of px at 768 m), while p50 stays at 0 px. That is ordinary LOD-switch behaviour + without geomorphing or cross-fade, so a fix belongs in rendering (Unreal), not in generation. The + owner decides whether it is visible enough to matter. +- Next speed lever, per the far breakdown: exact cross-tile column reuse (column work outside noise is + 34% of far worker time; ~411k duplicate XY evaluations at LOD3–8). diff --git a/Standalone/Research/WorldAssemblerM3/Linux6/captures/lod6_to_5_max_pop.jpg b/Standalone/Research/WorldAssemblerM3/Linux6/captures/lod6_to_5_max_pop.jpg new file mode 100644 index 0000000..6c16c9f Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux6/captures/lod6_to_5_max_pop.jpg differ diff --git a/Standalone/Source/LinuxOffscreenCapture.cpp b/Standalone/Source/LinuxOffscreenCapture.cpp index 87335c8..95757cc 100644 --- a/Standalone/Source/LinuxOffscreenCapture.cpp +++ b/Standalone/Source/LinuxOffscreenCapture.cpp @@ -341,11 +341,12 @@ int main(int argc,char** argv) { if(argc<3) { - std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--yaw-deg=N] [--pitch-deg=N]\n",argv[0]); + std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N]\n",argv[0]); return 2; } int Width=2560,Height=1440;double Fov=90.0; bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0; + bool HasCamera=false;std::array Camera{}; for(int I=3;I=179.0) {std::fprintf(stderr,"Invalid horizontal FOV\n");return 2;} } + else if(Arg.starts_with("--camera-m=")) + { + const std::string Value(Arg.substr(11)); + if(std::sscanf(Value.c_str(),"%lf,%lf,%lf",&Camera[0],&Camera[1],&Camera[2])!=3 + ||!std::isfinite(Camera[0])||!std::isfinite(Camera[1])||!std::isfinite(Camera[2])) + {std::fprintf(stderr,"Invalid camera position\n");return 2;} + HasCamera=true; + } else if(Arg.starts_with("--yaw-deg=")||Arg.starts_with("--pitch-deg=")) { const bool IsYaw=Arg.starts_with("--yaw-deg="); @@ -376,6 +385,7 @@ int main(int argc,char** argv) FScene Scene;std::string Error; if(!LoadScene(argv[1],Scene,Error)) {std::fprintf(stderr,"[OffscreenCapture] FAIL: %s\n",Error.c_str());return 2;} + if(HasCamera)Scene.CameraMeters=Camera; if(HasYaw)Scene.YawDegrees=Yaw; if(HasPitch)Scene.PitchDegrees=Pitch; std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f vertices=%zu triangles=%llu\n", diff --git a/Standalone/Source/LinuxScreenSpaceGate.h b/Standalone/Source/LinuxScreenSpaceGate.h index bf5f751..10c56b0 100644 --- a/Standalone/Source/LinuxScreenSpaceGate.h +++ b/Standalone/Source/LinuxScreenSpaceGate.h @@ -22,6 +22,12 @@ namespace StandaloneScreenSpaceGate int Lod=0; std::uint64_t Samples=0,Missing=0,Floating=0; double P50=0.0,P99=0.0,Maximum=0.0; + double VerticalP50=0.0,VerticalP99=0.0,VerticalMaximum=0.0; + double MaximumNormalDistanceMeters=0.0,MaximumX=0.0,MaximumY=0.0; + double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0; + double MaximumSlopeDegrees=0.0; + std::array SlopeSamples{}; + std::array SlopeP99{},SlopeMaximum{}; bool Valid=false; }; @@ -30,9 +36,128 @@ namespace StandaloneScreenSpaceGate int CoarseLod=0; std::uint64_t Samples=0,Cracks=0,Floating=0; double P50=0.0,P99=0.0,Maximum=0.0; + double VerticalP50=0.0,VerticalP99=0.0,VerticalMaximum=0.0; + double MaximumNormalDistanceMeters=0.0,MaximumX=0.0,MaximumY=0.0; + double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0; + double MaximumSlopeDegrees=0.0; + std::array SlopeSamples{}; + std::array SlopeP99{},SlopeMaximum{}; bool Valid=false; }; + struct FSurfaceRoot + { + double ZMeters=0.0; + double SlopeX=0.0; + double SlopeY=0.0; + }; + + struct FViewBasis + { + std::array Right{}; + std::array Up{}; + }; + + struct FErrorAccumulator + { + std::vector Pixels,VerticalPixels; + std::array,4> BySlope; + double Maximum=-1.0,MaximumVertical=0.0,MaximumNormalDistanceMeters=0.0; + double MaximumX=0.0,MaximumY=0.0,MaximumSlopeDegrees=0.0; + double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0; + std::array SlopeSamples{}; + }; + + inline double Percentile(std::vector& Values,double P); + + inline FViewBasis MakeViewBasis(double YawDegrees,double PitchDegrees) + { + constexpr double Pi=3.14159265358979323846; + const double Yaw=YawDegrees*Pi/180.0,Pitch=PitchDegrees*Pi/180.0; + const std::array Forward={std::sin(Yaw)*std::cos(Pitch), + std::cos(Yaw)*std::cos(Pitch),std::sin(Pitch)}; + FViewBasis Basis; + Basis.Right={std::cos(Yaw),-std::sin(Yaw),0.0}; + Basis.Up={Basis.Right[1]*Forward[2]-Basis.Right[2]*Forward[1], + Basis.Right[2]*Forward[0]-Basis.Right[0]*Forward[2], + Basis.Right[0]*Forward[1]-Basis.Right[1]*Forward[0]}; + return Basis; + } + + inline std::size_t SlopeBin(double Degrees) + { + if(Degrees<30.0)return 0; + if(Degrees<45.0)return 1; + if(Degrees<60.0)return 2; + return 3; + } + + inline void AddError(FErrorAccumulator& Accumulator,double DeltaZMeters, + const FSurfaceRoot& Root,double PixelBudgetMeters,const FViewBasis& View, + double X,double Y) + { + constexpr double Pi=3.14159265358979323846; + const double GradientSquared=Root.SlopeX*Root.SlopeX+Root.SlopeY*Root.SlopeY; + const double NormalLength=std::sqrt(1.0+GradientSquared); + const double NormalX=-Root.SlopeX/NormalLength; + const double NormalY=-Root.SlopeY/NormalLength; + const double NormalZ=1.0/NormalLength; + const double NormalDistance=std::abs(DeltaZMeters)/NormalLength; + const double ProjectedNormal=std::hypot( + NormalX*View.Right[0]+NormalY*View.Right[1]+NormalZ*View.Right[2], + NormalX*View.Up[0]+NormalY*View.Up[1]+NormalZ*View.Up[2]); + const double ProjectedVertical=std::hypot(View.Right[2],View.Up[2]); + const double NormalPixels=NormalDistance*ProjectedNormal/PixelBudgetMeters; + const double VerticalPixels=std::abs(DeltaZMeters)*ProjectedVertical/PixelBudgetMeters; + const double SlopeDegrees=std::atan(std::sqrt(GradientSquared))*180.0/Pi; + Accumulator.Pixels.push_back(NormalPixels); + Accumulator.VerticalPixels.push_back(VerticalPixels); + const std::size_t Bin=SlopeBin(SlopeDegrees); + Accumulator.BySlope[Bin].push_back(NormalPixels); + ++Accumulator.SlopeSamples[Bin]; + if(NormalPixels>Accumulator.Maximum) + { + Accumulator.Maximum=NormalPixels; + Accumulator.MaximumVertical=VerticalPixels; + Accumulator.MaximumNormalDistanceMeters=NormalDistance; + Accumulator.MaximumX=X;Accumulator.MaximumY=Y; + Accumulator.MaximumSlopeDegrees=SlopeDegrees; + Accumulator.MaximumReferenceZ=Root.ZMeters; + Accumulator.MaximumDisplayedZ=Root.ZMeters+DeltaZMeters; + } + } + + inline void AddMissing(FErrorAccumulator& Accumulator) + { + Accumulator.Pixels.push_back(2.0); + Accumulator.VerticalPixels.push_back(2.0); + } + + inline void FinalizeError(FErrorRow& Row,FErrorAccumulator& Accumulator) + { + Row.Samples=Accumulator.Pixels.size(); + Row.P50=Percentile(Accumulator.Pixels,0.50); + Row.P99=Percentile(Accumulator.Pixels,0.99); + Row.Maximum=Accumulator.Pixels.empty()?0.0 + :*std::max_element(Accumulator.Pixels.begin(),Accumulator.Pixels.end()); + Row.VerticalP50=Percentile(Accumulator.VerticalPixels,0.50); + Row.VerticalP99=Percentile(Accumulator.VerticalPixels,0.99); + Row.VerticalMaximum=Accumulator.VerticalPixels.empty()?0.0 + :*std::max_element(Accumulator.VerticalPixels.begin(),Accumulator.VerticalPixels.end()); + Row.MaximumNormalDistanceMeters=Accumulator.MaximumNormalDistanceMeters; + Row.MaximumX=Accumulator.MaximumX;Row.MaximumY=Accumulator.MaximumY; + Row.MaximumReferenceZ=Accumulator.MaximumReferenceZ; + Row.MaximumDisplayedZ=Accumulator.MaximumDisplayedZ; + Row.MaximumSlopeDegrees=Accumulator.MaximumSlopeDegrees; + Row.SlopeSamples=Accumulator.SlopeSamples; + for(std::size_t Bin=0;Bin ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy, + inline std::vector ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy, const FVoxelTileKey& Patch,double VoxelMeters, const VoxelForgeCore::WorldAssemblyManifest& Assembly) { - std::vector Roots; + std::vector Roots; const double XSubvoxels=Grid.X(Ix)/VoxelMeters*256.0; const double YSubvoxels=Grid.Y(Iy)/VoxelMeters*256.0; + const auto X=static_cast(std::llround(XSubvoxels)); + const auto Y=static_cast(std::llround(YSubvoxels)); const double MinZ=static_cast(Patch.OriginVoxels().Z); const double MaxZ=MinZ+Patch.ExtentVoxels(); + constexpr std::int64_t NormalProbeSubvoxels=256; for(const auto& Plan:Assembly.plan.strates) { VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan); if(!Field.isValid())continue; - const auto Column=Field.point(static_cast(std::llround(XSubvoxels)), - static_cast(std::llround(YSubvoxels)),0); + const auto Column=Field.point(X,Y,0); if(Column.groundHeightSubvoxels>=Column.capHeightSubvoxels)continue; - const double Ground=static_cast(Column.groundHeightSubvoxels)/(256.0)*VoxelMeters; - const double Cap=static_cast(Column.capHeightSubvoxels)/(256.0)*VoxelMeters; + const auto XMinus=Field.point(X-NormalProbeSubvoxels,Y,0); + const auto XPlus=Field.point(X+NormalProbeSubvoxels,Y,0); + const auto YMinus=Field.point(X,Y-NormalProbeSubvoxels,0); + const auto YPlus=Field.point(X,Y+NormalProbeSubvoxels,0); + const auto Slope=[&](std::int64_t Minus,std::int64_t Plus) + {return static_cast(Plus-Minus)/(2.0*NormalProbeSubvoxels);}; + const double Ground=static_cast(Column.groundHeightSubvoxels)/256.0*VoxelMeters; + const double Cap=static_cast(Column.capHeightSubvoxels)/256.0*VoxelMeters; const double PlanMinZ=static_cast(Plan.bottomChunkZ)*32.0*VoxelMeters; const double PlanMaxZ=static_cast(Plan.topChunkZ+1)*32.0*VoxelMeters; if(Ground>=MinZ*VoxelMeters-0.25*VoxelMeters &&Ground<=MaxZ*VoxelMeters+0.25*VoxelMeters &&Ground>=PlanMinZ-0.25*VoxelMeters&&Ground<=PlanMaxZ+0.25*VoxelMeters) - Roots.push_back(Ground); + Roots.push_back({Ground,Slope(XMinus.groundHeightSubvoxels, + XPlus.groundHeightSubvoxels),Slope(YMinus.groundHeightSubvoxels, + YPlus.groundHeightSubvoxels)}); if(Cap>=MinZ*VoxelMeters-0.25*VoxelMeters &&Cap<=MaxZ*VoxelMeters+0.25*VoxelMeters &&Cap>=PlanMinZ-0.25*VoxelMeters&&Cap<=PlanMaxZ+0.25*VoxelMeters) - Roots.push_back(Cap); + Roots.push_back({Cap,Slope(XMinus.capHeightSubvoxels, + XPlus.capHeightSubvoxels),Slope(YMinus.capHeightSubvoxels, + YPlus.capHeightSubvoxels)}); } - std::sort(Roots.begin(),Roots.end()); - Roots.erase(std::unique(Roots.begin(),Roots.end(),[](double A,double B) - {return std::abs(A-B)<0.01;}),Roots.end()); + std::sort(Roots.begin(),Roots.end(),[](const FSurfaceRoot& A,const FSurfaceRoot& B) + {return A.ZMeters Errors; + FErrorAccumulator Accumulator; for(int Iy=0;Iy Used(MeshGrid.At(Ix,Iy).size(),false); - for(double Root:Roots) + for(const FSurfaceRoot& Root:Roots) { std::size_t HitIndex=0;double ErrorMeters=0.0; - if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root,HitIndex,ErrorMeters)) + if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root.ZMeters,HitIndex,ErrorMeters)) { ++Row.Missing; - Errors.push_back(2.0); + AddMissing(Accumulator); continue; } Used[HitIndex]=true; - Errors.push_back(ErrorMeters/PixelBudgetMeters); + AddError(Accumulator,MeshGrid.At(Ix,Iy)[HitIndex]-Root.ZMeters, + Root,PixelBudgetMeters,View,MeshGrid.X(Ix),MeshGrid.Y(Iy)); ++Row.Samples; } // Heightfield2D declares ground and cap roots only. Other mesh intersections may // be valid tunnel geometry; the exact-mesh comparison checks extra pieces. } - Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99); - Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end()); - Row.Samples=Errors.size(); + FinalizeError(Row,Accumulator); return Row; } inline FErrorRow CompareToReference(const FHeightGrid& MeshGrid, - const FHeightGrid& ReferenceGrid,int Lod,double PixelBudgetMeters) + const FHeightGrid& ReferenceGrid,const FVoxelTileKey& Patch,double VoxelMeters, + double PixelBudgetMeters,const VoxelForgeCore::WorldAssemblyManifest& Assembly, + const FViewBasis& View) { - FErrorRow Row;Row.Lod=Lod;Row.Valid=true; - std::vector Errors; + FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true; + FErrorAccumulator Accumulator; for(int Iy=0;Iy Used(Mesh.size(),false); - for(double ReferenceZ:Reference) + const auto Roots=ExactHeightRoots(ReferenceGrid,Ix,Iy,Patch,VoxelMeters,Assembly); + std::vector UsedReference(Reference.size(),false),UsedMesh(Mesh.size(),false); + for(std::size_t ReferenceIndex=0;ReferenceIndexZMeters-ReferenceZ)<1.0) + Root=*BestRoot; + } + AddError(Accumulator,Mesh[MeshIndex]-Reference[ReferenceIndex], + Root,PixelBudgetMeters,View,ReferenceGrid.X(Ix),ReferenceGrid.Y(Iy)); } for(std::size_t I=0;I Errors; + FErrorAccumulator Accumulator; for(int Iy=0;Iy CoarseUsed(CoarseHits.size(),false); std::vector FineUsed(FineHits.size(),false); - for(double Root:Roots) + for(const FSurfaceRoot& Root:Roots) { std::size_t CoarseIndex=0,FineIndex=0; double CoarseError=0.0,FineError=0.0; - if(!MatchNearest(CoarseHits,CoarseUsed,Root,CoarseIndex,CoarseError) - ||!MatchNearest(FineHits,FineUsed,Root,FineIndex,FineError)) + if(!MatchNearest(CoarseHits,CoarseUsed,Root.ZMeters,CoarseIndex,CoarseError) + ||!MatchNearest(FineHits,FineUsed,Root.ZMeters,FineIndex,FineError)) { - ++Row.Cracks;Errors.push_back(2.0);continue; + ++Row.Cracks;AddMissing(Accumulator);continue; } CoarseUsed[CoarseIndex]=true; FineUsed[FineIndex]=true; - Errors.push_back(std::abs(CoarseHits[CoarseIndex]-FineHits[FineIndex]) - /PixelBudgetMeters); + AddError(Accumulator,CoarseHits[CoarseIndex]-FineHits[FineIndex], + Root,PixelBudgetMeters,View,Coarse.X(Ix),Coarse.Y(Iy)); ++Row.Samples; } } - Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99); - Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end()); - Row.Samples=Errors.size(); + FErrorRow Stats;Stats.Lod=Patch.Level;Stats.Valid=true; + FinalizeError(Stats,Accumulator); + Row.Samples=Stats.Samples;Row.P50=Stats.P50;Row.P99=Stats.P99;Row.Maximum=Stats.Maximum; + Row.VerticalP50=Stats.VerticalP50;Row.VerticalP99=Stats.VerticalP99; + Row.VerticalMaximum=Stats.VerticalMaximum; + Row.MaximumNormalDistanceMeters=Stats.MaximumNormalDistanceMeters; + Row.MaximumX=Stats.MaximumX;Row.MaximumY=Stats.MaximumY; + Row.MaximumReferenceZ=Stats.MaximumReferenceZ; + Row.MaximumDisplayedZ=Stats.MaximumDisplayedZ; + Row.MaximumSlopeDegrees=Stats.MaximumSlopeDegrees; + Row.SlopeSamples=Stats.SlopeSamples;Row.SlopeP99=Stats.SlopeP99; + Row.SlopeMaximum=Stats.SlopeMaximum; return Row; } @@ -352,19 +511,52 @@ namespace StandaloneScreenSpaceGate return Distances; } + inline void PrintSlopeTail(const char* Kind,int Lod,const std::array& Counts, + const std::array& P99,const std::array& Maximum) + { + constexpr const char* Labels[4]={"lt30","30to45","45to60","ge60"}; + for(std::size_t Bin=0;Bin(Counts[Bin]),P99[Bin],Maximum[Bin]); + } + + inline void PrintKnownFailureControl(double CellMeters,double DisplayDistanceMeters, + const FViewBasis& View) + { + constexpr double Slope=100.0; + const double LateralMeters=CellMeters/200.0; + const double VerticalMeters=Slope*LateralMeters; + const double NormalLength=std::sqrt(1.0+Slope*Slope); + const double NormalDistance=VerticalMeters/NormalLength; + const std::array Normal={-Slope/NormalLength,0.0,1.0/NormalLength}; + const double ProjectedNormal=std::hypot( + Normal[0]*View.Right[0]+Normal[1]*View.Right[1]+Normal[2]*View.Right[2], + Normal[0]*View.Up[0]+Normal[1]*View.Up[1]+Normal[2]*View.Up[2]); + const double PixelBudgetMeters=DisplayDistanceMeters*OnePixelRadians; + const double VerticalPixels=VerticalMeters/PixelBudgetMeters; + const double NormalPixels=NormalDistance*ProjectedNormal/PixelBudgetMeters; + const bool Passed=VerticalPixels>4.0&&NormalPixels<1.0; + std::printf("[ScreenSpaceKnownFailureControl] case=steep-plane-lateral-offset slope_deg=%.4f cell_m=%.6f lateral_m=%.6f cell_fraction=%.6f vertical_error_m=%.6f vertical_height_px=%.4f closest_normal_m=%.6f projected_normal_px=%.4f status=%s\n", + std::atan(Slope)*180.0/3.14159265358979323846,CellMeters,LateralMeters, + LateralMeters/CellMeters,VerticalMeters,VerticalPixels,NormalDistance,NormalPixels, + Passed?"EXPECTED_VERTICAL_FALSE_POSITIVE":"CONTROL_FAILED"); + } + inline bool Run(const std::array,9>& SampleTiles, FStandaloneStreamingGenerator& Streamer, const VoxelForgeCore::WorldAssemblyManifest& Assembly, const std::array& Distances,std::uint32_t VoxelSizeMicrons,int MaxLod, - const std::array& PixelBudgetsSubvoxels) + const std::array& PixelBudgetsSubvoxels, + double CameraYawDegrees,double CameraPitchDegrees) { const double VoxelMeters=static_cast(VoxelSizeMicrons)/1000000.0; + const FViewBasis View=MakeViewBasis(CameraYawDegrees,CameraPitchDegrees); std::array Budgets{}; std::array ErrorRows{}; std::array PopRows{}; bool GatePass=true; - std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (90deg/2560) lod>=3_limit=1.0px sampling=65x65 skirts=excluded metric=vertical_height_error field_reference=direct-Heightfield2D-roots\n", - OnePixelRadians); + std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (horizontal_fov=90deg,width=2560) lod>=3_limit=1.0px sampling=65x65 skirts=excluded metric=local_surface_normal_projected field_reference=direct-Heightfield2D-roots normal_probe_vox=1 view_yaw_deg=%.3f view_pitch_deg=%.3f\n", + OnePixelRadians,CameraYawDegrees,CameraPitchDegrees); for(int Lod=0;Lod<=MaxLod&&Lod<9;++Lod) { Budgets[Lod]=Distances[Lod]*OnePixelRadians; @@ -372,6 +564,8 @@ namespace StandaloneScreenSpaceGate Lod,Distances[Lod],Budgets[Lod],Budgets[Lod]*100.0, static_cast(PixelBudgetsSubvoxels[Lod]),Lod<3?"exact-2cm":"1px"); } + const double LOD3CellMeters=static_cast(CHUNK_SIZE*(1<<3))*VoxelMeters/64.0; + PrintKnownFailureControl(LOD3CellMeters,Distances[3],View); VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0); for(int Lod=3;Lod<=MaxLod&&Lod<9;++Lod) { @@ -389,32 +583,43 @@ namespace StandaloneScreenSpaceGate const FStandaloneCpuTile* ExactPointer=&ExactTile; const std::span ExactSpan(&ExactPointer,1); const FHeightGrid ExactGrid=Rasterize(ExactSpan,ExactTile.Key,VoxelMeters); - ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,Lod,Budgets[Lod]); + ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,CoarseTile.Key,VoxelMeters, + Budgets[Lod],Assembly,View); const FErrorRow& Error=ErrorRows[Lod]; const bool LodPass=Error.Valid&&Error.Missing==0&&Error.Floating==0 &&Error.Samples>=16&&Error.Maximum<=1.0; GatePass=GatePass&&LodPass; - std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n", + std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xy_m=(%.3f,%.3f) max_slope_deg=%.2f missing=%llu floating=%llu status=%s\n", Lod,CoarseTile.Key.Coord.X,CoarseTile.Key.Coord.Y,CoarseTile.Key.Coord.Z, static_cast(Error.Samples),Error.P50,Error.P99,Error.Maximum, + Error.VerticalP50,Error.VerticalP99,Error.VerticalMaximum, + Error.MaximumNormalDistanceMeters,Error.MaximumX,Error.MaximumY, + Error.MaximumSlopeDegrees, static_cast(Error.Missing),static_cast(Error.Floating), LodPass?"PASS":"REJECT"); + PrintSlopeTail("same_lod",Lod,Error.SlopeSamples,Error.SlopeP99,Error.SlopeMaximum); const FErrorRow FieldError=CompareToExact(CoarseGrid,CoarseTile.Key,VoxelMeters, - Budgets[Lod],Assembly); + Budgets[Lod],Assembly,View); const bool FieldPass=FieldError.Valid&&FieldError.Missing==0&&FieldError.Floating==0 &&FieldError.Samples>=16&&FieldError.Maximum<=1.0; GatePass=GatePass&&FieldPass; - std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n", + std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xyz_m=(%.3f,%.3f,%.3f) max_slope_deg=%.2f missing=%llu floating=%llu status=%s\n", Lod,static_cast(FieldError.Samples),FieldError.P50, - FieldError.P99,FieldError.Maximum,static_cast(FieldError.Missing), + FieldError.P99,FieldError.Maximum,FieldError.VerticalP50,FieldError.VerticalP99, + FieldError.VerticalMaximum,FieldError.MaximumNormalDistanceMeters, + FieldError.MaximumX,FieldError.MaximumY,FieldError.MaximumDisplayedZ, + FieldError.MaximumSlopeDegrees, + static_cast(FieldError.Missing), static_cast(FieldError.Floating),FieldPass?"PASS":"REJECT"); + PrintSlopeTail("field",Lod,FieldError.SlopeSamples,FieldError.SlopeP99, + FieldError.SlopeMaximum); bool FaultControlsPass=true; const FHeightGrid MissingGrid(ExactGrid.Side,ExactGrid.MinimumX,ExactGrid.MaximumX, ExactGrid.MinimumY,ExactGrid.MaximumY); - const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,Lod, - Budgets[Lod]); + const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid, + CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View); const bool MissingRejected=MissingError.Missing>0&&MissingError.Maximum>1.0; FaultControlsPass=FaultControlsPass&&MissingRejected; std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-displayed-surface missing=%llu status=%s\n", @@ -435,8 +640,8 @@ namespace StandaloneScreenSpaceGate const FStandaloneCpuTile* FloatingPointer=&FloatingTile; const std::span FloatingSpan(&FloatingPointer,1); const FHeightGrid FloatingGrid=Rasterize(FloatingSpan,CoarseTile.Key,VoxelMeters); - const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,Lod, - Budgets[Lod]); + const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid, + CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View); const bool FloatingRejected=FloatingError.Floating>0||FloatingError.Maximum>1.0; FaultControlsPass=FaultControlsPass&&FloatingRejected; std::printf("[ScreenSpaceFaultInjection] lod=%d case=2m-floating-surface floating=%llu max_px=%.4f status=%s\n", @@ -461,20 +666,24 @@ namespace StandaloneScreenSpaceGate FinePointers.data(),FinePointers.size()); const FHeightGrid FineGrid=Rasterize(FineSpan,CoarseTile.Key,VoxelMeters); PopRows[Lod]=ComparePop(CoarseGrid,FineGrid,CoarseTile.Key,VoxelMeters, - Budgets[Lod],Assembly); + Budgets[Lod],Assembly,View); const FPopRow& Pop=PopRows[Lod]; const bool PopPass=Pop.Valid&&Pop.Cracks==0&&Pop.Floating==0&&Pop.Maximum<=1.0; GatePass=GatePass&&PopPass; - std::printf("[ScreenSpaceSwap] lod=%d_to_%d samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f cracks=%llu floating=%llu status=%s\n", + std::printf("[ScreenSpaceSwap] lod=%d_to_%d samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xyz_m=(%.3f,%.3f,%.3f) max_slope_deg=%.2f cracks=%llu floating=%llu status=%s\n", Lod,Lod-1,static_cast(Pop.Samples), - Pop.P50,Pop.P99,Pop.Maximum,static_cast(Pop.Cracks), + Pop.P50,Pop.P99,Pop.Maximum,Pop.VerticalP50,Pop.VerticalP99,Pop.VerticalMaximum, + Pop.MaximumNormalDistanceMeters,Pop.MaximumX,Pop.MaximumY, + Pop.MaximumDisplayedZ, + Pop.MaximumSlopeDegrees,static_cast(Pop.Cracks), static_cast(Pop.Floating), PopPass?"PASS":"REJECT"); + PrintSlopeTail("swap",Lod,Pop.SlopeSamples,Pop.SlopeP99,Pop.SlopeMaximum); const FHeightGrid MissingFineGrid(CoarseGrid.Side,CoarseGrid.MinimumX, CoarseGrid.MaximumX,CoarseGrid.MinimumY,CoarseGrid.MaximumY); const FPopRow MissingFineError=ComparePop(CoarseGrid,MissingFineGrid, - CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly); + CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View); const bool CrackRejected=MissingFineError.Cracks>0&&MissingFineError.Maximum>1.0; FaultControlsPass=FaultControlsPass&&CrackRejected; GatePass=GatePass&&FaultControlsPass; @@ -490,8 +699,8 @@ namespace StandaloneScreenSpaceGate const FStandaloneCpuTile* BadPointer=&Bad; const std::span BadSpan(&BadPointer,1); const FHeightGrid BadGrid=Rasterize(BadSpan,Bad.Key,VoxelMeters); - const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,Lod, - Budgets[Lod]); + const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid, + CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View); const bool Rejected=BadError.Missing>0||BadError.Floating>0 ||BadError.Samples<16||BadError.Maximum>1.0; NegativeControlsPass=NegativeControlsPass&&Rejected; diff --git a/Standalone/Source/StreamBenchmarkHeadless.cpp b/Standalone/Source/StreamBenchmarkHeadless.cpp index 8e5a310..ad0ad16 100644 --- a/Standalone/Source/StreamBenchmarkHeadless.cpp +++ b/Standalone/Source/StreamBenchmarkHeadless.cpp @@ -38,7 +38,6 @@ namespace bool ForceGenerate=false; bool MeshCoverage=false; bool ScreenSpaceGate=false; - bool VisualDetailCutoff=false; bool HasSeedOverride=false; int SeedOverride=0; bool HasRenderDistanceOverride=false; @@ -98,7 +97,6 @@ namespace " --auto-surface-camera (place the 27-tile cube on this seed's surface)\n" " --mesh-coverage (report non-empty tiles and indexed triangles)\n" " --screen-space-gate (measure display error and historical negative controls)\n" - " --visual-detail-cutoff (enable the declared far visual octave cutoff)\n" " --render-distance-chunks=N (0 disables the extra far ring)\n" " --tls-probe=N (Linux worker TLS/stack measurement; N=0..12)\n" " --tls-probe-touch=none|surface-world|floating-islands (first-use probe)\n" @@ -114,7 +112,6 @@ namespace else if(arg=="--force-generate")options.ForceGenerate=true; else if(arg=="--mesh-coverage")options.MeshCoverage=true; else if(arg=="--screen-space-gate")options.ScreenSpaceGate=true; - else if(arg=="--visual-detail-cutoff")options.VisualDetailCutoff=true; else if(arg=="--auto-surface-camera")options.AutoSurfaceCamera=true; else if(arg.starts_with("--recipe=")) { @@ -317,5 +314,5 @@ int main(int argc,char** argv) scheduler,options.StreamHash,false,false,false,false,coldStart, options.ProfileColumnOverlap,options.FarHorizonLOD8InteriorStepVoxels, options.MeshCoverage,options.MeshExportPath,options.CameraYawDegrees, - options.CameraPitchDegrees,options.ScreenSpaceGate,options.VisualDetailCutoff); + options.CameraPitchDegrees,options.ScreenSpaceGate); } diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index 1f3fcea..8388ea8 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -137,22 +137,17 @@ namespace StandaloneStreamBenchmark std::chrono::steady_clock::time_point ColdStart,bool bProfileColumnOverlap, int32 FarHorizonLOD8InteriorStepVoxels,bool bReportMeshCoverage=false, const std::filesystem::path& MeshExportPath={},float CameraYawDegrees=0.0f, - float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false, - bool bApplyVisualDetailCutoff=false) + float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false) { using Clock=std::chrono::steady_clock; FStandaloneOwnerWorld& World=*OwnerWorld; const VoxelForgeCore::WorldAssemblyManifest* Assembly=CoreSnapshot.worldAssembly(); std::array ClosestDisplayDistances{}; std::array PixelBudgetsSubvoxels{}; - std::array AppliedPixelBudgetsSubvoxels{}; - if(bScreenSpaceGate||bApplyVisualDetailCutoff) + if(bScreenSpaceGate) { - ClosestDisplayDistances=bScreenSpaceGate - ?StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters( - World,CameraPositionVoxels,World.CoreVoxelSizeMicrons) - :StandaloneScreenSpaceGate::ClipmapClosestDisplayedDistancesMeters( - World,World.CoreVoxelSizeMicrons); + ClosestDisplayDistances=StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters( + World,CameraPositionVoxels,World.CoreVoxelSizeMicrons); const double VoxelMeters=static_cast(World.CoreVoxelSizeMicrons)/1000000.0; for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod) PixelBudgetsSubvoxels[static_cast(Lod)]=static_cast(std::floor( @@ -160,67 +155,6 @@ namespace StandaloneStreamBenchmark *StandaloneScreenSpaceGate::OnePixelRadians/VoxelMeters *VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)); } - AppliedPixelBudgetsSubvoxels=PixelBudgetsSubvoxels; - if(Assembly&&(bScreenSpaceGate||bApplyVisualDetailCutoff)) - { - for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod) - { - const std::int32_t Cells=FMath::Max(1,World.CoarseTileCells); - const std::int32_t Step=FMath::Max(1,(CHUNK_SIZE<(Lod)]; - Probe.sampleStepSubvoxels=static_cast(Step) - *VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; - std::uint64_t TermCount=0,EligibleTermCount=0,PotentialSkippedOctaves=0; - for(const auto& Plan:Assembly->plan.strates) - { - const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,Plan); - for(const auto& Term:Field.visualDetailContract().terms) - { - ++TermCount; - const auto Result=VoxelForgeCore::FindVisualDetailTailCutoffV1(Term,Probe); - if(Result.firstSkippedOctave==0)continue; - ++EligibleTermCount; - PotentialSkippedOctaves+=Term.octaveCount-Result.firstSkippedOctave; - } - } - std::printf("[WorldAssemblerVisualDetailCutoffPlan] lod=%d terms=%llu eligible_terms=%llu potential_skipped_octaves=%llu per_call_budget_divisor=%u sample_step_vox=%d status=%s\n", - Lod,static_cast(TermCount), - static_cast(EligibleTermCount), - static_cast(PotentialSkippedOctaves), - Probe.callBudgetDivisor,Step, - EligibleTermCount==0?"exact-no-eligible-tail":"candidate"); - if(bApplyVisualDetailCutoff&&EligibleTermCount==0) - AppliedPixelBudgetsSubvoxels[static_cast(Lod)]=0; - } - } - if(bScreenSpaceGate&&Assembly) - { - for(const auto& Plan:Assembly->plan.strates) - { - const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,Plan); - for(const auto& Term:Field.visualDetailContract().terms) - for(std::uint32_t Octave=0;Octave(1ull<(Term.maximumDisplacementSubvoxels) - *Term.maximumNoiseAbsQ16/65536.0L - /static_cast(1ull<(Term.stableId),Octave, - static_cast(Wavelength)/256.0,static_cast(Amplitude)); - } - } - for(const auto& Island:Assembly->floatingIslands) - { - const VoxelForgeCore::FloatingIslandFieldV1 Field( - Island.manifest.recipe,Island.manifest.plan); - const auto Contract=Field.visualDetailContract(); - std::printf("[WorldAssemblerVisualDetailContract] recipe=floating-islands strate=%d declared_terms=%zu exact_path=%d\n", - Island.strateIndex,Contract.terms.size(),Contract.terms.empty()?1:0); - } - } const bool bRoundTunnels=World.Generator->bStandaloneResearchRoundTunnels; const bool bEnhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls; VoxelForgeCore::World Shape(CoreSnapshot); @@ -309,12 +243,7 @@ namespace StandaloneStreamBenchmark FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel, bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap, FarHorizonLOD8InteriorStepVoxels); - if(bScreenSpaceGate||bApplyVisualDetailCutoff) - { - Streamer.SetVisualDetailCutoffBudgets(AppliedPixelBudgetsSubvoxels, - bApplyVisualDetailCutoff); - Streamer.SetVisualDetailProfilingEnabled(bScreenSpaceGate); - } + if(bScreenSpaceGate)Streamer.SetMesherStageProfilingEnabled(true); const auto StreamStart=Clock::now(); const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired); if(Update.DesiredTiles!=static_cast(Desired.Tiles.size())) @@ -370,14 +299,11 @@ namespace StandaloneStreamBenchmark std::array ColumnCaptureCompleteByLOD{}; ColumnCaptureCompleteByLOD.fill(true); std::array WorkerSecondsByLOD{}; - std::array,9> DetailOctaveEvaluationsByLOD{}; - std::array,9> DetailOctaveEligibleByLOD{}; - std::array,9> DetailOctaveSkipsByLOD{}; - std::array,9> DetailOctaveMillisecondsByLOD{}; - std::array,9> DetailOctaveEligibleMillisecondsByLOD{}; std::array ColumnEvalMsByLOD{},MarchingExclusiveMsByLOD{}; std::array GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{}; std::array MeshAssemblyMsByLOD{}; + std::array SkirtMsByLOD{}; + std::array SkirtTrianglesByLOD{}; std::array TileCountByLOD{}; for(const FVoxelTileKey& Tile:Desired.Tiles) if(Tile.Level>=0&&Tile.Level(ExpectedTilesByLOD.size())) @@ -425,19 +351,8 @@ namespace StandaloneStreamBenchmark GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds; CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds; MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds; - for(std::size_t Octave=0;Octave<8;++Octave) - { - DetailOctaveEvaluationsByLOD[LOD][Octave]+= - Tile.CoreDiagnostics.visualDetailOctaveEvaluations[Octave]; - DetailOctaveEligibleByLOD[LOD][Octave]+= - Tile.CoreDiagnostics.visualDetailOctaveBudgetEligible[Octave]; - DetailOctaveSkipsByLOD[LOD][Octave]+= - Tile.CoreDiagnostics.visualDetailOctaveSkips[Octave]; - DetailOctaveMillisecondsByLOD[LOD][Octave]+= - Tile.CoreDiagnostics.visualDetailOctaveMilliseconds[Octave]; - DetailOctaveEligibleMillisecondsByLOD[LOD][Octave]+= - Tile.CoreDiagnostics.visualDetailOctaveEligibleMilliseconds[Octave]; - } + SkirtMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesSkirtMilliseconds; + SkirtTrianglesByLOD[LOD]+=Tile.SkirtTriangleCount; if(bScreenSpaceGate&&Assembly&&Tile.Key.Level>=3&&Tile.Key.Level<9 &&!Tile.Vertices.empty() &&StandaloneScreenSpaceGate::TileHasNativeSurface(Tile.Key,*Assembly, @@ -541,7 +456,8 @@ namespace StandaloneStreamBenchmark if(Assembly) bScreenSpaceGatePassing=StandaloneScreenSpaceGate::Run(GateSampleTiles, Streamer,*Assembly,ClosestDisplayDistances,World.CoreVoxelSizeMicrons, - World.MaxClipLevel,PixelBudgetsSubvoxels); + World.MaxClipLevel,PixelBudgetsSubvoxels,CameraYawDegrees, + CameraPitchDegrees); else {std::fprintf(stderr,"[ScreenSpaceGateResult] no-world-assembly status=REJECT\n");bScreenSpaceGatePassing=false;} } @@ -576,19 +492,12 @@ namespace StandaloneStreamBenchmark } for(std::size_t LOD=0;LOD(TileCountByLOD[LOD]),WorkerSecondsByLOD[LOD], ColumnEvalMsByLOD[LOD],MarchingExclusiveMsByLOD[LOD], - GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],MeshAssemblyMsByLOD[LOD]); - if(bScreenSpaceGate) - for(std::size_t Octave=0;Octave<8;++Octave) - std::printf("[WorldAssemblerLODDetailOctave] lod=%zu octave=%zu evaluated=%llu budget_eligible=%llu skipped=%llu eval_ms=%.6f eligible_ms=%.6f\n", - LOD,Octave, - static_cast(DetailOctaveEvaluationsByLOD[LOD][Octave]), - static_cast(DetailOctaveEligibleByLOD[LOD][Octave]), - static_cast(DetailOctaveSkipsByLOD[LOD][Octave]), - DetailOctaveMillisecondsByLOD[LOD][Octave], - DetailOctaveEligibleMillisecondsByLOD[LOD][Octave]); + GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD], + SkirtMsByLOD[LOD],static_cast(SkirtTrianglesByLOD[LOD]), + MeshAssemblyMsByLOD[LOD]); } const double NeighborhoodSeconds=std::chrono::duration(NeighborhoodReady-ColdStart).count(); const double NeighborhoodMeshSeconds=std::chrono::duration(NeighborhoodReady-ShapeReady).count(); diff --git a/Standalone/Source/StreamingWorld.h b/Standalone/Source/StreamingWorld.h index 1f5467c..0391016 100644 --- a/Standalone/Source/StreamingWorld.h +++ b/Standalone/Source/StreamingWorld.h @@ -646,16 +646,14 @@ public: int32 GetWorkerCount()const{return WorkerCount;} uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);} - void SetVisualDetailCutoffBudgets(const std::array& Budgets,bool Apply) - {VisualDetailBudgetSubvoxels=Budgets;bEnableVisualDetailCutoff=true;bApplyVisualDetailCutoff=Apply;} - void SetVisualDetailProfilingEnabled(bool Enabled) - {bProfileVisualDetailOctaves=Enabled;} + void SetMesherStageProfilingEnabled(bool Enabled) + {bProfileMesherStages=Enabled;} FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key, std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1, - bool ForceExactVisualDetail=false) + bool ForceGenerate=false) { FStandaloneTileJob Job;Job.Key=Key;Job.StartedAt=FClock::now();Job.QueuedAt=Job.StartedAt; - return Generate(Job,GlobalOctaveDrop,CellStride,0,ForceExactVisualDetail); + return Generate(Job,GlobalOctaveDrop,CellStride,ForceGenerate); } private: @@ -775,13 +773,12 @@ private: } FStandaloneCpuTile Generate(const FStandaloneTileJob& Job, std::uint32_t DiagnosticGlobalOctaveDrop=0,std::uint32_t DiagnosticCellStride=1, - std::int64_t DiagnosticVisualBudgetSubvoxels=0, - bool ForceExactVisualDetail=false) + bool ForceGenerate=false) { const auto StartTime=Job.StartedAt; FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing; Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt; - if(MeshCache&&!ForceExactVisualDetail) + if(MeshCache&&!ForceGenerate) { if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key)) { @@ -820,19 +817,9 @@ private: Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors; Options.captureColumnCoordinates=bCaptureColumnCoordinates; Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels; - Options.visualDetailPixelBudgetSubvoxels=DiagnosticVisualBudgetSubvoxels; - if(DiagnosticVisualBudgetSubvoxels==0&&bEnableVisualDetailCutoff - &&Level>=3&&Level(VisualDetailBudgetSubvoxels.size())) - Options.visualDetailPixelBudgetSubvoxels= - VisualDetailBudgetSubvoxels[static_cast(Level)]; - Options.visualDetailSampleStepVoxels=FMath::Max(1,Extent/Cells); - Options.profileVisualDetailOctaves=bProfileVisualDetailOctaves&&!ForceExactVisualDetail; - Options.visualDetailCutoffEnabled=bApplyVisualDetailCutoff - &&!ForceExactVisualDetail&&Level>=3 - &&Options.visualDetailPixelBudgetSubvoxels>0; - Options.visualDetailContractVersion=Options.visualDetailCutoffEnabled - ?VoxelForgeCore::CurrentVisualDetailCutoffContractVersion:0; + Options.profileMesherStages=bProfileMesherStages&&!ForceGenerate; Options.diagnosticGlobalOctaveDrop=DiagnosticGlobalOctaveDrop; + Options.diagnosticCellStride=std::max(1,DiagnosticCellStride); Options.diagnosticColumnInterpolationStride= std::max(1,DiagnosticCellStride); std::string Error; @@ -1007,10 +994,7 @@ private: bool bSkipColors=false; bool bCaptureColumnCoordinates=false; int32 Lod8HorizonInteriorStepVoxels=0; - std::array VisualDetailBudgetSubvoxels{}; - bool bEnableVisualDetailCutoff=false; - bool bApplyVisualDetailCutoff=false; - bool bProfileVisualDetailOctaves=false; + bool bProfileMesherStages=false; std::atomic CacheHitCount{0}; mutable std::mutex Mutex; std::condition_variable Wake; diff --git a/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp b/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp index 936ef02..30b7a44 100644 --- a/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp +++ b/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp @@ -1768,6 +1768,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, const int32 GroundSurfaceTriangleCount=GroundTris.Num()/3; const int32 CapSurfaceTriangleCount=CapTris.Num()/3; TArray GroundSkirtFaceMasks,CapSkirtFaceMasks; + const uint64 SkirtStageStartCycles=OutStageTimings?FPlatformTime::Cycles64():0; if (bGenerateSkirts && (GroundTris.Num() + CapTris.Num()) > 0) { const float ExtentCm = (float)(CellsPerAxis * Step) * GeometryVoxelSize; @@ -2007,6 +2008,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, EmitSkirts(GroundTris,GroundSkirtFaceMasks); EmitSkirts(CapTris,CapSkirtFaceMasks); } + if(OutStageTimings) + OutStageTimings->SkirtMilliseconds=static_cast( + FPlatformTime::Cycles64()-SkirtStageStartCycles) + *FPlatformTime::GetSecondsPerCycle64()*1000.0; // F17 — concatène sol PUIS sky-cap : un run d'indices contigu par polygroup (voir // FVoxelMeshData::NumCeilingTriangles). Mêmes triangles qu'avant, seul l'ordre change. @@ -2059,6 +2064,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, AuditInterpolationCycles,AuditInterpolationSamples,SurfaceCellCalls); OutStageTimings->VertexDedupStreamMilliseconds=ScaleSampledCycles( AuditDedupStreamCycles,AuditDedupStreamSamples,SurfaceCellCalls); + OutStageTimings->SkirtMilliseconds=std::max(0.0, + OutStageTimings->SkirtMilliseconds); const double TotalMilliseconds=static_cast( FPlatformTime::Cycles64()-StageMesherStartCycles)*CycleToMs; OutStageTimings->OtherMilliseconds=FMath::Max(0.0,TotalMilliseconds @@ -2066,7 +2073,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, -OutStageTimings->CellClassificationMilliseconds -OutStageTimings->GradientNormalsMilliseconds -OutStageTimings->EdgeInterpolationMilliseconds - -OutStageTimings->VertexDedupStreamMilliseconds); + -OutStageTimings->VertexDedupStreamMilliseconds + -OutStageTimings->SkirtMilliseconds); } return MeshData; diff --git a/Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h b/Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h index 056dcac..1fb81d6 100644 --- a/Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h +++ b/Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h @@ -50,6 +50,7 @@ struct FVoxelMarchingCubesStageTimings double GradientNormalsMilliseconds = 0.0; double EdgeInterpolationMilliseconds = 0.0; double VertexDedupStreamMilliseconds = 0.0; + double SkirtMilliseconds = 0.0; double OtherMilliseconds = 0.0; uint64 GradientFallbackDensityCalls = 0; uint64 EdgeMidpointDensityCalls = 0;