From 1e111cc042fd4ab55090db33c92ee3e00b35c67a Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Wed, 28 Oct 2026 07:33:53 +0100 Subject: [PATCH] diag: bounded passage carve reach rejected (L93); R43 Unreal SW startup notes Explicit carve reach R (diagnostic, default unbounded): R=2.5 m -> 1.56 cm max displacement, 106 deg max normal change; R=4 m -> 0.63 cm but 53 deg normals. Fails the bar; readiness gain small and inconsistent (TN -5%, VS +6-7%). No world change (v214). Fast tier PASS. R43: Unreal SW bReady 2.16 s = serial sign-edge repair (1.94 s, 10.9M corrections; parallelSignEdgeRepair exists but off); ~0.45 s of SerializeResolvedWorld = adapter SW route search. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1 --- .../include/VoxelForgeCore/VoxelForgeCore.h | 4 + Standalone/Core/src/ShapeWorldInternal.h | 94 +++++++++--- Standalone/Core/src/VoxelForgeCore.cpp | 3 +- .../Linux93/R43-unreal-sw-startup-notes.md | 68 +++++++++ .../WorldAssemblerM3/Linux93/REPORT.md | 66 +++++++++ .../Linux93/RunL93BoundedReadiness.py | 139 ++++++++++++++++++ .../Linux93/RunL93CarveReach.py | 135 +++++++++++++++++ Standalone/Source/StreamBenchmarkHeadless.cpp | 5 +- Standalone/Source/StreamBenchmarkPipeline.h | 86 +++++++---- 9 files changed, 545 insertions(+), 55 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Linux93/R43-unreal-sw-startup-notes.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux93/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux93/RunL93BoundedReadiness.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux93/RunL93CarveReach.py diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index ff1cab3..c40b4c4 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -1813,6 +1813,10 @@ namespace VoxelForgeCore // entering passage composition to at least -N voxel units for this tile. // A negative value leaves the process-level diagnostic override in control. double diagnosticPassageSourceFloorVoxels = -1.0; + // Research-only field comparison. Zero keeps the unbounded passage carve; + // a positive value bounds its signed-distance reach in metres. A negative + // value leaves the process-level L93 setting in control. + double diagnosticPassageCarveReachMetres = -1.0; // Research-only: collect tile-drain kernel counters and floating-island cache metrics. // Inactive on ordinary generation so the hot path only pays the option check. bool instrumentTileDrain = false; diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 6e2ea54..2357b62 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -84,6 +84,20 @@ static double VF_L91PassageSourceFloorVoxels() return Value; } +static double VF_L93PassageCarveReachMetres() +{ + static const double Value=[] + { + const char* Raw=std::getenv("VOXELFORGE_L93_PASSAGE_CARVE_REACH_METRES"); + if(!Raw||!*Raw)return 0.0; + char* End=nullptr; + const double Parsed=std::strtod(Raw,&End); + return End!=Raw&&*End=='\0'&&std::isfinite(Parsed)&&Parsed>0.0 + ?Parsed:0.0; + }(); + return Value; +} + // Standalone-only world compiler. It compiles the runtime room/tunnel/passages into // axis-line intervals, composes rock/cave signs into packed Z-column words, then displaces // only the conservative surface band. The copied Unreal generator remains the A/B reference. @@ -241,14 +255,18 @@ public: const std::array& minimumSubvoxels, const std::array& maximumSubvoxels, VoxelForgeCore::TileGenerationStats* profileStats=nullptr,int32 profileLod=0, - double passageSourceFloorVoxels=-1.0) + double passageSourceFloorVoxels=-1.0, + double passageCarveReachMetres=-1.0) :shape_(&shape),minimumSubvoxels_(minimumSubvoxels), maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries), profileStats_(profileStats),profileLod_(profileLod), candidateProfiled_(profileStats!=nullptr&&shape.L90PassageCandidateProgramEnabled), passageSourceFloorVoxels_(std::isfinite(passageSourceFloorVoxels) &&passageSourceFloorVoxels>=0.0?passageSourceFloorVoxels - :VF_L91PassageSourceFloorVoxels()) + :VF_L91PassageSourceFloorVoxels()), + passageCarveReachMetres_(std::isfinite(passageCarveReachMetres) + &&passageCarveReachMetres>=0.0?passageCarveReachMetres + :VF_L93PassageCarveReachMetres()) { if(passageSourceFloorVoxels_>0.0) clampCounters_=std::make_shared(); @@ -360,6 +378,7 @@ public: previous_(ActivePassageTileQueries),profileLod_(source.profileLod_), candidateProfiled_(source.candidateProfiled_), passageSourceFloorVoxels_(source.passageSourceFloorVoxels_), + passageCarveReachMetres_(source.passageCarveReachMetres_), clampCounters_(source.clampCounters_),ownsClampCounters_(false) { ActivePassageTileQueries=this; } ~FScopedPassageTileQueries() @@ -479,6 +498,7 @@ public: {return profileStats_;} bool candidateCensusProfiled() const noexcept{return candidateProfiled_;} double passageSourceFloorVoxels() const noexcept{return passageSourceFloorVoxels_;} + double passageCarveReachMetres() const noexcept{return passageCarveReachMetres_;} void recordPassageSourceClamp(float sourceDensity) const noexcept { if(passageSourceFloorVoxels_<=0.0)return; @@ -556,6 +576,7 @@ public: int32 profileLod_=0; bool candidateProfiled_=false; double passageSourceFloorVoxels_=0.0; + double passageCarveReachMetres_=0.0; mutable std::shared_ptr clampCounters_; bool ownsClampCounters_=true; mutable std::uint64_t l90Queries_=0,l90NoBrickQueries_=0; @@ -1284,6 +1305,23 @@ public: &&passageTileQueries->matches(*this); auto* passageProfile=passageTileScopeMatches ?passageTileQueries->profileStats():nullptr; + const double passageCarveReachMetres=passageTileScopeMatches + ?passageTileQueries->passageCarveReachMetres() + :VF_L93PassageCarveReachMetres(); + const bool passageCarveReachEnabled=passageCarveReachMetres>0.0 + &&std::isfinite(passageCarveReachMetres); + const double passageCarveReachQ=passageCarveReachEnabled + ?passageCarveReachMetres*qPerMetre + :std::numeric_limits::infinity(); + const auto carveSourceReachQ=[&](float source) + { + const double sourceReach=source<0.0f + ?-static_cast(source)*Q+0.24*qPerMetre + :std::max(2.25*qPerMetre,-static_cast(source)*Q + +0.24*qPerMetre); + return passageCarveReachEnabled + ?std::min(sourceReach,passageCarveReachQ):sourceReach; + }; if(passageProfile)++passageProfile->passageComposerQueries; const bool l90CandidateProfiled=passageTileScopeMatches &&passageTileQueries->candidateCensusProfiled(); @@ -1335,13 +1373,10 @@ public: &&!passageTileQueries->hasAnyMouthSupport() &&!passageTileQueries->mayContainUpperMouthSupport())return density; - // On solid samples, the SDF carve can still move the scalar density far outside the - // physical tunnel. The per-passage test below already accounts for this tail using the - // incoming density plus the bounded relief allowance. Apply that same proof before - // entering the passage loop for points outside the scalar influence reach. Columns and - // both mouth-support paths keep their own tile envelopes and therefore disable this - // cut. Optional extra padding is only for research sweeps; zero is the exact bound. - // Invalid values fail open. + // On solid samples, the unbounded SDF carve can move density far outside the tube. + // Bound that source-dependent tail, or cap it at the explicit carve reach. Whole-tile + // rejection is limited to tiles with no route, column, or mouth feature, so the separate + // support and landing paths keep their existing envelopes. Invalid values fail open. static const double PassageBoundPaddingVoxels=[] { constexpr double DefaultPaddingVoxels=0.0; @@ -1358,7 +1393,9 @@ public: &&std::isfinite(PassageBoundPaddingVoxels) &&!passageTileQueries->hasColumnZOverlap() &&!passageTileQueries->hasAnyMouthSupport() - &&!passageTileQueries->mayContainUpperMouthSupport()) + &&!passageTileQueries->mayContainUpperMouthSupport() + &&(!passageCarveReachEnabled + ||!passageTileQueries->hasRouteFeatureOverlap())) { if(passageProfile)++passageProfile->passageTileBoundChecks; bool bAnyPassageWithinBound=false; @@ -1388,8 +1425,11 @@ public: (CarveHigh-Passage.controlPoints.begin())-1); const std::ptrdiff_t CarveEnd=std::min(LowerMouthIndex-1, (CarveLow-Passage.controlPoints.begin())+1); - const double SignedDistanceLimit=-static_cast(density)*Q - +0.24*qPerMetre+ExtraPadding; + const double rawSignedDistanceLimit=-static_cast(density)*Q + +0.24*qPerMetre; + const double SignedDistanceLimit=passageCarveReachEnabled + ?std::min(rawSignedDistanceLimit,passageCarveReachQ)+ExtraPadding + :rawSignedDistanceLimit+ExtraPadding; if(PassageMayAffectDensity(PassageIndex,CarveBegin,CarveEnd, LowerMouthIndex,Constants.mouthSearchBegin,Constants.mouthSearchEnd, Constants.lastIndex,qx,qy,qz,SignedDistanceLimit)) @@ -1686,10 +1726,7 @@ public: const std::ptrdiff_t mouthSearchEnd=passageConstants.mouthSearchEnd; if(l90CandidateProfiled) { - const double signedDistanceLimit=density<0.0f - ?-static_cast(composed)*Q+0.24*qPerMetre - :std::max(2.25*qPerMetre,-static_cast(composed)*Q - +0.24*qPerMetre); + const double signedDistanceLimit=carveSourceReachQ(composed); const bool sourceProofAvailable=L90PassageCandidateProgramValid &&std::isfinite(signedDistanceLimit) &&signedDistanceLimit<=L90PassageCandidateReachMetres*qPerMetre @@ -1704,10 +1741,12 @@ public: } if(density<0.0f&&tilePassageFree) { + // A tile without this passage's route feature cannot contain its separate + // tread/support work, so the carve reach is sufficient for this query skip. if(passageProfile)++passageProfile->passageNegativeBoundChecks; if(!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd, lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz, - -static_cast(composed)*Q+0.24*qPerMetre)) + carveSourceReachQ(composed))) { if(passageProfile)++passageProfile->passageNegativeBoundRejects; continue; @@ -1726,8 +1765,7 @@ public: if(passageProfile)++passageProfile->passagePositiveBoundChecks; if(!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd, lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz, - std::max(2.25*qPerMetre,-static_cast(composed)*Q - +0.24*qPerMetre))) + carveSourceReachQ(composed))) { if(passageProfile)++passageProfile->passagePositiveBoundRejects; continue; @@ -1863,10 +1901,7 @@ public: considerCarveSegment(static_cast(index),3); } if(!std::isfinite(bestSignedDistance))continue; - const double l90SignedDistanceLimit=density<0.0f - ?-static_cast(composed)*Q+0.24*qPerMetre - :std::max(2.25*qPerMetre,-static_cast(composed)*Q - +0.24*qPerMetre); + const double l90SignedDistanceLimit=carveSourceReachQ(composed); if(l90CandidateProfiled&&bestSignedDistance<=l90SignedDistanceLimit &&l90SignedDistanceLimit<=L90PassageCandidateReachMetres*qPerMetre) passageTileQueries->recordL90ProofCheck(0, @@ -2108,6 +2143,17 @@ public: *floorAlignment*floorAlignment*floorBelowWeight*mouthWalkLaneWeight; const float fullyCarvedDensity=std::max(composed, static_cast(-roughSignedDistance/Q)); + double carveReachWeight=1.0; + if(passageCarveReachEnabled) + { + constexpr double ReachBlendWidthMetres=0.5; + const double blendWidthQ=std::min(ReachBlendWidthMetres, + passageCarveReachMetres)*qPerMetre; + const double fade=std::clamp((passageCarveReachQ-bestSignedDistance) + /std::max(1.0,blendWidthQ),0.0,1.0); + if(passageProfile)++passageProfile->passageSmoothstepEvaluations; + carveReachWeight=fade*fade*(3.0-2.0*fade); + } static const bool bL75TraceTnPassage=[] { const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION"); @@ -2115,7 +2161,7 @@ public: }(); const float passageInput=composed; composed=std::lerp(composed,fullyCarvedDensity, - static_cast(floorCarveWeight)); + static_cast(floorCarveWeight*carveReachWeight)); if(bL75TraceTnPassage&&X==-80.0f&&Y==-20.0f&&Z==-3218.0f &&composed!=passageInput) { diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index 4dfb84a..abfc4b7 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -2797,7 +2797,8 @@ namespace VoxelForgeCore const FStandaloneShapeWorld::FScopedPassageTileQueries passageFeatureScope( tileShape,queryMinimum,queryMaximum, options.profilePassageComposer?&out.diagnostics:nullptr,key.lod, - options.diagnosticPassageSourceFloorVoxels); + options.diagnosticPassageSourceFloorVoxels, + options.diagnosticPassageCarveReachMetres); if(options.profilePassageComposer) { out.diagnostics.passageComposerProfiled=true; diff --git a/Standalone/Research/WorldAssemblerM3/Linux93/R43-unreal-sw-startup-notes.md b/Standalone/Research/WorldAssemblerM3/Linux93/R43-unreal-sw-startup-notes.md new file mode 100644 index 0000000..56f26ba --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux93/R43-unreal-sw-startup-notes.md @@ -0,0 +1,68 @@ +## Ranked findings + +1. **[TRACE] The extra ~2 seconds are inside Core’s shape build.** Across the three final runs, SW’s `core_compile_start → core_bready` interval is **2.171, 2.157, and 2.038 s**; FI’s is **0.173, 0.184, and 0.187 s**. The median gap is **1.973 s**. [SW traces](), [FI traces]() + + **[TRACE]** The SW `ShapeStage`’s median `sign_edge_distances` time is **1.942 s**, versus **8.8 ms** for FI. SW repairs **10,864,702** endpoint proxies, with **10,864,427** occupancy corrections; FI records zero endpoint proxies. Both compile **138,188,413 nodes** and use the same assembly, recipe, and plan hashes. [SW shape log](), [FI shape log](), [L92 hashes]() + + **[CODE]** `FStandaloneShapeWorld::Build` runs `ApplyDetailBand`, then `CompleteSignEdgeDistances`; the latter calls `AnalyticDensityAt` for missing edge samples. The parallel repair branch exists, but `parallelSignEdgeRepair` defaults to `false` and Unreal does not set it on this request. [Shape build](), [serial repair](), [request default](), [Unreal request]() + + **[INFERENCE]** The dominant delay is serial sign-edge repair over the SW-start shape region. The different spawn heights support that explanation: SW starts at −200 m, within the SurfaceWorld stratum; FI starts at −737 m. **Falsifier:** an identical SW run showing sign-edge repair below roughly 100 ms, or showing that the repair is not part of `World::compile`, would overturn this attribution. [SW spawn](), [FI spawn]() + +2. **[TRACE] The adapter’s route search is a separate ~0.45 s cost before compile launch.** In the measured SW run, serialization takes **466.957 ms**. The corresponding route logs span about **452 ms** from the failed legacy candidate set to the bounded-mouth candidate result; that fallback visits 78,408 candidates and ends with only three sweep attempts. [SW trace](), [SW route logs]() + + **[CODE]** This runs in `BuildFiniteSurfaceRoute`, called while `BuildSurfaceStratumPlan` assembles the manifest inside `SerializeResolvedWorld`. It finishes before `core_compile_launch`; it cannot explain the launch-to-`bReady` interval. [Route search](), [plan call](), [manifest build](), [compile launch]() + + **[INFERENCE]** The route search explains much of the **shared** ~466–468 ms serialization cost in both FI and SW, but not why SW takes longer than FI after compile starts. The log span is strong attribution, though it is not a per-function timer. **Falsifier:** a direct timer showing the candidate loops take far less than 400 ms, or showing they fall outside the measured exporter interval. + +3. **[CODE][TRACE] The “radius 6 / max clip 6” values are the Settings asset values, not the Core compile settings in these traces.** Core uses `CoreClipRadius=3` and `CoreMaxClipLevel=8`; the adapter writes those values into the snapshot, and the runtime log says `clip_radius=3 max_level=8`. The trace’s `settings_resolved` event reports the separate Settings values 6/6. [Core defaults](), [snapshot override](), [trace Settings values](), [runtime Core values]() + + **[INFERENCE]** Changing the legacy Settings radius to 3 would not fix this Core compile; it already uses 3. **Falsifier:** inspecting the Core snapshot actually consumed by `World` and finding radius 6 / max level 6. + +4. **[CODE][TRACE] Far parents, recipe differences, and long validation do not explain this gap.** Unreal sets `Request.includeFarWorld=false`, and the SW logs report `far_parents=0.000 ms`; FI and SW also log the same assembly/recipe/plan hashes. Passage validation is about 1.2–1.7 ms. [Request](), [far build condition](), [SW shape stages]() + + **[INFERENCE]** Neither another plan path nor a debug/validation pass accounts for the 1.97 s FI/SW gap. **Falsifier:** a run with different plan hashes, nonzero far-parent time, or an unmeasured validation stage above hundreds of milliseconds. + +## Trace arithmetic + +**[TRACE]** These are direct subtractions of `core_compile_start.t_s` from `core_bready.t_s`: + +| Arrival | Repeat 1 | Repeat 2 | Repeat 3 | Median | +|---|---:|---:|---:|---:| +| FI | `.742608 − .569354 = .173254 s` | `.749704 − .565919 = .183785 s` | `.796746 − .609896 = .186850 s` | `.183785 s` | +| SW | `2.780194 − .608768 = 2.171426 s` | `2.737530 − .580978 = 2.156552 s` | `2.616805 − .578886 = 2.037919 s` | `2.156552 s` | + +[FI trace 1](), [FI trace 2](), [FI trace 3](), [SW trace 1](), [SW trace 2](), [SW trace 3]() + +**[TRACE]** The reported `compile_s` values differ by at most about 26 μs because the code samples the duration just before recording the event timestamp. The SW run 1 trace then shows `bReady` at **2.780194 s**, first tile `request` at **2.794163 s**, and scheduler admission at **2.794603 s**. So the ~2.75 s table value is almost entirely the wait for `bReady`; the first per-tile request is about **14 ms** after readiness. [Ready sample](), [SW events]() + +**[TRACE]** The detailed logs match the Core trace: median FI `ShapeStage` is **183.527 ms**, median SW is **2,156.283 ms**; SW’s median sign-edge substage alone is **1,942.160 ms**. **Falsifier:** a trace/log pair with the same run identifiers but materially different `ShapeStage` and `core_bready` durations would break this mapping. [FI stage](), [SW stage]() + +## What `SerializeResolvedWorld` does + +**[CODE]** `BeginPlay` calls `InitializeVoxelForgeCoreWorld` synchronously; that function calls `SerializeResolvedWorld` before converting the result to UTF-8 and calling Core’s `ResolvedWorldSnapshot::FromJson`. [BeginPlay](), [export and parse]() + +**[CODE]** The exporter reflects over Settings and nested Voxel objects with `TFieldIterator`, handles arrays/maps/sets/structs, serializes resolved strata and passages, and synchronously loads terrain-operation assets. It then builds or imports a world assembly, rebuilds the resolved layout, writes canonical manifest JSON, parses that manifest back into an Unreal JSON object, and writes the full snapshot JSON. [Reflection](), [asset load](), [manifest build](), [JSON embed/write]() + +**[TRACE]** L92 measures the whole exporter at **465.6 ms FI / 468.0 ms SW** and `FromJson` at **18.9 / 19.3 ms**. The route-search logs align with about **452 ms** inside the SW exporter window, so generic asset reflection and JSON handling are not supported as the main cause of the 466 ms. Their individual shares were not timed. [L92 timing and attribution note](), [SW route logs]() + +**[CODE]** Core’s public snapshot API currently exposes `FromJson`, getters, and layout helpers; the snapshot’s data constructor is private. A direct handoff is possible with a new typed Core builder, but the adapter would still need to map the resolved Unreal assets into Core-owned data. [Snapshot API]() + +**[INFERENCE]** Direct typed construction could remove JSON encode/decode work, but it would not remove the route search. The measured ceiling for the parser plus the exporter’s non-route remainder is roughly **35 ms gross**; actual net savings may be smaller because a typed builder must still resolve and validate the owner data. **Falsifier:** a typed A/B that saves substantially more than that while generating the same plan would show unmeasured JSON work is larger than the current timing decomposition suggests. + +**[CODE]** Running the existing exporter unchanged off-thread is unsafe: it reads Unreal `UObject` properties and calls `LoadSynchronous`. A split is possible: snapshot UObject-backed values on the game thread, then build the route/manifest from immutable Core inputs on a worker. **[INFERENCE]** That would reduce the game-thread hitch, but it will not reduce first-request wall time unless the work can overlap independent startup or use a precomputed manifest. **Falsifier:** a worker-thread run of the unchanged exporter that is shown safe for all UObject access and measurably reduces first admission without overlap. + +## Smallest changes and measurements + +1. **[INFERENCE] First SW experiment: enable the existing deterministic parallel sign-edge repair.** Set `Request.parallelSignEdgeRepair=true`; Core already has a parallel density evaluation path with sorted, deterministic publication. The current sign-edge stage is **1.94 s**, so that is the hard upper bound. L55 measured a different headless FI workload falling from **535 ms to 47 ms** with the same approach; using that ratio gives a rough SW estimate of **about 1.77 s saved**, not a result for this Unreal case. [Parallel implementation](), [prior L55 result]() + + **[INFERENCE]** Cheapest confirmation: A/B that one request flag at the same SW spawn and worker count; compare existing `sign_edge_distances`, `ShapeStage`, `core_bready`, and output hashes. Keep the serial path if the SW hashes differ or the stage barely moves. **Falsifier:** the paired run saves under roughly 100 ms, or changes the exact SW outputs. + +2. **[INFERENCE] For the separate ~0.45 s exporter cost, use an already generated manifest through the existing override path when a seed/layout-matched artifact is available.** `AttachValidatedWorldAssembly` parses and validates an imported manifest instead of building `BuildSurfaceStratumPlan` and searching candidates; preserving the existing manifest hash preserves the route plan and does not change the passage composer. Expected saving is **about 0.4–0.45 s**, subject to the artifact matching the resolved seed and strata. [Override branch]() + + **[INFERENCE]** Cheapest confirmation: one fixed-seed trace using the identical manifest; check that route-candidate logs disappear, hashes remain identical, and `SerializeResolvedWorld` falls by roughly the route-search span. **Falsifier:** the route search still runs, hashes change, or the exporter falls by much less than 0.4 s. + +3. **[INFERENCE] Treat direct typed handoff as a small cleanup, not the startup fix.** It can target the roughly **19 ms** parse and some residual JSON work, but not the sign-edge repair or route search. **Falsifier:** a direct-builder A/B removes hundreds of milliseconds without changing route planning; that would show the current residual estimate is wrong. + +4. **[TRACE][INFERENCE] Do not tune the legacy 6/6 clip values or raise the compile task priority as the first fix.** Core already runs 3/8, and launch-to-start queue delay is only **0.05–1.59 ms**; priority could still affect CPU progress, but the trace isolates a much larger serial substage. **Falsifier:** a same-input priority A/B materially reduces `sign_edge_distances` or the shape-stage wall time. [Task priority and queue delay]() + +**[TRACE]** The headless L84/L86/L87 shape timings show that the compiler can finish in roughly **0.25–0.4 s** for those runs, but they do not falsify the R43 finding: R43’s SW camera hits the SurfaceWorld region and triggers the measured 10.9M serial sign corrections. **[INFERENCE]** A headless trace with the same request bounds, spawn, and repair mode would be the clean comparison. **Falsifier:** that matched headless run also takes about 2 s in `sign_edge_distances`. [L84](), [L86](), [L87]() \ No newline at end of file diff --git a/Standalone/Research/WorldAssemblerM3/Linux93/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux93/REPORT.md new file mode 100644 index 0000000..50e2944 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux93/REPORT.md @@ -0,0 +1,66 @@ +# Linux93 — explicit passage carve reach + +## R and acceptance bar + +Before measurement, the bar was: **zero sign flips, maximum surface displacement ≤1 cm, maximum normal change ≤2°, and all passage audits pass.** + +I chose **R = 2.5 m beyond the tube surface**, with the final 0.5 m smoothly fading to identity. The code’s local terms are a 2.25 m positive-source reach floor and a 0.24 m wall-relief/source allowance (0.21 m relief plus 0.03 m margin); mouth-floor relief is separately capped at 0.20 m. Wider short-span decks and mouth/transition envelopes are separate paths, retained behind their existing feature guards. R is an experiment, not a proof that those separate envelopes fit inside the carve radius. I also measured R=1.5, 2.0 and 4.0 m. + +The instrument reported `GenerateTile -> ApplyResolvedStrateComposition -> ApplyResolvedStratePassageCarves`, comparing density, mesh-edge crossings and normals. It did not use the L91 source-gradient probe. Each reach covered the full desired LOD0–2 set in S0/FI/TN/S3: 5,312 tiles and 174,736,448 samples per reach. Source stamp: `938a849b053aa7e5bc9bf5fcaf51a73590d408c15bc9d8f0564fec685e68921d`. + +## Effect + +Worst displacement and normal change are across all four strata; moved crossings and pairing failures are totals. Every row had zero sign flips and zero edge-crossing mismatches. + +| R (m) | Moved crossings | Max displacement (cm) | Max normal change | Normal pairing failures | Bar | +|---:|---:|---:|---:|---:|---| +| 1.5 | 4,303 | 85.136 | 144.823° | 65 | Fail | +| 2.0 | 1,910 | 41.106 | 118.485° | 57 | Fail | +| 2.5 | 922 | 1.555 | 106.628° | 57 | Fail | +| 4.0 | 916 | 0.632 | 53.193° | 57 | Fail | + +R=2.5 exceeds the displacement limit and misses the normal limit by a wide margin. R=4 meets the displacement limit but still fails normals. The effect audits completed with zero generation failures and zero invalid comparisons; full same-world passage audits were not run after this gate failed. + +## Census and prototype + +The L90 census was not rerun because the effect bar failed. The requested active-p90 (about 4–6) and fallback (<10%) stops therefore have no L93 measurements; the previous 10 m baseline was p90=13 and 18.28% fallback. The compiled evaluator prototype on `passage/1-2/0` was not started because it depends on a passing census. + +## Bounded-only readiness + +Cold headless runs compared default reach with R=2.5 m, L81 padding 0, and passage-composer profiling. Each mode had one warmup and three alternating measured pairs. Counts below are complete LOD0–2 near sets; times are medians in seconds. + +| Stratum | W8 default → R=2.5 | W12 default → R=2.5 | +|---|---:|---:| +| S0 | 0.975 → 0.897 | 0.976 → 0.929 | +| FI | 2.354 → 2.307 | 2.181 → 2.158 | +| TN | 2.356 → 2.229 | 2.156 → 2.032 | +| S3 | 1.276 → 1.371 | 1.100 → 1.169 | + +All runs completed their near sets: S0 1,408, FI 1,216, TN 1,344, S3 1,344 tiles. The work table sums the four per-stratum median counters. Positive deltas mean more counted work with bounded reach. + +| Work counter | W8 default → R=2.5 (Δ) | W12 default → R=2.5 (Δ) | +|---|---:|---:| +| Tile-bound checks | 21,697,558 → 15,820,579 (−27.1%) | 23,141,444 → 15,820,579 (−31.6%) | +| Tile-bound rejects | 15,393,408 → 15,702,098 (+2.0%) | 16,114,028 → 15,702,098 (−2.6%) | +| Passage iterations skipped by tile bound | 46,180,224 → 47,106,294 (+2.0%) | 48,342,084 → 47,106,294 (−2.6%) | +| Negative-bound checks / rejects | 5,506,163 / 2,500,536 → 3,461,477 / 3,286,103 (checks −37.1%, rejects +31.4%) | 5,705,127 / 2,699,500 → 3,767,727 / 3,592,353 (checks −34.0%, rejects +33.1%) | +| Passage iterations | 33,137,208 → 32,209,659 (−2.8%) | 38,207,001 → 39,438,384 (+3.2%) | +| Carve segment visits | 78,166,733 → 77,908,735 (−0.3%) | 101,322,676 → 106,806,150 (+5.4%) | +| Support segment visits | 31,466,498 → 30,406,353 (−3.4%) | 37,630,363 → 38,810,022 (+3.1%) | +| Mouth route checks | 38,701,878 → 37,198,938 (−3.9%) | 47,097,975 → 47,996,583 (+1.9%) | +| Relief field calls | 56,100,413 → 51,745,464 (−7.8%) | 66,227,624 → 64,386,389 (−2.8%) | + +The skip counters do not show a consistent work reduction: several active-loop counters rise at W12, and S3 readiness regresses at both worker counts. This is not a readiness win. + +## Same-world evidence and disposition + +The bounded candidate is rejected. No candidate peer-hash comparison at W1/W8/W12 or across Clang/GCC, bounded passage-audit suite, mouth/mid-tunnel captures, version bump, or hash repin was done. The evaluator remains version 214 and the default reach remains unbounded. The Clang fast tier on the default path passed: 274 checks, 269 PASS and 5 expected diagnostics, in 285.9 s; its W1/W8/W12 checks matched. Standalone diagnostic builds took 65.49 s (Clang) and 89.39 s (GCC). No Unreal source changed, so no Unreal build was run. + +## Failed and superseded runs + +- The first fast-tier launch stopped before tests because its expected binary did not exist. After building the stamped binaries, the fast tier passed. +- An early readiness parser incorrectly required a composer-profile row for every near tile; the profiler emits rows only for active tiles. The parser was corrected and the final matrix validated complete near-tile coverage. Earlier fixed-5 m support-floor readiness data is superseded and excluded. +- Final effect runs completed but intentionally reported FAIL against the stated effect bar. No run exceeded 30 minutes. +- Build, headless measurement and verifier processes were pinned to CPUs 0–11; headless runs also had the 6 GiB address-space limit. A few read-only status/source/JSON inspection commands during consolidation were not pinned. No measurement or build ran in those commands. + +Logs and machine-readable summaries are under `Saved/Linux/l93/`. diff --git a/Standalone/Research/WorldAssemblerM3/Linux93/RunL93BoundedReadiness.py b/Standalone/Research/WorldAssemblerM3/Linux93/RunL93BoundedReadiness.py new file mode 100644 index 0000000..aa417cf --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux93/RunL93BoundedReadiness.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""L80-style cold near-readiness A/B for the bounded carve and L81 reach skip.""" +from __future__ import annotations + +import argparse +import json +import re +import statistics +import subprocess +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[4] +OUT = ROOT / "Saved/Linux/l93/readiness" +DATA = ROOT / "Standalone/data/owner_world.json" +BINARY = ROOT / "Saved/Linux/l93/bin/VoxelForgeCoreHeadless-clang19" +STRATA = { + "S0": {"id": 0, "camera": (8.0, 16.0, -372.2626953125), "near": 1408}, + "FI": {"id": 1, "camera": (25.8369140625, 3.19921875, -737.0), "near": 1216}, + "TN": {"id": 2, "camera": (19.166015625, -14.2001953125, -753.3740234375), "near": 1344}, + "S3": {"id": 3, "camera": (-20.0625, -5.068359375, -900.353515625), "near": 1344}, +} +MODES = {"unbounded": "0", "bounded": "2.5"} +PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M) +NEAR = re.compile(r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) " + r"cold_ready_s=([0-9.]+)$", re.M) +PROFILE = re.compile(r"^\[PassageComposerTile\] key=\((-?\d+),(-?\d+),(-?\d+),lod(\d+)\) (.*)$", re.M) +FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)") +WORK_FIELDS = ( + "samples", "queries", "passage_iterations", "tile_bound_checks", + "tile_bound_rejects", "tile_bound_skipped_iterations", "negative_bounds", + "negative_rejects", "positive_bounds", "positive_rejects", + "carve_segment_visits", "support_segment_visits", "support_capsule_checks", + "later_bore_checks", "other_capsule_checks", "mouth_route_segment_checks", + "relief_field_calls", "smoothstep_evaluations", +) + + +def parse_fields(line: str) -> dict[str, str]: + return dict(FIELDS.findall(line)) + + +def run(stratum: str, workers: int, mode: str, phase: str, replicate: int) -> dict: + OUT.mkdir(parents=True, exist_ok=True) + spec = STRATA[stratum] + label = f"{stratum.lower()}-w{workers}-{mode}-{phase}{replicate}" + log = OUT / f"{label}.log" + camera = ",".join(f"{value:.12f}" for value in spec["camera"]) + ",0,0" + command = [ + "taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "env", + f"VOXELFORGE_L93_PASSAGE_CARVE_REACH_METRES={MODES[mode]}", + "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s", + str(BINARY), str(DATA), "--world-assembler-prototype", "--stream-benchmark", + "--stream-pipeline", f"--workers={workers}", "--clip-radius=3", + "--max-clip-level=8", "--max-level=8", f"--camera={camera}", + "--profile-lod-work", "--profile-passage-composer", + "--stream-scheduler-policy=3", + ] + start = time.monotonic() + try: + with log.open("w", encoding="utf-8") as stream: + proc = subprocess.run(command, cwd=ROOT, stdout=stream, + stderr=subprocess.STDOUT, check=False, timeout=1750) + except subprocess.TimeoutExpired as exc: + raise RuntimeError(f"readiness run exceeded 1,750 seconds: {log}") from exc + output = log.read_text(encoding="utf-8", errors="replace") + near = NEAR.search(output) + pipeline = PIPELINE.search(output) + if proc.returncode or near is None or pipeline is None: + raise RuntimeError(f"readiness run failed or required output missing: {log}; exit={proc.returncode}") + pipeline_fields = parse_fields(pipeline.group(1)) + by_key: dict[tuple[int, int, int, int], dict[str, int]] = {} + for match in PROFILE.finditer(output): + key = tuple(int(value) for value in match.groups()[:4]) + if key[3] > 2: + continue + fields = parse_fields(match.group(5)) + by_key[key] = {name: int(fields.get(name, "0")) for name in WORK_FIELDS} + work = {name: sum(values[name] for values in by_key.values()) for name in WORK_FIELDS} + expected = spec["near"] + row = { + "stratum": stratum, "workers": workers, "mode": mode, + "phase": phase, "replicate": replicate, "exit_code": proc.returncode, + "wall_s": round(time.monotonic() - start, 3), + "lod02_ready_s": float(near.group(3)), + "near_tiles": f"{near.group(1)}/{near.group(2)}", + "expected_near_tiles": expected, + "profiled_near_tiles": len(by_key), + "unprofiled_near_tiles": max(0, expected - len(by_key)), + "whole_view_ready_s": float(pipeline_fields["whole_view_ready"].removesuffix("s")), + "whole_tiles": int(pipeline_fields["whole_tiles"].split("/")[0]), + "work": work, + "log": str(log.relative_to(ROOT)), + } + print("[L93BoundedReadinessRun] " + json.dumps(row, sort_keys=True), flush=True) + return row + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--stratum", choices=tuple(STRATA), required=True) + parser.add_argument("--workers", choices=(8, 12), type=int, required=True) + args = parser.parse_args() + if not BINARY.is_file() or not DATA.is_file(): + raise SystemExit(f"missing input: {BINARY} or {DATA}") + rows = [] + for mode in MODES: + run(args.stratum, args.workers, mode, "warmup", 0) + orders = (("unbounded", "bounded"), ("bounded", "unbounded"), + ("unbounded", "bounded")) + for replicate, order in enumerate(orders, 1): + for mode in order: + rows.append(run(args.stratum, args.workers, mode, "measured", replicate)) + (OUT / f"runs-partial-{args.stratum.lower()}-w{args.workers}.json").write_text( + json.dumps(rows, indent=2) + "\n", encoding="utf-8") + summary = {"stratum": args.stratum, "workers": args.workers, + "measured_pairs": 3, "runs": rows} + for mode in MODES: + subset = [row for row in rows if row["mode"] == mode] + summary[f"{mode}_lod02_ready_s"] = [row["lod02_ready_s"] for row in subset] + summary[f"{mode}_lod02_ready_median_s"] = statistics.median( + row["lod02_ready_s"] for row in subset) + summary[f"{mode}_whole_ready_s"] = [row["whole_view_ready_s"] for row in subset] + summary[f"{mode}_whole_ready_median_s"] = statistics.median( + row["whole_view_ready_s"] for row in subset) + summary[f"{mode}_work_median"] = { + name: statistics.median(row["work"][name] for row in subset) + for name in WORK_FIELDS} + path = OUT / f"summary-{args.stratum.lower()}-w{args.workers}.json" + path.write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8") + print("[L93BoundedReadinessSummary] " + json.dumps(summary, sort_keys=True), flush=True) + valid = all(row["near_tiles"].split("/") == [str(row["expected_near_tiles"])] * 2 + and 0 < row["profiled_near_tiles"] <= row["expected_near_tiles"] + for row in rows) + return 0 if valid else 14 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux93/RunL93CarveReach.py b/Standalone/Research/WorldAssemblerM3/Linux93/RunL93CarveReach.py new file mode 100644 index 0000000..350d303 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux93/RunL93CarveReach.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Compare bounded passage-carve reaches over each owner's complete near view.""" +from __future__ import annotations + +import json +import argparse +import re +import subprocess +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[4] +OUT = ROOT / "Saved/Linux/l93/effects" +DATA = ROOT / "Standalone/data/owner_world.json" +BINARY = ROOT / "Saved/Linux/l93/bin/VoxelForgeCoreHeadless-clang19" +STRATA = { + "S0": {"camera": (8.0, 16.0, -372.2626953125), "near": 1408}, + "FI": {"camera": (25.8369140625, 3.19921875, -737.0), "near": 1216}, + "TN": {"camera": (19.166015625, -14.2001953125, -753.3740234375), "near": 1344}, + "S3": {"camera": (-20.0625, -5.068359375, -900.353515625), "near": 1344}, +} +REACHES = (1.5, 2.0, 2.5, 4.0) +STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M) +COVERAGE = re.compile(r"^\[L93PassageReachCoverage\] (.*)$", re.M) +SUMMARY = re.compile(r"^\[L93PassageReachSummary\] (.*)$", re.M) +AUDIT = re.compile(r"^\[L93PassageReachAudit\] (.*)$", re.M) +FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)") + + +def parse_fields(line: str) -> dict[str, str]: + return dict(FIELDS.findall(line)) + + +def run(stratum: str) -> dict: + OUT.mkdir(parents=True, exist_ok=True) + spec = STRATA[stratum] + camera = ",".join(f"{value:.12f}" for value in spec["camera"]) + ",0,0" + log = OUT / f"{stratum.lower()}-reach-effects.log" + command = [ + "taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "env", + "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s", + str(BINARY), str(DATA), "--world-assembler-prototype", "--stream-benchmark", + "--stream-pipeline", "--workers=8", "--clip-radius=3", "--max-clip-level=8", + "--max-level=2", f"--camera={camera}", "--audit-passage-carve-reach", + ] + started = time.monotonic() + try: + with log.open("w", encoding="utf-8") as stream: + proc = subprocess.run(command, cwd=ROOT, stdout=stream, + stderr=subprocess.STDOUT, check=False, timeout=1750) + except subprocess.TimeoutExpired as exc: + raise RuntimeError(f"reach effect audit exceeded 1,750 seconds: {log}") from exc + output = log.read_text(encoding="utf-8", errors="replace") + coverage = COVERAGE.search(output) + summaries = [parse_fields(match.group(1)) for match in SUMMARY.finditer(output)] + audit = AUDIT.search(output) + stamp = STAMP.search(output) + expected = spec["near"] + coverage_fields = parse_fields(coverage.group(1)) if coverage else {} + coverage_ok = (int(coverage_fields.get("tiles", "0")) == expected + and int(coverage_fields.get("lod0", "0")) + + int(coverage_fields.get("lod1", "0")) + + int(coverage_fields.get("lod2", "0")) == expected) + rows = [] + for summary in summaries: + reach = float(summary["reach_m"]) + tile_counts = summary["tiles"].split("/") + passed = (len(tile_counts) == 2 and int(tile_counts[0]) == expected + and int(tile_counts[1]) == expected + and summary["status"] == "PASS") + rows.append({ + "reach_m": reach, + "tiles": summary["tiles"], + "samples": int(summary["samples"]), + "changed_samples": int(summary["changed_samples"]), + "sign_flips": int(summary["sign_flips"]), + "edge_crossing_mismatches": int(summary["edge_crossing_mismatches"]), + "moved_crossings": int(summary["moved_crossings"]), + "max_displacement_cm": float(summary["max_displacement_cm"]), + "max_normal_degrees": float(summary["max_normal_degrees"]), + "normal_pairing_failures": int(summary["normal_pairing_failures"]), + "status": "PASS" if passed else "FAIL", + }) + row = { + "stratum": stratum, + "exit_code": proc.returncode, + "wall_s": round(time.monotonic() - started, 3), + "expected_near_tiles": expected, + "coverage": coverage_fields or None, + "coverage_ok": coverage_ok, + "reaches": rows, + "audit": parse_fields(audit.group(1)) if audit else None, + "source_stamp": stamp.group(1) if stamp else None, + "log": str(log.relative_to(ROOT)), + } + (OUT / f"{stratum.lower()}-reach-effects.json").write_text( + json.dumps(row, indent=2) + "\n", encoding="utf-8") + print("[L93CarveReachRun] " + json.dumps(row, sort_keys=True), flush=True) + return row + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--stratum", choices=tuple(STRATA), required=True) + args = parser.parse_args() + if not BINARY.is_file() or not DATA.is_file(): + raise SystemExit(f"missing input: {BINARY} or {DATA}") + rows = [run(args.stratum)] + summaries: dict[str, dict] = {} + for reach in REACHES: + samples = [row for run_row in rows for row in run_row["reaches"] + if row["reach_m"] == reach] + summaries[f"{reach:g}"] = { + "strata": len(samples), + "sign_flips": sum(row["sign_flips"] for row in samples), + "edge_crossing_mismatches": sum(row["edge_crossing_mismatches"] for row in samples), + "max_displacement_cm": max((row["max_displacement_cm"] for row in samples), default=0.0), + "max_normal_degrees": max((row["max_normal_degrees"] for row in samples), default=0.0), + "normal_pairing_failures": sum(row["normal_pairing_failures"] for row in samples), + "changed_samples": sum(row["changed_samples"] for row in samples), + "status": "PASS" if len(samples) == len(rows) + and all(row["status"] == "PASS" for row in samples) else "FAIL", + } + result = {"runs": rows, "reach_summary": summaries} + (OUT / f"summary-{args.stratum.lower()}.json").write_text( + json.dumps(result, indent=2) + "\n", encoding="utf-8") + print("[L93CarveReachSummary] " + json.dumps(result, sort_keys=True), flush=True) + return 0 if all(row["coverage_ok"] and row["source_stamp"] + and row["audit"] and row["audit"].get("status") == "PASS" + for row in rows) and all(item["status"] == "PASS" + for item in summaries.values()) else 14 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Standalone/Source/StreamBenchmarkHeadless.cpp b/Standalone/Source/StreamBenchmarkHeadless.cpp index f46ff00..381cb17 100644 --- a/Standalone/Source/StreamBenchmarkHeadless.cpp +++ b/Standalone/Source/StreamBenchmarkHeadless.cpp @@ -60,6 +60,7 @@ namespace bool AuditBasePageEquivalence=false; bool AuditRouteParity=false; bool AuditPassageSourceClamp=false; + bool AuditPassageCarveReach=false; bool RegionCrossing=false; bool AuditTileShapeReadiness=false; int DiagnosticSlowShapeCompileMs=0; @@ -1462,6 +1463,7 @@ namespace " --audit-base-page-equivalence (hash one tile through both shape routes)\n" " --audit-route-parity (compare page, native-only, exact-query and mesh routes)\n" " --audit-passage-source-clamp (compare passage source floors 2,3,4,6 over all near tiles)\n" + " --audit-passage-carve-reach (compare post-composer reaches 1.5,2,2.5,4 m over all near tiles)\n" " --audit-tile-shape-readiness (run a deliberately mis-marked tile against the native-only guard)\n" " --diagnostic-slow-shape-compile-ms=N (diagnostic overlap control; default 0)\n" " --render-distance-chunks=N (0 disables the extra far ring)\n" @@ -1539,6 +1541,7 @@ namespace else if(arg=="--audit-base-page-equivalence")options.AuditBasePageEquivalence=true; else if(arg=="--audit-route-parity")options.AuditRouteParity=true; else if(arg=="--audit-passage-source-clamp")options.AuditPassageSourceClamp=true; + else if(arg=="--audit-passage-carve-reach")options.AuditPassageCarveReach=true; else if(arg=="--region-crossing")options.RegionCrossing=true; else if(arg=="--audit-tile-shape-readiness")options.AuditTileShapeReadiness=true; else if(arg=="--auto-surface-camera")options.AutoSurfaceCamera=true; @@ -2280,5 +2283,5 @@ int main(int argc,char** argv) options.StreamingSchedulerPolicy,options.MovingCameraTrace, options.MovingCameraSpeedMetersPerSecond,options.MovingCameraDurationSeconds, options.MovingCameraDwellSeconds,options.ColdWorkerTimeline, - options.AuditPassageSourceClamp); + options.AuditPassageSourceClamp,options.AuditPassageCarveReach); } diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index 074170b..9ea4b3a 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -386,9 +386,22 @@ namespace StandaloneStreamBenchmark inline int AuditPassageSourceClamp(VoxelForgeCore::World& Shape, const FStandaloneDesiredTileSet& Desired, const VoxelForgeCore::ResolvedLayoutSnapshotV3* Layout, - std::uint32_t CoreVoxelSizeMicrons) + std::uint32_t CoreVoxelSizeMicrons,bool bAuditCarveReach=false) { - constexpr std::array Floors={2.0,3.0,4.0,6.0}; + constexpr std::array SourceFloors={2.0,3.0,4.0,6.0}; + constexpr std::array CarveReaches={1.5,2.0,2.5,4.0}; + const auto& Candidates=bAuditCarveReach?CarveReaches:SourceFloors; + const char* const valueName=bAuditCarveReach?"reach_m":"floor_vox"; + const char* const coverageTag=bAuditCarveReach + ?"L93PassageReachCoverage":"L91PassageClampCoverage"; + const char* const tileTag=bAuditCarveReach + ?"L93PassageReachTile":"L91PassageClampTile"; + const char* const tileFailureTag=bAuditCarveReach + ?"L93PassageReachTileFailure":"L91PassageClampTileFailure"; + const char* const summaryTag=bAuditCarveReach + ?"L93PassageReachSummary":"L91PassageClampSummary"; + const char* const auditTag=bAuditCarveReach + ?"L93PassageReachAudit":"L91PassageClampAudit"; std::vector keys; std::array expectedByLod{}; for(const FVoxelTileKey& tile:Desired.Tiles) @@ -399,10 +412,11 @@ namespace StandaloneStreamBenchmark } if(keys.empty()) { - std::printf("[L91PassageClampAudit] status=FAIL reason=no_lod0_2_tiles\n"); + std::printf("[%s] status=FAIL reason=no_lod0_2_tiles\n",auditTag); return 14; } - std::printf("[L91PassageClampCoverage] tiles=%zu lod0=%llu lod1=%llu lod2=%llu mode=full_desired_near_set\n", + std::printf("[%s] tiles=%zu lod0=%llu lod1=%llu lod2=%llu mode=full_desired_near_set\n", + coverageTag, keys.size(),static_cast(expectedByLod[0]), static_cast(expectedByLod[1]), static_cast(expectedByLod[2])); @@ -412,6 +426,8 @@ namespace StandaloneStreamBenchmark baseOptions.forceFullDensityCapture=true; baseOptions.skipUvAndColors=true; baseOptions.skipMaterialGroups=true; + baseOptions.diagnosticPassageSourceFloorVoxels=0.0; + baseOptions.diagnosticPassageCarveReachMetres=0.0; VoxelForgeCore::TileGenerationOptions candidateOptions=baseOptions; candidateOptions.profilePassageComposer=true; std::array tileCount{},sampleCount{},changedSamples{},signFlips{}, @@ -429,22 +445,27 @@ namespace StandaloneStreamBenchmark if(!base) { ++generationFailures; - std::printf("[L91PassageClampTileFailure] mode=base key=(%d,%d,%d,lod%d) error=%s\n", - key.coordinate.x,key.coordinate.y,key.coordinate.z,key.lod,error.c_str()); + std::printf("[%s] mode=base key=(%d,%d,%d,lod%d) error=%s\n", + tileFailureTag,key.coordinate.x,key.coordinate.y,key.coordinate.z, + key.lod,error.c_str()); continue; } - GatherPassageSourceGradients(Shape,*base,Layout,gradientStats); - for(std::size_t floorIndex=0;floorIndex(comparison.samples), static_cast(comparison.changedSamples), static_cast(comparison.signFlips), @@ -489,7 +511,7 @@ namespace StandaloneStreamBenchmark } bool acceptedAtAllFloors=generationFailures==0&&invalidComparisons==0 &&keys.size()>0; - for(std::size_t floorIndex=0;floorIndex(tileCount[floorIndex]), + std::printf("[%s] %s=%.2f tiles=%llu/%zu samples=%llu changed_samples=%llu sign_flips=%llu edge_crossing_mismatches=%llu shared_crossings=%llu moved_crossings=%llu max_displacement_cm=%.6g mean_displacement_cm=%.6g changed_mesh_tiles=%llu normal_vertices=%llu changed_normal_vertices=%llu max_normal_degrees=%.6g mean_normal_degrees=%.6g normal_pairing_failures=%llu moved_regions=(wall:%llu,floor:%llu,mouth:%llu,other:%llu) normal_regions=(wall:%llu,floor:%llu,mouth:%llu,other:%llu) status=%s\n", + summaryTag,valueName,Candidates[floorIndex], + static_cast(tileCount[floorIndex]), keys.size(),static_cast(sampleCount[floorIndex]), static_cast(changedSamples[floorIndex]), static_cast(signFlips[floorIndex]), @@ -538,14 +561,18 @@ namespace StandaloneStreamBenchmark std::accumulate(gradientStats.gradientMagnitudes.begin(), gradientStats.gradientMagnitudes.end(),0.0) /gradientStats.gradientMagnitudes.size(); - std::printf("[L91PassageSourceGradient] surface_source_crossings=%llu sampled_near_zero=%llu gradient_samples=%zu step_vox=1 source_abs_limit_vox=2 p50=%.6g p90=%.6g max=%.6g mean=%.6g status=%s\n", - static_cast(gradientStats.candidateCrossings), - static_cast(gradientStats.sourceNearZeroSamples), - gradientStats.gradientMagnitudes.size(),percentile(gradientStats.gradientMagnitudes,0.5), - percentile(gradientStats.gradientMagnitudes,0.9), - gradientStats.gradientMagnitudes.empty()?0.0:gradientStats.gradientMagnitudes.back(), - gradientMean,gradientStats.gradientMagnitudes.size()>=64?"MEASURED":"LOW_SAMPLE"); - std::printf("[L91PassageClampAudit] tiles=%zu generation_failures=%llu invalid_comparisons=%llu status=%s\n", + if(!bAuditCarveReach) + std::printf("[L91PassageSourceGradient] surface_source_crossings=%llu sampled_near_zero=%llu gradient_samples=%zu step_vox=1 source_abs_limit_vox=2 p50=%.6g p90=%.6g max=%.6g mean=%.6g status=%s\n", + static_cast(gradientStats.candidateCrossings), + static_cast(gradientStats.sourceNearZeroSamples), + gradientStats.gradientMagnitudes.size(),percentile(gradientStats.gradientMagnitudes,0.5), + percentile(gradientStats.gradientMagnitudes,0.9), + gradientStats.gradientMagnitudes.empty()?0.0:gradientStats.gradientMagnitudes.back(), + gradientMean,gradientStats.gradientMagnitudes.size()>=64?"MEASURED":"LOW_SAMPLE"); + if(bAuditCarveReach) + std::printf("[L93PassageReachInstrument] path=GenerateTile_to_ApplyResolvedStrateComposition_to_ApplyResolvedStratePassageCarves stage=post_rough_sdf_max blend=0.5m transition=identity_outside_reach support_mouth_deferred=unchanged comparison=paired_density_mesh_edges_normals\n"); + std::printf("[%s] tiles=%zu generation_failures=%llu invalid_comparisons=%llu bar=sign_flips_0_displacement_cm_le_1_normal_degrees_le_2 status=%s\n", + auditTag, keys.size(),static_cast(generationFailures), static_cast(invalidComparisons), acceptedAtAllFloors?"PASS":"FAIL"); @@ -2305,7 +2332,8 @@ namespace StandaloneStreamBenchmark bool bLinux77Profile=false,int32 SchedulerPolicy=-1, bool bMovingCameraTrace=false,double MovingCameraSpeedMetersPerSecond=0.0, double MovingCameraDurationSeconds=12.0,double MovingCameraDwellSeconds=5.0, - bool bColdWorkerTimeline=false,bool bAuditPassageSourceClamp=false) + bool bColdWorkerTimeline=false,bool bAuditPassageSourceClamp=false, + bool bAuditPassageCarveReach=false) { using Clock=std::chrono::steady_clock; const std::clock_t ColdCpuOrigin=std::clock(); @@ -2475,7 +2503,7 @@ namespace StandaloneStreamBenchmark ||bAuditOreVeinYield||bAuditLandmarkReachability||bAuditStrateTransitions ||bAuditResolvedLayoutPassages||bAuditResolvedLayoutBoundaries ||bAuditTileShapeReadiness||bAuditBasePageEquivalence||bAuditRouteParity - ||bAuditPassageSourceClamp; + ||bAuditPassageSourceClamp||bAuditPassageCarveReach; const bool bDeferFarWorld=bIncludeFarWorld&&!bMovingCameraTrace&&!bFarWorldAudit; VoxelForgeCore::ShapeBuildRequest ShapeRequest; ShapeRequest.minVoxel={FineMin.X,FineMin.Y,FineMin.Z}; @@ -2555,11 +2583,11 @@ namespace StandaloneStreamBenchmark Streamer.Stop(); std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2; } - if(bAuditPassageSourceClamp) + if(bAuditPassageSourceClamp||bAuditPassageCarveReach) { Streamer.Stop(); return AuditPassageSourceClamp(Shape,Desired,ResolvedLayout, - World.CoreVoxelSizeMicrons); + World.CoreVoxelSizeMicrons,bAuditPassageCarveReach); } if(bAuditNativeCaves) {