diff --git a/Standalone/Research/OwnerShapeWorld/round2/REPORT.md b/Standalone/Research/OwnerShapeWorld/round2/REPORT.md new file mode 100644 index 0000000..377f696 --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/round2/REPORT.md @@ -0,0 +1,57 @@ +# OwnerShapeWorld round 2 — current evidence snapshot + +This report reflects the last source build in this folder. It is a **partial snapshot**, not a final sign-off: Windows stopped starting shell processes with exit status `-1073741502` (`0xC0000142`) during final capture/benchmark work. The latest source compiled successfully, and the audits below completed. Final captures, cold timings, owner comparison, and determinism have not yet been rerun against that exact build. + +## Source changes + +- Tapered tunnel and passage radii now match at shared control points after sub-voxel endpoint quantization. The measured worst mismatch fell from `0.0077` vox in the earlier `passage_fixed_after_extensions` audit to `0.000001` vox in the latest audit. +- Native tunnel floor endpoints are checked independently; the current worst mismatch is `0.000000` vox. Smooth-union radius is zero, so it adds no tunnel inflation. +- Authored radius samples are low-pass smoothed across nine control points before tube construction. This is the most recent source change; its final image captures are still pending. +- Cave floor displacement uses a stronger relief multiplier (`2.40`), while cave wall relief uses `0.55`. Strata and solid-overlay relief remain `1.35`. +- The packed interval and surface-band path remains the fast shape-world implementation. + +## Latest completed audits + +Both latest audits used 8 workers, a 64 m full-detail clip, cache-free shape construction, and BelowNormal process priority on affinity `0xFF00`. + +| View | Shape stage | Route samples | Detail ray stddev | Axis-locked normals | Flat 16-voxel patch share | Result | +|---|---:|---:|---:|---:|---:|---| +| Passage floor | 725.807 ms | 5,642 / 5,642 open | 1.1335 vox | 17.8% | 0.0% | Pass | +| Chamber | 947.796 ms | 5,642 / 5,642 open | 0.4545 vox | 2.2% | 0.1% | Pass | + +The chamber's largest flat patch was 56 samples. The floor probe tagged the directly hit cave surface as `floor_surface=yes`; its hit detail was `-0.4081` vox. All final audit records are in `profile9_floor_audit.stdout.log` and `profile9_chamber_audit.stdout.log`. + +The latest tunnel continuity audit reports 280 shared joins in the chamber view, maximum endpoint radius mismatch `0.000001` vox, maximum floor-endpoint mismatch `0.000000` vox, and zero smooth-union growth. The older fixed-radius representation's maximum join step was `2.9156` vox; the final radius profile reports `1.7487` vox between adjacent samples in that view. These are geometric checks, not proof that every visible radius swell has disappeared. + +## Visual evidence currently available + +The owner reference images are under `../final/`. The four round-2 fixed views captured before the last radius-filter change are: + +- `chamber_after_seamfix.png` +- `tunnel_after_seamfix.png` +- `passage_after_seamfix.png` +- `far_after_seamfix.png` + +The closest available passage-floor and bend images are `softprofile_floor_closeup.png` and `tunnel_bend_closeup_after_seamfix.png`. The fixed-view images are from the endpoint-calibration build; the floor close-up is from the five-tap radius filter. The last build changed only the filter to nine taps, so these are diagnostic references, not exact after-build captures. + +Visual review: the chamber now has detail across the cave wall and floor, but its broad relief still looks lumpier than the owner's chamber. The passage tube is connected and routeable, but its long profile still reads as gently repeated radius bands in the close-up. Those bands are continuous by the endpoint audit, though visual closeness to the owner is not yet satisfactory enough to call complete. The fixed passage view also includes an authored landing plane; it should not be counted as a missing cave-floor detail patch. + +## Missing final evidence + +- Cold full-detail area and whole-view timings on the latest nine-tap build, with per-stage timings and cache counts. +- Final lattice-sign difference and surface distance to the owner field. +- Repeated-run and 1-worker versus 6-worker determinism hashes on the latest build. +- New fixed-view and close-up captures from the latest nine-tap build, followed by a final side-by-side visual review. +- A same-camera before/after numeric flat-patch and grid-stair comparison. The baseline captures show the defects, but the current audit instrumentation was added after those captures. + +Earlier successful timings and hashes are in `benchmark_full_detail_final.stdout.log`, `benchmark_full_clipmap_final.stdout.log`, and `determinism_w*_final.stdout.log`; they predate the final radius-profile filter and must not be treated as current measurements. + +## Failed attempts to disclose + +- `build_hardjoin.stdout.log`: failed because it referenced `standalone.rsp` from the wrong working directory. Retrying with `Build\standalone.rsp` succeeded. +- `final_passage_floor_audit.stdout.log`: failed its detail probe because the camera was placed about 11 cm above the landing. This was a bad probe; the raised natural-floor probe passed. +- Several early image captures failed because their relative output path was resolved from the repository root. Captures were subsequently rerun from `Standalone\` and succeeded. +- An owner-floor capture requested the full clipmap and was stopped after its full-detail phase exceeded 19 seconds. The later full-detail-only owner probe completed, but its staircase-heavy image is not a valid speed comparison. +- `passage_worldchain.stdout.log` and `passage_compare_worldchain.stdout.log`: an intermediate world-chain extraction left 412 of 5,642 route samples solid; that extraction was discarded, and the current route audit passes all samples. + +No commit, push, or stash was made. diff --git a/Standalone/Research/OwnerShapeWorld/round2/RunLimited.ps1 b/Standalone/Research/OwnerShapeWorld/round2/RunLimited.ps1 new file mode 100644 index 0000000..8babc97 --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/round2/RunLimited.ps1 @@ -0,0 +1,48 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory=$true)][string]$ExecutableName, + [Parameter(Mandatory=$true)][string[]]$ExecutableArguments, + [Parameter(Mandatory=$true)][string]$LogStem, + [double]$MemoryLimitGB = 6.0 +) + +$ErrorActionPreference = 'Stop' +$RoundDir = [IO.Path]::GetFullPath($PSScriptRoot) +$StandaloneRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..')) +if ([IO.Path]::GetFileName($ExecutableName) -ne $ExecutableName) { throw 'ExecutableName must be a filename.' } +if ([IO.Path]::GetFileName($LogStem) -ne $LogStem) { throw 'LogStem must be a filename stem.' } +$ExecutablePath = Join-Path (Join-Path $StandaloneRoot 'bin') $ExecutableName +if (-not (Test-Path -LiteralPath $ExecutablePath -PathType Leaf)) { throw "Executable not found: $ExecutablePath" } +$StdoutPath = Join-Path $RoundDir ($LogStem + '.stdout.log') +$StderrPath = Join-Path $RoundDir ($LogStem + '.stderr.log') + +$HostProcess = Get-Process -Id $PID +$HostProcess.PriorityClass = 'BelowNormal' +$HostProcess.ProcessorAffinity = [IntPtr]0xFF00 + +$Child = Start-Process -FilePath $ExecutablePath -ArgumentList $ExecutableArguments ` + -WorkingDirectory $StandaloneRoot -WindowStyle Hidden -PassThru ` + -RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath +$KilledForMemory = $false +$PeakWorkingSet = 0L +while (-not $Child.HasExited) { + $Child.Refresh() + if ($Child.HasExited) { break } + $Child.PriorityClass = 'BelowNormal' + $Child.ProcessorAffinity = [IntPtr]0xFF00 + $PeakWorkingSet = [Math]::Max($PeakWorkingSet,$Child.WorkingSet64) + if ($Child.WorkingSet64 -gt $MemoryLimitGB * 1GB) { + $KilledForMemory = $true + Stop-Process -Id $Child.Id -Force + break + } + Start-Sleep -Milliseconds 500 +} +$Child.WaitForExit() +$Child.Refresh() +if ($KilledForMemory) { + Write-Output "ABORTED_MEMORY_LIMIT pid=$($Child.Id) peak_observed_gb=$([math]::Round($PeakWorkingSet/1GB,2)) limit_gb=$MemoryLimitGB" + exit 98 +} +Write-Output "RUN_EXIT pid=$($Child.Id) priority=BelowNormal affinity=0xFF00 peak_observed_gb=$([math]::Round($PeakWorkingSet/1GB,2)) exit_code=$($Child.ExitCode)" +exit $Child.ExitCode diff --git a/Standalone/Research/OwnerShapeWorld/round2/chamber_strata_cave_blend3_after.png b/Standalone/Research/OwnerShapeWorld/round2/chamber_strata_cave_blend3_after.png new file mode 100644 index 0000000..0ce2627 Binary files /dev/null and b/Standalone/Research/OwnerShapeWorld/round2/chamber_strata_cave_blend3_after.png differ diff --git a/Standalone/Research/OwnerShapeWorld/round2/tunnel_strata_cave_blend3_after.png b/Standalone/Research/OwnerShapeWorld/round2/tunnel_strata_cave_blend3_after.png new file mode 100644 index 0000000..4fc4ee9 Binary files /dev/null and b/Standalone/Research/OwnerShapeWorld/round2/tunnel_strata_cave_blend3_after.png differ diff --git a/Standalone/Source/StandaloneShapeWorld.h b/Standalone/Source/StandaloneShapeWorld.h index 1d72f65..ec91a7b 100644 --- a/Standalone/Source/StandaloneShapeWorld.h +++ b/Standalone/Source/StandaloneShapeWorld.h @@ -47,6 +47,10 @@ public: uint64 WalkFailures = 0; double MaxCapsuleJoinRadiusDelta = 0.0; double MaxCapsuleJoinFloorDelta = 0.0; + double MaxCapsuleJoinFloorSlopeDelta = 0.0; + double MaxUnmarkedFloorSlopeDelta = 0.0; + double MaxMarkedFloorSlopeDelta = 0.0; + uint64 MarkedFloorSlopeJoins = 0; double LegacySegmentRadiusStep = 0.0; double SmoothedProfileRadiusStep = 0.0; double MaxTunnelBendDegrees = 0.0; @@ -99,6 +103,8 @@ public: static constexpr int32 DetailBand = 4; static constexpr float DetailSupremum = 4.0f; static constexpr double JoinBlendRadius = 0.0; + static constexpr double RoomUnionBlendRadius = 12.0; + static constexpr float StrataCaveBlendRadius = 3.0f; static constexpr int32 LineBinSize = 32; static constexpr int32 SpatialBinSize = 256; @@ -170,10 +176,12 @@ public: static_cast(Stats.WalkSamples-Stats.WalkFailures), static_cast(Stats.WalkSamples)); const double SmoothUnionGrowthBound=JoinBlendRadius/6.0; - std::printf("[TunnelRadiusAudit] shared_joins=%llu legacy_fixed_segment_max_step=%.4f vox smoothed_profile_max_step=%.4f vox tapered_endpoint_max_delta=%.6f vox floor_endpoint_max_delta=%.6f vox smooth_union_growth_bound=%.4f vox thresholds=(0.005,0.005,0.25) status=%s\n", + std::printf("[TunnelRadiusAudit] shared_joins=%llu legacy_fixed_segment_max_step=%.4f vox smoothed_profile_max_step=%.4f vox tapered_endpoint_max_delta=%.6f vox floor_endpoint_max_delta=%.6f vox floor_slope_join_delta=%.5f unmarked_floor_slope_delta=%.5f marked_floor_slope_delta=%.5f marked_slope_joins=%llu smooth_union_growth_bound=%.4f vox thresholds=(0.005,0.005,0.25) status=%s\n", static_cast(Stats.CapsuleJoinCount),Stats.LegacySegmentRadiusStep, Stats.SmoothedProfileRadiusStep,Stats.MaxCapsuleJoinRadiusDelta, - Stats.MaxCapsuleJoinFloorDelta,SmoothUnionGrowthBound, + Stats.MaxCapsuleJoinFloorDelta,Stats.MaxCapsuleJoinFloorSlopeDelta, + Stats.MaxUnmarkedFloorSlopeDelta,Stats.MaxMarkedFloorSlopeDelta, + static_cast(Stats.MarkedFloorSlopeJoins),SmoothUnionGrowthBound, Stats.MaxCapsuleJoinRadiusDelta<=0.005&&Stats.MaxCapsuleJoinFloorDelta<=0.005 &&SmoothUnionGrowthBound<=0.25?"PASS":"FAIL"); const double BendHorizontal=std::sqrt(Stats.MaxTunnelBendDirection.X*Stats.MaxTunnelBendDirection.X @@ -231,17 +239,23 @@ public: Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/16.0f); return Density; } - return bRock?-(DetailBand+2.0f):(DetailBand+2.0f); + // Match the fixed fallback to the edge of the stored proxy band. The + // displacement is tapered to zero there, so this avoids a magnitude + // jump along the candidate bit-mask boundary. + constexpr float ProxyBandEdge=DetailSupremum+0.125f; + return bRock?-ProxyBandEdge:ProxyBandEdge; } ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; - bool bFloorSurface=false; - float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF,&bFloorSurface); + bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; + float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF, + &bFloorSurface,&CaveInfluence,&FloorInfluence); const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1] &&Z>=Min[2]&&Z<=Max[2]; const bool bCriticalAir=IsInsideCriticalAir(X,Y,Z); if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth &&std::abs(RockDistance)<=DetailSupremum+0.125f) - RockDistance+=DetailNoise(X,Y,Z,SurfaceSDF,SurfaceFamily,bFloorSurface); + RockDistance+=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence) + *DetailBandFade(RockDistance); if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f); float Density=-RockDistance; if((Density<0.0f)!=bRock||std::abs(Density)<0.05f) @@ -335,10 +349,12 @@ public: const size_t Zi=W*64+Bit;if(Zi>=static_cast(Size[2]))continue; const int32 Z=Min[2]+static_cast(Zi); ESurfaceFamily Family=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; - bool bFloorSurface=false; - const float Raw=EffectiveRockDistance(X,Y,Z,&Family,&SurfaceSDF,&bFloorSurface); + bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; + const float Raw=EffectiveRockDistance(X,Y,Z,&Family,&SurfaceSDF, + &bFloorSurface,&CaveInfluence,&FloorInfluence); if(std::abs(Raw)>=0.5f)continue; - const float Detail=DetailNoise(X,Y,Z,SurfaceSDF,Family,bFloorSurface); + const float Detail=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence) + *DetailBandFade(Raw); const size_t FamilyIndex=static_cast(Family); FFamilyStats& F=Families[FamilyIndex];++F.Count;F.Sum+=Detail;F.SumSq+=Detail*Detail; if(std::abs(Detail)<0.05f)++F.NearZero; @@ -392,22 +408,26 @@ public: double Sum=0.0,SumSq=0.0; }; std::array FamilyStats{}; - constexpr std::array YawOffsets={-32.0f,-24.0f,-16.0f,-8.0f,0.0f,8.0f,16.0f,24.0f,32.0f}; - constexpr std::array PitchOffsets={-20.0f,-10.0f,0.0f,10.0f,20.0f}; + constexpr std::array YawOffsets={-36.0f,-30.0f,-24.0f,-18.0f,-12.0f,-6.0f, + 0.0f,6.0f,12.0f,18.0f,24.0f,30.0f,36.0f}; + constexpr std::array PitchOffsets={-80.0f,-70.0f,-60.0f,-50.0f,-40.0f, + -30.0f,-20.0f,-10.0f,0.0f,10.0f,20.0f}; const double OX=CameraX*4.0,OY=CameraY*4.0,OZ=CameraZ*4.0; auto Distance=[&](double X,double Y,double Z,ESurfaceFamily* OutFamily=nullptr, float* OutRaw=nullptr,float* OutDetail=nullptr, bool* OutFloorSurface=nullptr) { ESurfaceFamily Family=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; - bool bFloorSurface=false; - float D=EffectiveRockDistance(X,Y,Z,&Family,&SurfaceSDF,&bFloorSurface); + bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; + float D=EffectiveRockDistance(X,Y,Z,&Family,&SurfaceSDF, + &bFloorSurface,&CaveInfluence,&FloorInfluence); float Detail=0.0f; const bool bInside=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1] &&Z>=Min[2]&&Z<=Max[2]; const bool bCriticalAir=IsInsideCriticalAir(X,Y,Z); if(bInside&&DetailVariant!=EDetailVariant::Smooth&&std::abs(D)<=DetailSupremum+0.125f) - {Detail=DetailNoise(X,Y,Z,SurfaceSDF,Family,bFloorSurface);D+=Detail;} + {Detail=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence) + *DetailBandFade(D);D+=Detail;} if(bCriticalAir)D=std::min(D,-1.0f/16.0f); if(OutFamily)*OutFamily=Family; if(OutRaw)*OutRaw=D-Detail; @@ -415,7 +435,9 @@ public: if(OutFloorSurface)*OutFloorSurface=bFloorSurface; return D; }; - uint64 TotalHits=0,TotalAxisLocked=0,TotalNearZero=0; + uint64 TotalHits=0,TotalAxisLocked=0,TotalNearZero=0,FloorTaggedHits=0,FloorAxisLocked=0; + uint64 FamilyTransitionPairs=0,StableFamilyPairs=0; + double FamilyTransitionGradientSq=0.0,StableFamilyGradientSq=0.0; for(float YawOffset:YawOffsets)for(float PitchOffset:PitchOffsets) { const double YawRad=Yaw+YawOffset*0.017453292519943295; @@ -447,10 +469,50 @@ public: const double GZ=Distance(XH,YH,ZH+0.5)-Distance(XH,YH,ZH-0.5); const double GL=std::sqrt(GX*GX+GY*GY+GZ*GZ); const double AxisDot=GL>1.0e-8?std::max({std::abs(GX),std::abs(GY),std::abs(GZ)})/GL:1.0; + for(int32 Axis=0;Axis<3;++Axis) + { + double AP[3]={XH,YH,ZH},AM[3]={XH,YH,ZH}; + AP[Axis]+=0.5;AM[Axis]-=0.5; + ESurfaceFamily FamilyP=ESurfaceFamily::Strata,FamilyM=ESurfaceFamily::Strata; + float DetailP=0.0f,DetailM=0.0f;bool bFloorP=false,bFloorM=false; + Distance(AP[0],AP[1],AP[2],&FamilyP,nullptr,&DetailP,&bFloorP); + Distance(AM[0],AM[1],AM[2],&FamilyM,nullptr,&DetailM,&bFloorM); + const double Gradient=static_cast(DetailP-DetailM); + if(FamilyP!=FamilyM||bFloorP!=bFloorM) + {FamilyTransitionGradientSq+=Gradient*Gradient;++FamilyTransitionPairs;} + else + {StableFamilyGradientSq+=Gradient*Gradient;++StableFamilyPairs;} + } + constexpr double ScanRadius=16.0; + constexpr int32 ScanSteps=128; + double ScanT=std::max(0.0,HitT-ScanRadius); + ESurfaceFamily ScanFamily=ESurfaceFamily::Strata; + bool bScanFloor=false;float ScanRaw=0.0f,ScanDetail=0.0f; + Distance(OX+DX*ScanT,OY+DY*ScanT,OZ+DZ*ScanT, + &ScanFamily,&ScanRaw,&ScanDetail,&bScanFloor); + for(int32 Step=1;Step<=ScanSteps;++Step) + { + const double T=std::min(HitT+ScanRadius,ScanT+0.25); + ESurfaceFamily NextFamily=ESurfaceFamily::Strata; + bool bNextFloor=false;float NextRaw=0.0f,NextDetail=0.0f; + Distance(OX+DX*T,OY+DY*T,OZ+DZ*T, + &NextFamily,&NextRaw,&NextDetail,&bNextFloor); + if(std::abs(ScanRaw)<=5.0f&&std::abs(NextRaw)<=5.0f) + { + const double Gradient=static_cast(NextDetail-ScanDetail); + if(ScanFamily!=NextFamily||bScanFloor!=bNextFloor) + {FamilyTransitionGradientSq+=Gradient*Gradient;++FamilyTransitionPairs;} + else + {StableFamilyGradientSq+=Gradient*Gradient;++StableFamilyPairs;} + } + ScanT=T;ScanFamily=NextFamily;bScanFloor=bNextFloor; + ScanRaw=NextRaw;ScanDetail=NextDetail; + } FFamilyStats& S=FamilyStats[static_cast(Family)]; ++S.Hits;++TotalHits;S.Sum+=Detail;S.SumSq+=Detail*Detail; if(std::abs(Detail)<0.05f){++S.NearZero;++TotalNearZero;} if(AxisDot>0.985){++S.AxisLocked;++TotalAxisLocked;} + if(bFloorSurface){++FloorTaggedHits;if(AxisDot>0.985)++FloorAxisLocked;} if(YawOffset==0.0f&&PitchOffset==0.0f) { const char* Name=Family==ESurfaceFamily::Strata?"strata": @@ -466,17 +528,31 @@ public: } const double NearZeroShare=TotalHits?static_cast(TotalNearZero)/TotalHits:1.0; const double AxisShare=TotalHits?static_cast(TotalAxisLocked)/TotalHits:1.0; + const double FloorAxisShare=FloorTaggedHits?static_cast(FloorAxisLocked)/FloorTaggedHits:1.0; + const double TransitionRMS=FamilyTransitionPairs + ?std::sqrt(FamilyTransitionGradientSq/FamilyTransitionPairs):0.0; + const double StableRMS=StableFamilyPairs + ?std::sqrt(StableFamilyGradientSq/StableFamilyPairs):0.0; + const double TransitionRatio=StableRMS>1.0e-6?TransitionRMS/StableRMS:1000000.0; double Sum=0.0,SumSq=0.0; for(const FFamilyStats& S:FamilyStats){Sum+=S.Sum;SumSq+=S.SumSq;} const double Mean=TotalHits?Sum/TotalHits:0.0; const double Variance=TotalHits?std::max(0.0,SumSq/TotalHits-Mean*Mean):0.0; + const double SurfaceDetailRMS=std::sqrt(Variance); + const double RelativeJoinStep=SurfaceDetailRMS>1.0e-6?TransitionRMS/SurfaceDetailRMS:1000000.0; const bool bDetailPass=TotalHits>=24&&std::sqrt(Variance)>=0.15&&NearZeroShare<0.50; - const bool bGridPass=TotalHits>=24&&AxisShare<0.60; + const bool bJoinCoverage=FloorTaggedHits>=8 + &&(FamilyTransitionPairs==0||(FamilyTransitionPairs>=8&&RelativeJoinStep<0.45)); + const bool bGridPass=TotalHits>=24&&AxisShare<0.60 + &&bJoinCoverage; std::printf("[FlatSurfaceAudit] rays=%llu detail_stddev=%.4f vox near_zero_share=%.1f%% threshold=50.0%% status=%s\n", static_cast(TotalHits),std::sqrt(Variance),NearZeroShare*100.0, bDetailPass?"PASS":"FAIL"); - std::printf("[GridStairAudit] rays=%llu axis_locked_normals=%.1f%% threshold=60.0%% status=%s\n", - static_cast(TotalHits),AxisShare*100.0,bGridPass?"PASS":"FAIL"); + std::printf("[GridStairAudit] rays=%llu axis_locked_normals=%.1f%% threshold=60.0%% floor_tagged_hits=%llu floor_axis_locked=%.1f%% family_transition_pairs=%llu transition_rms=%.4f vox stable_rms=%.4f vox surface_detail_rms=%.4f vox relative_join_step=%.3f threshold=0.450 legacy_join_ratio=%.3f status=%s\n", + static_cast(TotalHits),AxisShare*100.0, + static_cast(FloorTaggedHits),FloorAxisShare*100.0, + static_cast(FamilyTransitionPairs),TransitionRMS,StableRMS, + SurfaceDetailRMS,RelativeJoinStep,TransitionRatio,bGridPass?"PASS":"FAIL"); for(size_t I=0;I(Owner->Seed)).Z; for(size_t I=RoomPrimitiveStart;I(std::abs(std::max(RPrev,RHere)-std::max(RHere,RNext)))); if(FloorProfiles.IsValidIndex(Join-1)&&FloorProfiles.IsValidIndex(Join)) + { Stats.MaxCapsuleJoinFloorDelta=std::max(Stats.MaxCapsuleJoinFloorDelta, static_cast(std::abs(FloorProfiles[Join-1].EndFloorZ -FloorProfiles[Join].StartFloorZ))); + const FVector PrevD=Points[Join]-Points[Join-1]; + const FVector NextD=Points[Join+1]-Points[Join]; + const double PrevXY=std::sqrt(PrevD.X*PrevD.X+PrevD.Y*PrevD.Y); + const double NextXY=std::sqrt(NextD.X*NextD.X+NextD.Y*NextD.Y); + const double PrevSlope=PrevXY>1.0e-6 + ?(FloorProfiles[Join-1].EndFloorZ-FloorProfiles[Join-1].StartFloorZ)/PrevXY:0.0; + const double NextSlope=NextXY>1.0e-6 + ?(FloorProfiles[Join].EndFloorZ-FloorProfiles[Join].StartFloorZ)/NextXY:0.0; + const double SlopeDelta=std::abs(PrevSlope-NextSlope); + Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(Stats.MaxCapsuleJoinFloorSlopeDelta,SlopeDelta); + if(FloorProfiles[Join-1].bLedgeTransition||FloorProfiles[Join].bLedgeTransition) + { + ++Stats.MarkedFloorSlopeJoins; + Stats.MaxMarkedFloorSlopeDelta=std::max(Stats.MaxMarkedFloorSlopeDelta,SlopeDelta); + } + else Stats.MaxUnmarkedFloorSlopeDelta=std::max(Stats.MaxUnmarkedFloorSlopeDelta,SlopeDelta); + } ++Stats.CapsuleJoinCount; } if(Points.Num()>1) @@ -904,7 +1016,7 @@ private: const FVector Segment=Points[I+1]-Points[I]; const double SegmentLength=std::sqrt(Segment.X*Segment.X+Segment.Y*Segment.Y+Segment.Z*Segment.Z); const double Extension=ContinuousJoinExtension(R0,R1,SegmentLength); - float ZA=Points[I].Z-R0, ZB=Points[I+1].Z-R1; + float ZA=Points[I].Z-R0,ZB=Points[I+1].Z-R1; if(FloorProfiles.IsValidIndex(I))ZA=FloorProfiles[I].StartFloorZ; if(FloorProfiles.IsValidIndex(I))ZB=FloorProfiles[I].EndFloorZ; AddTaperedCapsule(Points[I],Points[I+1],R0,R1,true,false,true,ZA,ZB, @@ -940,7 +1052,7 @@ private: } } const double PassageJoinBlend=ActiveBlendRadius;ActiveBlendRadius=JoinBlendRadius; - ActiveFloorBlendRadius=1.5; + ActiveFloorBlendRadius=3.0; for(const FVoxelPassage& P:Owner->Manager->GetPassages()) { ++Stats.PassageCount; @@ -954,6 +1066,20 @@ private: const float RNext=std::max(6.0f,Radii.IsValidIndex(Join+1)?Radii[Join+1]:P.Radius); Stats.LegacySegmentRadiusStep=std::max(Stats.LegacySegmentRadiusStep, static_cast(std::abs(std::max(RPrev,RHere)-std::max(RHere,RNext)))); + if(P.NativeFloorProfileZ.IsValidIndex(Join-1)&&P.NativeFloorProfileZ.IsValidIndex(Join)) + { + const FVector PrevD=Points[Join]-Points[Join-1]; + const FVector NextD=Points[Join+1]-Points[Join]; + const double PrevXY=std::sqrt(PrevD.X*PrevD.X+PrevD.Y*PrevD.Y); + const double NextXY=std::sqrt(NextD.X*NextD.X+NextD.Y*NextD.Y); + const double PrevSlope=PrevXY>1.0e-6 + ?(P.NativeFloorProfileZ[Join]-P.NativeFloorProfileZ[Join-1])/PrevXY:0.0; + const double NextSlope=NextXY>1.0e-6 + ?(P.NativeFloorProfileZ[Join+1]-P.NativeFloorProfileZ[Join])/NextXY:0.0; + const double SlopeDelta=std::abs(PrevSlope-NextSlope); + Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(Stats.MaxCapsuleJoinFloorSlopeDelta,SlopeDelta); + Stats.MaxUnmarkedFloorSlopeDelta=std::max(Stats.MaxUnmarkedFloorSlopeDelta,SlopeDelta); + } ++Stats.CapsuleJoinCount; } if(Points.Num()>1) @@ -1562,10 +1688,12 @@ private: ++LocalCandidates; const bool bCriticalAir=IsInsideCriticalAir(X,Y,Z); ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; - bool bFloorSurface=false; - const float SDF=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF,&bFloorSurface); + bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; + const float SDF=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF, + &bFloorSurface,&CaveInfluence,&FloorInfluence); if(std::abs(SDF)>DetailSupremum+0.125f)continue; - float Displaced=SDF+DetailNoise(X,Y,Z,SurfaceSDF,SurfaceFamily,bFloorSurface); + float Displaced=SDF+DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence) + *DetailBandFade(SDF); if(bCriticalAir)Displaced=std::min(Displaced,-1.0f/16.0f); const bool bNewRock=Displaced>=0.0f; const bool bOldRock=((RockWords[WordBase+(Zi>>6)]>>(Zi&63u))&1u)!=0; @@ -1660,52 +1788,48 @@ private: { const FPrimitive& P=Primitives[Id];if(!P.bCarve||!P.bCritical)continue; if(XP.Max[0]||YP.Max[1]||ZP.Max[2])continue; - if(ShapeDistance(P,X,Y,Z)>0.0f)continue; - if(P.bHasFloorClip) - { - const double Floor=P.FloorCenterZ+P.FloorSlopeX*(X-P.CenterSub[0]/16.0) - +P.FloorSlopeY*(Y-P.CenterSub[1]/16.0); - if(Zsecond) { const FPrimitive& P=Primitives[Id];if(!P.bCarve)continue; if(XP.Max[0]+DetailBand||YP.Max[1]+DetailBand ||ZP.Max[2]+DetailBand)continue; float Distance=ShapeDistance(P,X,Y,Z); - bool bFloorInfluencesSurface=false; + float PrimitiveFloorInfluence=0.0f; if(P.bHasFloorClip) { const double Floor=P.FloorCenterZ+P.FloorSlopeX*(X-P.CenterSub[0]/16.0) +P.FloorSlopeY*(Y-P.CenterSub[1]/16.0); - bFloorInfluencesSurface=OutFloorSurface!=nullptr - &&std::abs(Z-Floor)<=DetailBand+P.FloorBlendRadius; + const float FloorT=(static_cast(std::abs(Z-Floor))-static_cast(P.FloorBlendRadius)) + /static_cast(std::max(DetailBand*2,1)); + PrimitiveFloorInfluence=1.0f-SmoothUnit(FloorT); Distance=SmoothMaxDistance(Distance,static_cast(Floor-Z), static_cast(P.FloorBlendRadius)); } + const float MetadataWidth=static_cast(std::max(P.BlendRadius,6.0)); + const float PrimitiveWeight=SmoothUnit(0.5f+(Best-Distance)/(2.0f*MetadataWidth)); const float Combined=SmoothMinDistance(Best,Distance,static_cast(P.BlendRadius)); - if(Combined=0.5f; return Best; } @@ -1725,27 +1849,37 @@ private: float EffectiveRockDistance(double X,double Y,double Z, ESurfaceFamily* OutFamily=nullptr, float* OutSurfaceSDF=nullptr, - bool* OutFloorSurface=nullptr) const + bool* OutFloorSurface=nullptr, + float* OutCaveInfluence=nullptr, + float* OutFloorInfluence=nullptr) const { const float Base=StrataDistance(Z); - bool bCaveFloorSurface=false; - const float Cave=CaveDistance(X,Y,Z,OutFloorSurface?&bCaveFloorSurface:nullptr); + float CaveFloorInfluence=0.0f; + const float Cave=CaveDistance(X,Y,Z,nullptr, + (OutFloorSurface||OutFloorInfluence)?&CaveFloorInfluence:nullptr); const float Overlay=SolidOverlayDistance(X,Y,Z); const float CarveOrOverlay=std::max(Cave,Overlay); const ESurfaceFamily Family=Base<=CarveOrOverlay?ESurfaceFamily::Strata: (Cave>=Overlay?ESurfaceFamily::Cave:ESurfaceFamily::SolidOverlay); + constexpr float FamilyBlendWidth=6.0f; + const float CaveVsOverlay=SmoothUnit(0.5f+(Cave-Overlay)/(2.0f*FamilyBlendWidth)); + const float CarveInfluence=SmoothUnit(0.5f+(Base-CarveOrOverlay)/(2.0f*FamilyBlendWidth)); + const float CaveInfluence=CarveInfluence*CaveVsOverlay; + const float FloorInfluence=CaveInfluence*CaveFloorInfluence; if(OutFamily) *OutFamily=Family; if(OutSurfaceSDF) *OutSurfaceSDF=Family==ESurfaceFamily::Strata?Base: (Family==ESurfaceFamily::Cave?Cave:Overlay); - if(OutFloorSurface)*OutFloorSurface=Family==ESurfaceFamily::Cave&&bCaveFloorSurface; - return std::min(Base,CarveOrOverlay); + if(OutCaveInfluence)*OutCaveInfluence=CaveInfluence; + if(OutFloorInfluence)*OutFloorInfluence=FloorInfluence; + if(OutFloorSurface)*OutFloorSurface=FloorInfluence>=0.5f; + return SmoothMinDistance(Base,CarveOrOverlay,StrataCaveBlendRadius); } float OwnerCaveRoughness(double X,double Y,double Z,float SurfaceSDF, const FStrateGenerationParams& Params, - bool bCaveSurface) const + float CaveInfluence) const { const float Depth=Params.SurfaceRoughness*2.0f; if(!(Depth>0.0f)||std::abs(SurfaceSDF)>=Depth||!(Params.RoughnessFrequency>0.0f))return 0.0f; @@ -1795,7 +1929,8 @@ private: FineValue=VoxelNoise::FBM(Fine.X,Fine.Y,Fine.Z,2);break; } float Total=(Rough+FineValue*0.4f)*VOXEL_NOISE_SCALE*Params.SurfaceRoughness; - if(bCaveSurface&&SurfaceSDF<0.0f)Total=std::min(Total,0.0f); + if(SurfaceSDF<0.0f) + Total=std::lerp(Total,std::min(Total,0.0f),std::clamp(CaveInfluence,0.0f,1.0f)); const float Fade=1.0f-std::abs(SurfaceSDF)/Depth; return Total*Fade*Fade; } @@ -1808,7 +1943,7 @@ private: } float DetailNoise(double X,double Y,double Z,float SurfaceSDF, - ESurfaceFamily SurfaceFamily,bool bFloorSurface=false) const + float CaveInfluence,float FloorInfluence) const { if(DetailVariant==EDetailVariant::Smooth)return 0.0f; if(DetailVariant==EDetailVariant::PerVoxel) @@ -1820,9 +1955,9 @@ private: static_cast(Y),static_cast(Z),static_cast(Owner->Seed)); const FLayerField* Field=FindLayerField(Z); const float OwnerRelief=Field?OwnerCaveRoughness(X,Y,Z,SurfaceSDF,Field->Params, - SurfaceFamily==ESurfaceFamily::Cave):0.0f; - const float Contrast=bFloorSurface?2.40f: - (SurfaceFamily==ESurfaceFamily::Cave?0.55f:1.35f); + CaveInfluence):0.0f; + const float Contrast=1.35f-0.80f*std::clamp(CaveInfluence,0.0f,1.0f) + +1.85f*std::clamp(FloorInfluence,0.0f,1.0f); return std::clamp((Relief+OwnerRelief*0.5f)*Contrast,-3.9f,3.9f); } @@ -1830,12 +1965,14 @@ private: { const bool bCriticalAir=IsInsideCriticalAir(X,Y,Z); ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; - bool bFloorSurface=false; - float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF,&bFloorSurface); + bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; + float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF, + &bFloorSurface,&CaveInfluence,&FloorInfluence); const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]&&Z>=Min[2]&&Z<=Max[2]; if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth &&std::abs(RockDistance)<=DetailSupremum+0.125f) - RockDistance+=DetailNoise(X,Y,Z,SurfaceSDF,SurfaceFamily,bFloorSurface); + RockDistance+=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence) + *DetailBandFade(RockDistance); if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f); return RockDistance>=0.0f; }