From 63c7e563d06b229719a26299ab80cd59ebc758d0 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Wed, 21 Oct 2026 09:16:12 +0200 Subject: [PATCH] wip(assembler): round L29 -- strate transitions honoured at the bedrock/ceiling interface EVoxelStrateTransition from the upper strate shapes the bedrock(N)/ceiling(N+1) interface in the shared composition layer: Hard flat; Gradient = smooth 2D hash surface (16 m owner width, about 4 m measured undulation); Interleaved = 3D domain-warped fingered overlap within the width. Each band keeps >= 2 Core voxels solid; faces return to the seam near passage mouths. Interface audit over 512 m: 0 hole columns for all modes; wider-than-declared and too-thin controls rejected. All passage, route, proof and validator audits pass; deterministic Clang = GCC. Evaluator 15. Cost +4% SW, +1.3% FI vs L28. Open: ring-like artifact on the FI floor capture; transition look unverified. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX --- ARCHITECTURE.md | 29 ++- CODEMAP.md | 16 +- .../Private/VoxelForgeCoreAdapter.cpp | 20 +- Source/VoxelForge/Public/VoxelStrateTypes.h | 27 ++- .../include/VoxelForgeCore/VoxelForgeCore.h | 25 ++- Standalone/Core/src/ResolvedLayoutInternal.h | 192 +++++++++++++++++- Standalone/Core/src/ShapeWorldInternal.h | 164 ++++++++++++++- .../WorldAssemblerM3/Linux29/REPORT.md | 110 ++++++++++ .../captures/fi-floor-gradient-above.jpg | Bin 0 -> 16388 bytes .../interface-gradient-side-cutaway.jpg | Bin 0 -> 20687 bytes .../captures/interface-hard-side-cutaway.jpg | Bin 0 -> 18303 bytes .../interface-interleaved-side-cutaway.jpg | Bin 0 -> 19476 bytes .../captures/sw-ceiling-gradient-below.jpg | Bin 0 -> 13539 bytes Standalone/Source/StreamBenchmarkHeadless.cpp | 58 +++++- Standalone/Source/StreamBenchmarkPipeline.h | 175 ++++++++++++++++ Standalone/Source/WorldAssemblyPrototype.h | 20 +- 16 files changed, 794 insertions(+), 42 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux29/captures/fi-floor-gradient-above.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-gradient-side-cutaway.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-hard-side-cutaway.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-interleaved-side-cutaway.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux29/captures/sw-ceiling-gradient-below.jpg diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d12f2a8..04633c4 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1883,8 +1883,9 @@ Each record separates the nominal ceiling, content, and bedrock bands. A strict sequence for every strate and requires bedrock(N) to touch the next strate's ceiling(N+1). The two bands remain separately owned. Gradient and Interleaved interfaces may declare interpenetration only inside their explicit transition zone; Hard has zero effective width. The manager's `TransitionBlendChunks` -is expressed in owner-chunk metres. Interleaved's existing two-chunk domain warp extends its possible -fingers beyond the blend width and is recorded separately. Compatibility validation reports current +is expressed in owner-chunk metres. L21 recorded the older Interleaved allowance as blend width plus +warp amplitude; L29 now treats the declared blend width as the complete physical crossing allowance. +Domain warp changes finger placement, but cannot extend overlap beyond that width. Compatibility validation reports current gaps, overlaps, ambiguities, inactive/missing bands, and unhonoured transitions while accepting them; strict validation rejects them. Core dispatches per-strate recipe fields without applying that parameter blend, even though the legacy manager helper implements it. Both band types default diggable, @@ -1943,6 +1944,7 @@ opens a band; the support strip stays below the player capsule. The lower mouth monotone descent from the short route-bound landing tail, whose height may vary with the route. Both route and passage audits sample the 0.34 m by 1.76 m capsule, support probes, and grade; the negative control compiles the same world without passage carving and must detect a blocked capsule. + The L23 shared-field change is `WorldAssemblyEvaluatorVersion=9`. It does not change the L22 bands or any generator's field away from a passage. The L23 report includes exact-query/hash comparisons, the blocked control, captures, and the Windows adapter checklist. §8.10's cache, proof, streaming, @@ -2051,6 +2053,29 @@ proof bounds preserve the L27 output. [Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md) records counts, parity, costs and audit caveats. +### 8.46 Honored bedrock/ceiling transitions — World Assembler L29 + +The upper strate's `EVoxelStrateTransition` controls the shared interface between its bedrock band +and the next strate's ceiling band in the Core composition path. `TransitionBlendChunks` is converted +using the resolved layout chunk width (8 m by default). Hard preserves the flat interface. Gradient +moves the shared surface with a smooth 2D hash field whose peak-to-peak range is at most one quarter +of the declared width. Interleaved uses a seed/interface/coordinate-keyed, domain-warped 3D hash field +to set actual overlap between the facing bands from zero to the declared width. The two faces move +inward by half that overlap; the warp changes finger placement without increasing the crossing +allowance. Both bands keep authored thickness, and their solid intervals touch or overlap in every +XY column. Core does not blend unrelated generator fields. + +Each boundary band keeps at least **two Core voxels** of solid thickness (0.5 m at the default 25 cm +voxel size). Strict validation rejects thinner bands and declared interpenetration beyond the +transition width. The world audit samples a 512 m square XY area, measures actual crossing depth and +minimum band thickness, and sweeps every non-passage column through both bands at half-voxel spacing. +Passage columns are excluded from the hole sweep. Near an interface mouth, the shared face returns to +its nominal height inside the mouth radius plus a 1 m support margin, then fades back to the selected +transition over 2 m so the authored landing floor remains supported. Negative controls reject a +transition one micrometre wider than declared and a band one micrometre below the minimum. See +[`Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md) +for audit measurements, hashes, timings, captures and run history. + ## 9. Multiplayer model (listen-server first, dedicated-friendly) > **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a diff --git a/CODEMAP.md b/CODEMAP.md index 5c22097..cc24c2d 100644 --- a/CODEMAP.md +++ b/CODEMAP.md @@ -167,7 +167,7 @@ remains `0`, and lateral regions remain gated off. | `../../Standalone/Core/src/CoreNamespacePreamble.h` | First include of every Core TU. Forces FP contraction off per compiler (Clang/MSVC/GCC pragmas; unknown compiler = `#error`) and defines `vfcore::fp_contract::selfTest`, which `World::compile` runs before building: a fused multiply-add (e.g. Clang `-ffp-contract=fast`) refuses world generation. The world depends on contraction (Linux2/Linux3 reports), so never remove either. | | `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. | | `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. | -| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, transition and dispatch provenance, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.41. | +| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, honored transition settings, deterministic per-column transition bands, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.42, §8.46. | | `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. | | `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval. | Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. | @@ -1160,7 +1160,7 @@ and [`Standalone/Research/WorldAssemblerM3/Linux18/REPORT.md`](Standalone/Resear | Symbol | Canonical file | Role | |---|---|---| | `PlanResolvedLayoutPassagesV1`, `ResolvedLayoutPassageV1::lowerMouthControlIndex` | `Standalone/Core/src/ResolvedLayoutInternal.h`, public record in `VoxelForgeCore.h` | Plans deterministic fixed-metre passages from adjacent director-resolved strata, binds route departure/arrival mouths, and records the derived split between the monotone descent and route-bound landing tail. | -| `FStandaloneShapeWorld::ApplyResolvedStrateComposition`, `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies boundary bands first, then opens the passage capsule and supported floor along the passage for every native generator. | +| `FStandaloneShapeWorld::ApplyResolvedStrateComposition`, `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies resolved boundary bands first, then opens the passage capsule and supported floor along the passage for every native generator. | | `--audit-resolved-layout-passages` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples passage capsules, floor support, mouth ownership/radius, in-strate route bindings, and a blocked-passage negative control. | The evaluator version is 9. See ARCHITECTURE §8.41 and @@ -1217,6 +1217,18 @@ schema V3 is unchanged. See ARCHITECTURE §8.44 and See ARCHITECTURE §8.45 and [Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md). +### 3.29 Honored bedrock/ceiling transitions — World Assembler L29 + +| Symbol | Canonical file | Role | +|---|---|---| +| `EvaluateResolvedLayoutTransitionBandsV1`, `AuditResolvedStrateTransitions` | Public Core header / `Standalone/Core/src/ResolvedLayoutInternal.h`; `Standalone/Source/StreamBenchmarkPipeline.h` | Resolve the deterministic mode field from seed, interface and coordinates; audit actual crossing depth, minimum band thickness, passage-excluded holes, and strict negative controls. | +| `FStandaloneShapeWorld::ApplyResolvedStrateBoundaryBand` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies Hard, Gradient or Interleaved to both facing bands in the shared Core composition path; tapers locally at passage mouths to preserve landing support. | + +The shared evaluator is version 15. Recipe and resolved-layout schema versions are unchanged. +The Unreal adapter now forwards the authored transition type and chunk width; those Unreal edits +were not compiled here. See ARCHITECTURE §8.46 and +[`Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md). + ## 4. The density pipeline (most-edited hot path) ### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749 diff --git a/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp b/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp index 036d306..e5a05d6 100644 --- a/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp +++ b/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp @@ -1106,13 +1106,21 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings, Item.transitionWidthMicrometres = static_cast(Item.transitionBlendChunks) * Item.managerChunkWidthMicrometres; Item.transitionWarpAmplitudeMicrometres = Item.transitionMode == "Interleaved" - ? 2 * Item.managerChunkWidthMicrometres : 0; - Item.transitionInterpenetrationMicrometres = 0; - Item.transitionHonouredInCore = Slot.Definition->TransitionType == EVoxelStrateTransition::Hard; - Item.transitionFallsBackToHard = Slot.Definition->TransitionType != EVoxelStrateTransition::Hard; + ? std::min(Item.transitionWidthMicrometres / 2, Item.managerChunkWidthMicrometres) : 0; + Item.transitionInterpenetrationMicrometres = Item.transitionMode == "Interleaved" + ? Item.transitionWidthMicrometres + : Item.transitionMode == "Gradient" ? Item.transitionWidthMicrometres / 4 : 0; + Item.transitionFallsBackToHard = Item.transitionMode != "Hard" + && Item.transitionWidthMicrometres <= 0; + Item.transitionHonouredInCore = Item.transitionMode == "Hard" + || !Item.transitionFallsBackToHard; Item.transitionDispatchPath = Item.transitionFallsBackToHard - ? "declared Gradient/Interleaved recorded; Core uses an explicit Hard transition fallback with zero interpenetration" - : "declared Hard transition is applied at this interface"; + ? "declared zero-width transition uses an explicit Hard fallback" + : Item.transitionMode == "Hard" + ? "declared Hard transition preserves the flat shared bedrock/ceiling interface" + : Item.transitionMode == "Interleaved" + ? "shared composition overlaps both facing bands with deterministic domain-warped fingers inside the declared width" + : "shared composition moves both band faces together with the smooth declared-width transition field"; Item.managerLookup = "UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate"; if (Type == ECaveGeneratorType::SurfaceWorld) { diff --git a/Source/VoxelForge/Public/VoxelStrateTypes.h b/Source/VoxelForge/Public/VoxelStrateTypes.h index de3835b..f459f74 100644 --- a/Source/VoxelForge/Public/VoxelStrateTypes.h +++ b/Source/VoxelForge/Public/VoxelStrateTypes.h @@ -201,15 +201,19 @@ enum class ECaveGeneratorType : uint8 }; /** - * EVoxelStrateTransition — How two adjacent strates blend at their shared boundary. + * EVoxelStrateTransition — How two adjacent strates meet at their shared boundary. * * Each strate boundary can use a different transition style. The transition type * is defined on the UPPER strate's definition and controls the boundary between * itself and the strate below it. * - * Gradient: Smooth linear interpolation of generation params across TransitionBlendChunks. - * This is the classic approach — no seam visible, but also no dramatic - * geological feature at the boundary. Best for similar-looking strates. + * In the Core composition path, TransitionBlendChunks defines the full permitted + * crossing depth between the upper bedrock and lower ceiling bands. Each band keeps + * its authored thickness and the solid bands always overlap or touch. The legacy manager + * may also blend generation parameters for Gradient; Core does not blend unrelated fields. + * + * Gradient: A low-amplitude smooth height field moves the shared interface and both + * content-facing band surfaces. Best for gentle geological variation. * * Hard: No blending at all. The strate's params apply right up to the boundary, * then instantly switch to the neighbor's params. The abrupt change in @@ -217,26 +221,21 @@ enum class ECaveGeneratorType : uint8 * discontinuity at the boundary. Best for strates that should feel like * distinct geological layers with a visible dividing line. * - * Interleaved: The boundary position is warped by a 3D noise field, so "fingers" - * of one strate reach into the other. Some XY columns transition early, - * others late, creating an irregular, interlocking boundary. The warp - * amplitude is ~2 chunks, and Z frequency is lower than XY to produce - * horizontal finger-like intrusions. Best for strates that should feel - * like they grew into each other over geological time. + * Interleaved: A deterministic 3D domain-warped hash field sets the overlap depth per XY + * column from zero to the declared width, creating irregular fingers. + * Best for strates that should feel grown together. */ UENUM(BlueprintType) enum class EVoxelStrateTransition : uint8 { - // Smooth linear interpolation of all generation params across the blend zone. - // No visible boundary — params change gradually over TransitionBlendChunks distance. + // Smoothly undulates the shared bedrock/ceiling interface in the Core composition path. Gradient UMETA(DisplayName = "Gradient (smooth blend)"), // No blending — params switch instantly at the boundary. // Creates natural cliffs, ledges, or material discontinuities. Hard UMETA(DisplayName = "Hard (sharp boundary with cliff)"), - // 3D noise warps the boundary position per XY column. - // Creates finger-like intrusions of one strate into the other. + // A 3D domain-warped hash field creates finger-like intrusions within the declared width. Interleaved UMETA(DisplayName = "Interleaved (domain-warped fingers)") }; diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 23efbbc..4e49fa6 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -250,7 +250,7 @@ namespace VoxelForgeCore inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256; inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2; // Version 14 adds shared passage columns for long native open-air crossings. - inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 14; + inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 15; enum class FieldChannel : std::uint32_t { @@ -639,6 +639,18 @@ namespace VoxelForgeCore std::vector anchors; }; + /** Per-column resolved bands for one adjacent bedrock/ceiling interface. + * crossingDepthMicrometres is the actual overlap depth between the two facing bands. + */ + struct ResolvedLayoutTransitionBandsV1 + { + std::int64_t crossingDepthMicrometres = 0; + std::int64_t upperBedrockBottomMicrometres = 0; + std::int64_t upperBedrockTopMicrometres = 0; + std::int64_t lowerCeilingBottomMicrometres = 0; + std::int64_t lowerCeilingTopMicrometres = 0; + }; + struct ResolvedLayoutIssueV2 { std::string code; @@ -730,6 +742,17 @@ namespace VoxelForgeCore std::string_view json, std::string& error); }; + /** + * Resolve the deterministic band faces at one XY column. Gradient shares one smooth + * displaced face; Interleaved moves the faces inward by a total crossing no larger than + * the declared width. Both bands retain authored thickness and touch or overlap, so no + * air gap can form. + */ + VFCORE_API ResolvedLayoutTransitionBandsV1 EvaluateResolvedLayoutTransitionBandsV1( + const ResolvedLayoutStratumV2& upper, const ResolvedLayoutStratumV2& lower, + std::int32_t worldSeed, std::int64_t layoutChunkSizeMicrometres, + std::int64_t xMicrometres, std::int64_t yMicrometres); + struct ResolvedLayoutValidatorAuditV3 { std::uint32_t controlsRun = 0; diff --git a/Standalone/Core/src/ResolvedLayoutInternal.h b/Standalone/Core/src/ResolvedLayoutInternal.h index 6cf1504..bcdb368 100644 --- a/Standalone/Core/src/ResolvedLayoutInternal.h +++ b/Standalone/Core/src/ResolvedLayoutInternal.h @@ -13,6 +13,97 @@ namespace VoxelForgeCore { namespace { + constexpr std::int64_t TransitionQ16 = 1ll << 16; + + void TransitionHashWord(std::uint64_t& hash, std::uint64_t word) + { + for (int byte = 0; byte < 8; ++byte) + { + hash ^= static_cast(word >> (byte * 8)); + hash *= 1099511628211ull; + } + } + + std::uint64_t TransitionHash(std::int32_t seed, std::int32_t interfaceIndex, + std::int64_t x, std::int64_t y, std::int64_t z, std::uint32_t salt) + { + std::uint64_t hash = 14695981039346656037ull; + TransitionHashWord(hash, static_cast(seed)); + TransitionHashWord(hash, static_cast(interfaceIndex)); + TransitionHashWord(hash, static_cast(x)); + TransitionHashWord(hash, static_cast(y)); + TransitionHashWord(hash, static_cast(z)); + TransitionHashWord(hash, salt); + hash ^= hash >> 30; hash *= 0xbf58476d1ce4e5b9ull; + hash ^= hash >> 27; hash *= 0x94d049bb133111ebull; + hash ^= hash >> 31; + return hash; + } + + std::int64_t TransitionNoiseValue(std::int32_t seed, std::int32_t interfaceIndex, + std::int64_t x, std::int64_t y, std::int64_t z, std::uint32_t salt) + { + const std::uint32_t bits = static_cast( + TransitionHash(seed, interfaceIndex, x, y, z, salt) >> 32); + return static_cast(bits >> 15) - TransitionQ16; + } + + std::int64_t TransitionFloorDiv(std::int64_t value, std::int64_t divisor) + { + const std::int64_t quotient = value / divisor; + const std::int64_t remainder = value % divisor; + return quotient - (remainder < 0 ? 1 : 0); + } + + std::int64_t TransitionFade(std::int64_t valueQ16) + { + valueQ16 = std::clamp(valueQ16, std::int64_t{0}, TransitionQ16); + const std::int64_t square = (valueQ16 * valueQ16) / TransitionQ16; + return (square * (3 * TransitionQ16 - 2 * valueQ16)) / TransitionQ16; + } + + std::int64_t TransitionLerp(std::int64_t a, std::int64_t b, std::int64_t tQ16) + { return a + ((b - a) * tQ16) / TransitionQ16; } + + std::int64_t TransitionValueNoise2D(std::int32_t seed, std::int32_t interfaceIndex, + std::int64_t x, std::int64_t y, std::int64_t cell, std::uint32_t salt) + { + const std::int64_t ix = TransitionFloorDiv(x, cell), iy = TransitionFloorDiv(y, cell); + const std::int64_t tx = TransitionFade(((x - ix * cell) * TransitionQ16) / cell); + const std::int64_t ty = TransitionFade(((y - iy * cell) * TransitionQ16) / cell); + const auto at = [&](std::int64_t dx, std::int64_t dy) + { return TransitionNoiseValue(seed, interfaceIndex, ix + dx, iy + dy, 0, salt); }; + return TransitionLerp(TransitionLerp(at(0, 0), at(1, 0), tx), + TransitionLerp(at(0, 1), at(1, 1), tx), ty); + } + + std::int64_t TransitionValueNoise3D(std::int32_t seed, std::int32_t interfaceIndex, + std::int64_t x, std::int64_t y, std::int64_t z, std::int64_t cell, + std::uint32_t salt) + { + const std::int64_t ix = TransitionFloorDiv(x, cell), iy = TransitionFloorDiv(y, cell); + const std::int64_t iz = TransitionFloorDiv(z, cell); + const std::int64_t tx = TransitionFade(((x - ix * cell) * TransitionQ16) / cell); + const std::int64_t ty = TransitionFade(((y - iy * cell) * TransitionQ16) / cell); + const std::int64_t tz = TransitionFade(((z - iz * cell) * TransitionQ16) / cell); + const auto at = [&](std::int64_t dx, std::int64_t dy, std::int64_t dz) + { return TransitionNoiseValue(seed, interfaceIndex, ix + dx, iy + dy, iz + dz, salt); }; + const std::int64_t z0 = TransitionLerp( + TransitionLerp(at(0, 0, 0), at(1, 0, 0), tx), + TransitionLerp(at(0, 1, 0), at(1, 1, 0), tx), ty); + const std::int64_t z1 = TransitionLerp( + TransitionLerp(at(0, 0, 1), at(1, 0, 1), tx), + TransitionLerp(at(0, 1, 1), at(1, 1, 1), tx), ty); + return TransitionLerp(z0, z1, tz); + } + + std::int64_t LayoutTransitionLimit(const ResolvedLayoutStratumV2& stratum) + { + if (stratum.transitionMode == "Hard" || stratum.transitionFallsBackToHard) + return 0; + return std::max(0, stratum.transitionWidthMicrometres); + } + std::string LayoutEscape(std::string_view value) { std::string out="\""; @@ -41,12 +132,6 @@ namespace } std::string LayoutHex(std::uint64_t value) {std::ostringstream out;out<(0, upperThickness); + result.lowerCeilingBottomMicrometres = seam - std::max(0, lowerThickness); + result.lowerCeilingTopMicrometres = seam; + + const std::int64_t width = upper.transitionFallsBackToHard + ? 0 : std::max(0, upper.transitionWidthMicrometres); + if (upper.transitionMode == "Hard" || width == 0 + || (upper.transitionMode != "Gradient" && upper.transitionMode != "Interleaved") + || !upper.bedrock.presentInCurrentField || !lower.ceiling.presentInCurrentField + || upperThickness <= 0 || lowerThickness <= 0) + return result; + + if (width <= 0) return result; + + std::int64_t noise = 0; + if (upper.transitionMode == "Gradient") + { + const std::int64_t wavelength = std::max( + 1000000, std::max(1, layoutChunkSizeMicrometres) * 4); + noise = TransitionValueNoise2D(worldSeed, upper.strateIndex, + xMicrometres, yMicrometres, wavelength, 0x47524144u); + } + else + { + // A face crossing wider than the sum of the authored bands would separate + // their far edges and leave an air slab. Limit the physical crossing to the + // smaller of the declared transition width and the combined band thickness. + const std::int64_t maximumContinuousCrossing=upperThickness+lowerThickness; + const std::int64_t crossingLimit=std::min(width,maximumContinuousCrossing); + const std::int64_t cell = std::max(1000000, + std::max(1, layoutChunkSizeMicrometres) * 2); + const std::int64_t warp = std::min(crossingLimit / 2, cell / 2); + const std::int64_t warpX = (TransitionValueNoise3D(worldSeed, upper.strateIndex, + xMicrometres, yMicrometres, seam, cell, 0x49574158u) * warp) / TransitionQ16; + const std::int64_t warpY = (TransitionValueNoise3D(worldSeed, upper.strateIndex, + xMicrometres, yMicrometres, seam, cell, 0x49574159u) * warp) / TransitionQ16; + const std::int64_t warpZ = (TransitionValueNoise3D(worldSeed, upper.strateIndex, + xMicrometres, yMicrometres, seam, cell, 0x4957415au) * warp) / TransitionQ16; + noise = TransitionValueNoise3D(worldSeed, upper.strateIndex, + xMicrometres + warpX, yMicrometres + warpY, seam + warpZ, + cell, 0x494e544cu); + + // Interleaved means physical crossing: the two faces enter each other's + // bands by a total depth in [0, declared width]. Split that overlap across + // both faces so neither stratum owns the entire displacement. Since both + // resulting intervals are solid and overlap, the transition cannot open a gap. + const std::int64_t overlap = ((noise + TransitionQ16) * crossingLimit) + / (2 * TransitionQ16); + const std::int64_t upperInset = overlap / 2; + const std::int64_t lowerInset = overlap - upperInset; + result.crossingDepthMicrometres = overlap; + result.upperBedrockBottomMicrometres = seam - upperInset; + result.upperBedrockTopMicrometres = result.upperBedrockBottomMicrometres + upperThickness; + result.lowerCeilingTopMicrometres = seam + lowerInset; + result.lowerCeilingBottomMicrometres = result.lowerCeilingTopMicrometres - lowerThickness; + return result; + } + + const std::int64_t displacement = (noise * (width / 8)) / TransitionQ16; + const std::int64_t interfaceZ = seam + displacement; + result.upperBedrockBottomMicrometres = interfaceZ; + result.upperBedrockTopMicrometres = interfaceZ + upperThickness; + result.lowerCeilingBottomMicrometres = interfaceZ - lowerThickness; + result.lowerCeilingTopMicrometres = interfaceZ; + return result; +} + std::string ResolvedLayoutSnapshotV3::ToJson() const { const std::string body=LayoutBodyJson(*this); @@ -216,7 +379,8 @@ bool ResolvedLayoutSnapshotV3::finalize(std::string& error) { error.clear(); if(schemaVersion!=ResolvedLayoutSchemaVersionV3||worldIdentity.empty()||strates.empty() - ||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0) + ||coreVoxelSizeMicrometres==0||strateLayoutChunkSizeMicrometres==0 + ||strateLayoutChunkWidthSubvoxels<=0) {error="resolved layout has an unsupported version, empty world identity, or no strates";return false;} std::stable_sort(strates.begin(),strates.end(),[](const auto& a,const auto& b) {return a.interiorTopMicrometres!=b.interiorTopMicrometres @@ -257,7 +421,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode, { issues.clear();error.clear(); if(schemaVersion!=ResolvedLayoutSchemaVersionV3||worldIdentity.empty()||strates.empty() - ||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0) + ||coreVoxelSizeMicrometres==0||strateLayoutChunkSizeMicrometres==0 + ||strateLayoutChunkWidthSubvoxels<=0) {error="resolved layout schema/world/strata header is invalid";return false;} const auto add=[&](std::string code,std::string detail) {issues.push_back({std::move(code),std::move(detail)});}; @@ -271,6 +436,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode, if(s.transitionInterpenetrationMicrometres>effectiveTransitionWidth ||s.transitionInterpenetrationMicrometres<0) add("transition_overlap_exceeds_width",s.stableId+" transition interpenetration exceeds its declared width"); + if(s.transitionWidthMicrometres<0||s.transitionBlendChunks<0) + add("invalid_transition_width",s.stableId+" has a negative transition width"); if(s.managerChunkWidthMicrometres>0&&s.dispatchChunkWidthMicrometres>0 &&s.managerChunkWidthMicrometres!=s.dispatchChunkWidthMicrometres) add("ambiguous_dispatch_units",s.stableId+" manager chunk width differs from Core dispatch chunk width"); @@ -290,6 +457,10 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode, { if(b.kind!=expected||b.topMicrometres<=b.bottomMicrometres) add("wrong_order",s.stableId+" has an invalid "+std::string(expected)+" band"); + const std::int64_t minimumThickness=2ll*coreVoxelSizeMicrometres; + if(b.topMicrometres-b.bottomMicrometres=0&&static_cast(StrateIndex)strates.size()) + { + const auto& direct=ResolvedLayout->strates[static_cast(StrateIndex)]; + if(direct.strateIndex==StrateIndex)return &direct; + } const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(), [StrateIndex](const auto& item){return item.strateIndex==StrateIndex;}); return it==ResolvedLayout->strates.end()?nullptr:&*it; } + const VoxelForgeCore::ResolvedLayoutStratumV2* FindResolvedLayoutStratumByOrder(int32 Order) const + { + if(!ResolvedLayout||Order<0)return nullptr; + if(static_cast(Order)strates.size()) + { + const auto& direct=ResolvedLayout->strates[static_cast(Order)]; + if(direct.order==Order)return &direct; + } + const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(), + [Order](const auto& item){return item.order==Order;}); + return it==ResolvedLayout->strates.end()?nullptr:&*it; + } + + bool IsResolvedLayoutTransitionActive(const VoxelForgeCore::ResolvedLayoutStratumV2& Upper) const + { + return Upper.transitionMode!="Hard"&&!Upper.transitionFallsBackToHard + &&Upper.transitionWidthMicrometres>0 + &&Upper.transitionHonouredInCore; + } + + std::int64_t ResolvedTransitionMaximumFaceDisplacementMicrometres( + const VoxelForgeCore::ResolvedLayoutStratumV2& Upper, + const VoxelForgeCore::ResolvedLayoutStratumV2& Lower) const + { + const std::int64_t width=std::max(0,Upper.transitionWidthMicrometres); + if(Upper.transitionMode=="Gradient")return width/8; + if(Upper.transitionMode!="Interleaved")return 0; + const std::int64_t upperThickness=std::max(0, + Upper.bedrock.topMicrometres-Upper.bedrock.bottomMicrometres); + const std::int64_t lowerThickness=std::max(0, + Lower.ceiling.topMicrometres-Lower.ceiling.bottomMicrometres); + const std::int64_t maximumCrossing=std::min(width,upperThickness+lowerThickness); + return (maximumCrossing+1)/2; + } + + std::int64_t ResolvedTransitionPassageWeightQ16( + const VoxelForgeCore::ResolvedLayoutStratumV2& Upper, + const VoxelForgeCore::ResolvedLayoutStratumV2& Lower, + std::int64_t XMicrometres,std::int64_t YMicrometres) const + { + constexpr std::int64_t Q16=1ll<<16; + constexpr std::int64_t SupportMarginMicrometres=1000000; + constexpr std::int64_t FadeWidthMicrometres=2000000; + if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0)return Q16; + const std::int64_t seam=Upper.bedrock.bottomMicrometres; + const std::int64_t coreMicrometres=Owner->CoreVoxelSizeMicrons; + const std::int64_t subvoxelsPerVoxel=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; + std::int64_t weight=Q16; + for(const auto& passage:ResolvedLayout->passages) + { + if(passage.upperStrateIndex!=Upper.strateIndex + ||passage.lowerStrateIndex!=Lower.strateIndex)continue; + const std::int64_t mouthRadiusMicrometres=std::max(0, + passage.mouthRadiusSubvoxels*coreMicrometres/subvoxelsPerVoxel); + const std::int64_t protectedRadius=mouthRadiusMicrometres + +SupportMarginMicrometres; + for(const auto* mouth:{&passage.upperMouthSubvoxels,&passage.lowerMouthSubvoxels}) + { + const std::int64_t mouthZMicrometres=mouth->at(2)*coreMicrometres + /subvoxelsPerVoxel; + if(std::abs(mouthZMicrometres-seam) + >Upper.transitionWidthMicrometres/2+mouthRadiusMicrometres)continue; + const double dx=static_cast(XMicrometres-mouth->at(0)*coreMicrometres + /subvoxelsPerVoxel); + const double dy=static_cast(YMicrometres-mouth->at(1)*coreMicrometres + /subvoxelsPerVoxel); + const std::int64_t fadeEnd=protectedRadius+FadeWidthMicrometres; + const double distanceSquared=dx*dx+dy*dy; + if(distanceSquared>=static_cast(fadeEnd)*fadeEnd)continue; + if(distanceSquared<=static_cast(protectedRadius)*protectedRadius)return 0; + const double distance=std::sqrt(distanceSquared); + const double t=static_cast(static_cast(distance) + -protectedRadius)/FadeWidthMicrometres; + const double smooth=t*t*(3.0-2.0*t); + weight=std::min(weight,static_cast(std::llround(smooth*Q16))); + } + } + return weight; + } + bool IsInsideResolvedStrateBoundaryBand(float Z) const { if(!ResolvedLayout||!Owner||!Owner->Manager||!std::isfinite(Z))return false; @@ -160,6 +245,21 @@ public: const auto solidAt=[&](const VoxelForgeCore::ResolvedLayoutBandV2& band) {return band.presentInCurrentField&&zMicrometres>=band.bottomMicrometres &&zMicrometresbedrock.bottomMicrometres, + Lower->ceiling.bottomMicrometres)-maxDisplacement; + const double maximum=std::max(Upper->bedrock.topMicrometres, + Lower->ceiling.topMicrometres)+maxDisplacement; + return zMicrometres>=minimum&&zMicrometresorder+1); + const auto* upper=stratum->order>0?FindResolvedLayoutStratumByOrder(stratum->order-1):nullptr; + if(mayTouchTransition(stratum,lower)||mayTouchTransition(upper,stratum))return true; return solidAt(stratum->ceiling)||solidAt(stratum->bedrock); } @@ -175,8 +275,68 @@ public: const auto solidAt=[&](const VoxelForgeCore::ResolvedLayoutBandV2& band) {return band.presentInCurrentField&&zMicrometres>=band.bottomMicrometres &&zMicrometresceiling)?&stratum->ceiling - :solidAt(stratum->bedrock)?&stratum->bedrock:nullptr; + + bool bCeilingHandledByTransition=false,bBedrockHandledByTransition=false; + const auto applyInterface=[&](const VoxelForgeCore::ResolvedLayoutStratumV2* upper, + const VoxelForgeCore::ResolvedLayoutStratumV2* lower) + { + if(!upper||!lower||!IsResolvedLayoutTransitionActive(*upper))return false; + const auto maxDisplacement=ResolvedTransitionMaximumFaceDisplacementMicrometres( + *upper,*lower); + const double minimum=std::min(upper->bedrock.bottomMicrometres, + lower->ceiling.bottomMicrometres)-maxDisplacement; + const double maximum=std::max(upper->bedrock.topMicrometres, + lower->ceiling.topMicrometres)+maxDisplacement; + if(zMicrometres=maximum)return false; + if(upper==stratum)bBedrockHandledByTransition=true; + if(lower==stratum)bCeilingHandledByTransition=true; + const std::int64_t xMicrometres=static_cast(std::llround( + static_cast(X)*Owner->CoreVoxelSizeMicrons)); + const std::int64_t yMicrometres=static_cast(std::llround( + static_cast(Y)*Owner->CoreVoxelSizeMicrons)); + const auto bands=VoxelForgeCore::EvaluateResolvedLayoutTransitionBandsV1(*upper,*lower, + Owner->Seed,ResolvedLayout->strateLayoutChunkSizeMicrometres, + xMicrometres,yMicrometres); + auto resolvedBands=bands; + const std::int64_t transitionWeight=ResolvedTransitionPassageWeightQ16( + *upper,*lower,xMicrometres,yMicrometres); + constexpr std::int64_t TransitionQ16=1ll<<16; + if(transitionWeightbedrock.bottomMicrometres; + const std::int64_t upperThickness=upper->bedrock.topMicrometres-seam; + const std::int64_t lowerThickness=lower->ceiling.topMicrometres + -lower->ceiling.bottomMicrometres; + const auto scaled=[&](std::int64_t value) + {return (value*transitionWeight)/TransitionQ16;}; + resolvedBands.upperBedrockBottomMicrometres=seam+scaled( + bands.upperBedrockBottomMicrometres-seam); + resolvedBands.upperBedrockTopMicrometres= + resolvedBands.upperBedrockBottomMicrometres+upperThickness; + resolvedBands.lowerCeilingTopMicrometres=seam+scaled( + bands.lowerCeilingTopMicrometres-seam); + resolvedBands.lowerCeilingBottomMicrometres= + resolvedBands.lowerCeilingTopMicrometres-lowerThickness; + resolvedBands.crossingDepthMicrometres=std::max(0, + resolvedBands.lowerCeilingTopMicrometres + -resolvedBands.upperBedrockBottomMicrometres); + } + const bool upperBedrock=zMicrometres>=resolvedBands.upperBedrockBottomMicrometres + &&zMicrometres=resolvedBands.lowerCeilingBottomMicrometres + &&zMicrometresbedrock:lower->ceiling; + density=-std::max(0.05f,static_cast(band.solidDensityQ16)/65536.0f); + return true; + }; + + const auto* lower=FindResolvedLayoutStratumByOrder(stratum->order+1); + const auto* upper=stratum->order>0?FindResolvedLayoutStratumByOrder(stratum->order-1):nullptr; + if(applyInterface(stratum,lower)||applyInterface(upper,stratum))return density; + const auto* band=(!bCeilingHandledByTransition&&solidAt(stratum->ceiling)) + ?&stratum->ceiling:(!bBedrockHandledByTransition&&solidAt(stratum->bedrock)) + ?&stratum->bedrock:nullptr; if(!band)return density; return -std::max(0.05f,static_cast(band->solidDensityQ16)/65536.0f); } diff --git a/Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md new file mode 100644 index 0000000..a416b9f --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux29/REPORT.md @@ -0,0 +1,110 @@ +# World Assembler L29 — bedrock/ceiling transitions + +**Core now honors the upper strate's `EVoxelStrateTransition` at the shared interface.** The owner +world's upper two-chunk Gradient resolves to 16 m at the 8 m layout chunk size. SurfaceWorld's +one-chunk setting remains in the resolved strate record. Core does not blend unrelated generator +parameters. `WorldAssemblyEvaluatorVersion` is 15; recipe versions and resolved-layout schema V3 +are unchanged. + +## Appearance and safety + +| Mode | Result | +|---|---| +| Hard | The original flat, touching boundary. No displacement or overlap. | +| Gradient | Both faces follow one smooth 2D hash surface. On the 16 m interface, the measured peak-to-peak undulation is 3.988 m. | +| Interleaved | A deterministic 3D domain-warped hash field moves both faces inward, producing fingered overlap. Measured peak crossing was 6.487 m in FloatingIslands and 6.952 m in SurfaceWorld, within the declared 16 m. | + +Each boundary band keeps at least **two Core voxels** of solid thickness: 0.5 m at the default +25 cm voxel size. The owner bands measured 0.5–1.0 m on the upper side and 6.25 m on the lower +side. Interleaved is capped by both the declared width and the bands' combined thickness, so the +solid intervals still touch or overlap. + +The strict interface audit covers a 512 m square: 4,221 columns per interface, with four passage +columns excluded from the hole sweep. Hard, Gradient and Interleaved each report zero hole columns +and zero air samples. Both negative controls reject as expected: crossing one micrometre wider than +declared, and either band one micrometre thinner than the minimum. All six recipe/mode runs pass. + +Transition faces return to the nominal seam near passage mouths: the protected radius is the mouth +radius plus 1 m, followed by a 2 m smooth fade. This kept the style-2 FI landing supported; its +final capsule/support audit passes. + +## Passage and parity audits + +The final matrix has 4 styles × 2 recipes × 2 compilers (16 runs). Capsule and walkability, +open-air, naturalness, no-long-bridge, route bindings, strict-validator controls, L13 route parity, +and L9 proof audits pass. Open-air reports zero violations; validator controls reject 10/10; +route-parity and proof audits report zero mismatches/violations. The L23/L24/L26 negative controls +are detected. + +The L28 `FloorProfileBlend` diagnostic still exceeds its 2-voxel threshold in styles 1 and 3: +2.544 and 3.645 voxels. L28 records the same values on both compilers, so this is inherited and +unchanged. + +## Hashes + +The L25 W1/W8/W12 hash matrix is identical across Clang and GCC for both recipes: full-detail, +whole-view, all LOD rows and exact-query hashes match. Hash order below is density / mesh / payload / +topology. + +| Recipe | Full detail (960 tiles) | Whole view (2,370 tiles) | Exact-query density / detail / collision | +|---|---|---|---| +| SurfaceWorld | `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae` | `0e8c5af01df96b14 / 70099e89b77811ed / dbce0fdcf6764126 / ddaf81a1228f9b4b` | `c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849` | +| FloatingIslands | `ed550e0c8b182695 / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec` | `a492eefa4f534975 / 141f00f3e411a0df / 93f50c569526c5cb / d83b53c8e53df81c` | `7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf` | + +The 24 m moving crossing processed 314 entering tiles per recipe; Clang/GCC crossing hashes match. +SurfaceWorld's away-from-interface probe at z = −400 m also matches L28: all 960 full-detail +tiles and the exact-query density/detail/collision hashes are identical. The broader whole-view +hash differs because its far-LOD coverage also includes the changed interface. + +## Cost + +Five clean, alternating W12 pairs against +`Saved/Linux/bin/VoxelForgeCoreHeadless-linux28-clang-r28`, hashing off: + +| Recipe | L28 whole-view ready | L29 whole-view ready | Change | +|---|---:|---:|---:| +| SurfaceWorld | 1.581 s | 1.644 s | +3.97% | +| FloatingIslands | 16.605 s | 16.814 s | +1.26% | + +FloatingIslands is within the ~3% target. SurfaceWorld is about one percentage point above it. + +## Captures + +All are 1280×720 JPEGs, each under 21 KB (well below 300 KB): + +- `captures/fi-floor-gradient-above.jpg` — FI floor from above, inside FI. +- `captures/sw-ceiling-gradient-below.jpg` — SW ceiling from below, inside SW. +- `captures/interface-hard-side-cutaway.jpg` +- `captures/interface-gradient-side-cutaway.jpg` +- `captures/interface-interleaved-side-cutaway.jpg` + +## Changes and run history + +Core transition evaluation/validation and the shared composition path changed, along with the +headless transition audit, architecture/codemap, and the two Unreal files +`Source/VoxelForge/Public/VoxelStrateTypes.h` and +`Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`. Unreal was unavailable here, so those edits +were not compiled. No commit, push or stash was made. + +Failed or retried runs: + +- An early Clang build used a nonexistent dispatch helper; corrected to the current query method and rebuilt with Clang and GCC. +- The first Interleaved overlap used the full sum of band thicknesses and opened gaps (1,085 columns / 20,313 air samples). Capping overlap to `min(declared width, combined thickness)` removed the gaps. +- An initial hole sweep sampled a half-open band edge and reported 752 boundary-rounding holes. Sampling half a voxel inside each band removed those edge artifacts; final sweeps report zero holes. +- The first FI style-2 passage audit had 11 unsupported probes after Gradient moved the mouth face. The local mouth taper fixed it; all 16 final passage runs pass. +- A resolved-layout-only command initially omitted required pipeline flags and exited 2; rerun with the required flags passed. Early cutaway camera/clip settings produced blank framing; the final orientation and clip plane produced the captures above. +- One cost folder contained a duplicated timing sequence and was discarded. The five-pair clean run above was sequential. A cache experiment did not improve cost and was removed. A cost-extraction `sed` typo was rerun with Python; benchmark logs were unaffected. + +No build or audit exceeded 30 minutes, and no step remained stuck for 15 minutes. All builds and runs +used CPUs 0–11, at most 12 workers, normal priority, and the 6 GiB address-space cap. + +## Orchestrator review (Claude) + +- Merged: transitions honoured safely (0 holes over 4,221 columns per interface, negative controls reject, all + passage audits pass, deterministic). +- **The look is not verified by the captures:** `interface-interleaved-side-cutaway.jpg` shows flat faces + (black = uncapped solid between faces), with no visible fingers; `sw-ceiling-gradient-below.jpg` is a flat + plane; `fi-floor-gradient-above.jpg` shows **concentric dashed rings** on the FI floor, which looks like an + artifact (possibly the passage-mouth protected-radius fade, or quantised undulation), not a smooth Gradient. +- Cumulative SW cost vs L24 is now ~+8% (L28 +3.7% plus L29 +4%). L30 covers the ring artifact, readable + transition captures, and cost recovery. diff --git a/Standalone/Research/WorldAssemblerM3/Linux29/captures/fi-floor-gradient-above.jpg b/Standalone/Research/WorldAssemblerM3/Linux29/captures/fi-floor-gradient-above.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2794f8f066f490c3a64c2808731d95c35cc235ce GIT binary patch literal 16388 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 M00JKz0mFlT16qu`*8l(j literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-interleaved-side-cutaway.jpg b/Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-interleaved-side-cutaway.jpg new file mode 100644 index 0000000000000000000000000000000000000000..219c9dd2a3850bd00ef41088b4a855203582b918 GIT binary patch literal 19476 zcmeI2d011|w!n9c8f_(tAX-Fd;o^V-QU)bR;;jO`j4~GxDX1V}Dl*BG&|ay60wOXS zqoxu_2$Wf-#1>tD<>x}FTZWu)~!lPJ5*KoNE$&ZfWcq@F8#erh&8KV@U^R@^~+5^N$^#x zVVhu5g*9te!&f8LB39aAaM`pqtW^%F_6l}eY&XlV{`)N*&$S<2C4H&-afz-M`}ULI zvvNn&J~{RMcSEHo2W3}!Te1DdyzDC2W?MPWzr%HIeH1LY2V~c+YF{IDX){>08NfFK zNe|cnhk;eHa9Mx>U&~zniAzxSuScNc<=wnyw@`0NS$`~3_bJD&s3p-qy~-3zCD$w^ zpCUP$*J|x4b?X0sQgnkiVR$vkanH-UHke!n3$xhj9ow+1i9iuz0v{(7j`7@n&Zj%?w8-?PMMMB{5{x*JM^Y+Qu zu713qT{o%Nef<=%mo+OY&Mnv=0ajuOnA>6)BNx&qde&%MUq(La%pNiiAT}Fd`>`)i z^B4&gkH767XH$F$H;Q((_&oGhx2tp>rw?TxIPAv0dTgsi-O!@A;ftNYXHem}&&8C#}@Z&aFM6J)2klJhW|0NyXvLEf5Jet@8&oTQwkGpS$skT$?#gLto ziDiRR{vuaeWno=kX1)Z}%vaPC0vsq~Gvn--Cp_fCQ&HHyQvS!9bgs1=s2qZI) zO2DM0(36v^71etU|Gf5U2F7ef(cOXSOF)sr^QYOEZJ~9o*KSAvb3{4iw~pZtAP1wy zdpZVdFm}k7w@K%fKQ_(5Xcu1Y=}QUu+xJtHyXh9H{qp5vkp-H_CNmv`bPbdDLMjT znP_$xp4krTVzq;pus&Zkhd9H=qp}7DQrKl`BdeKx~b@` zdP`C8gTw~|U-K&Os)4Rp#u7fX3P6e^M ze&E!0U2XP(oFk&hv>)1uJ|spYez=cYQvbLpMkNKQ4qT$_V5lPur3Y}tWf_U ztmEi^xULO-4!`nCfgPEr84hMeU51wq?>O2(1xf^96#h$sf5^JAr|^W&;g?%#fzrP6 zY@ivj2^=;45R^RqRd0t-h2U&tOZ-(In?(pyx z#3lfgNDd7b;q11J`w?Wq!v|9!QgCPN)H!fS_~dozA)xHk(kZPxS6`R{5SM!1W!H6# zNQIF1{=L(8rSf;V-;+Vu8yJD$*@oH=VO=+NbSncS`pns7Cvd*gQ7+10qqZKu-e2lS zW=_nBO0zh4ODkI2P3<0Rm`mJBOO?K|%-UR9b~iHe4(}&p!1=CdWAy6xNtKB?Co)!a zS5%+RFL$8rC$;i_Wq-u*TkN*AYFkK+jc_yXNIjaJwRrMVs58q@l@3jWOFc+g;T^`k zAT7ZB&n-JuzN)>Ux!S#WM;_9Bg!p>69zO0C1VUrGIWyHl2X zf>U?I9gI}Er;IbZ32%Gw`gisYi9-Y+0)Hd|a!9ZH;VCS(1c0&U^z~tssGvuDqT}}b z)<>g)t~rNA&PNBhF*cN>4)2lB7~bOTSnQSDS@bTB>9}r44LVF@i>~<8CbDc`O2ioM z5|Ida0^Y%fb}a3g|AG(`APAFyGhaynN}Nx0y|t^ady;=*V1_3Fith_ojD5wo4qVu- zsnSMULwA|TXWG=p>^cygl#?>jnK-a-!R;iupDvdxYHG2V<(UL!MAJ&x69ZdYC14(( zCjs2V0qp@{K=QyDK0QbR%0vewK+j2llYk-r@FlbR%xC;c&G|H+riYA_anX5cOC>X? zJSOEpbZk!5NP8kfb7A{Qau?bxx1QeWHaq$ckBp_H5;l*ht`c2eQ4&;1lbt3SBnWWp z_s$FiSE6G5xQ1m^$z0NEqFzr!dN42sEF6V z_1a~2m#vJp()E57p7*9ncchy5{x~hnFpvCrAW63q9Y5RG7C+}ybDeEJ64aE9A2n%G;jzMzp_hhgqD z;Em3)jZgX}ktf$~p?Dcvn$S@iM{(5zgZ%>v%c3pX^`5!zJ7&{lf`jzQX-TFYQ4Iz) zMk702pFi{Kg31sNXgxst0Xh$$^AL&$f5~`gT^I2_OJR``BF^GaB1|)+ao;$yQ$MS& zAwEEbR3KjKCg^1r&L!pj=SxO>@#M~}WU&?vKSnN|8rUSgd`Pb+{V@x=gRzc9WCM9Y zuk^~nY^O(OPthq|_Bq&?SA4dySD@C;KhoV7@&zIQ5r7Cl1Rw&xMqonK+y7!^jN0@S zgcT;}pI*w`X7&*W=|569MXj_}MDq&cgp%ZihhHYb$gEye64QuXV zjPa+7JpM*%Ut(N1CiK3}Au86;Qd$&z1-p|6!ZL>BXo~tA&M=GYRBu1UmZL;`Qf-}% z(m8FcsAu*yK_?G=tSmcTF6SUp-BmLY{iy^jVv|(%2yy0iB4E*6htfnp#IDSc%M!sH=5c;I<@-_Wh%0AP|8)rM!+2%7JAM2dRFM(7c zOwf{mb`Eq(Kqum#F>v&WdK+(R&e#!4HB2a~{Tu}~H;cWVIB`53AK;{l)*-iUf8G0D zE~#*UN4OZEoJG(*)!4|l8Ppgym-jPC-@`5t(@)%@4({pOWpqQ1hxS-Az>=3G{S=Q!|1PF zH~@exo>{_#P2kf_X*QL5t+`^N&I>Ml7M*;dKPA?Fx`2|tZONyN8cuBB&Lz<|dd{ZQ z&r3jNT zozDhj7U&}a+_P_(4$Mp&F6WfSr3o(n_{)Z%_*)BZeY{dXN3u0qCr|nx`#Caq=}whM zMF{zo}M$0#mM<&5Z5?QFzQ&snrm2GH4AevOF*9s%2R!$mz(zazVAhO>lG-_+ZSFRa>@`{b5uUAOs- znlus0$9XC~#L_gY%<`z&d6U$dtrC#zEpM|swy$xo3#xpSq|!y5XK3a6#3rit#+Y2} z9N7CxJl@XcchQQz;h(w4BF6~Gd~|`81lUN2nBB3T==rK}CORsdDRL#XQE|1dngLb# z!nzAL-;MCEmPuwrR5chc)Chf-qSAKQE4v%bxgaxhtBD=Y8m8#CFZ2h66i?Hf2)n1) z0ahOTF+oemf=FlRINkk|TMI!?ZA+!!NvmD)#f~|6tz2}7hFVpp+xf+}?;h#cl*(JL zoxN;PoLjc#C%A1pjuQ^MZY0tbjrNEV%@Om1YNIKj z{K}E6n2eZO_om#oqh2d%&Pc8=VQ1O6B#Oi}=~DG4Es`vfEfV(>k{L-{<}^graI#qY zT|7eo0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bICAoo1Kx;MF9 zO}Sf@{9&T+D*c^kUwC<3nN6Kcyj41%ZWcG~!1TaIM`5?Ob?~(Ox;E2YIxn(&root.Data); + if(!object){error="transition fixture override requires an owner JSON object";return false;} + const auto stratesIt=object->find("strates"); + if(stratesIt==object->end()) + {error="transition fixture override cannot find the resolved strate array";return false;} + auto* strates=std::get_if(&stratesIt->second.Data); + if(!strates||strates->empty()) + {error="transition fixture override found an empty or invalid strate array";return false;} + for(auto& strate:*strates) + { + auto* strateObject=std::get_if(&strate.Data); + if(!strateObject){error="transition fixture contains a malformed strate";return false;} + const auto assetIt=strateObject->find("asset"); + if(assetIt==strateObject->end())continue; + auto* asset=std::get_if(&assetIt->second.Data); + if(!asset)continue; + if(mode>=0) + { + VFJson::Value value;value.Data=static_cast(mode); + (*asset)["TransitionType"]=std::move(value); + } + if(widthChunks>=0) + { + VFJson::Value value;value.Data=static_cast(widthChunks); + (*asset)["TransitionBlendChunks"]=std::move(value); + } + } + return true; + } + bool ParseOptions(int argc,char** argv,FOptions& options) { const std::filesystem::path executable=std::filesystem::absolute(argv[0]); @@ -126,6 +161,8 @@ namespace " --resolved-layout-output=FILE (canonical plan-time layout snapshot)\n" " --resolved-layout-only (exit after plan and snapshot validation)\n" " --audit-resolved-layout-boundaries (query Core at each recorded endpoint)\n" + " --audit-strate-transitions (measure interface crossing, band thickness and holes)\n" + " --transition-mode=hard|gradient|interleaved --transition-width-chunks=N (fixture override)\n" " --audit-resolved-layout-passages (capsule, support, route-binding and blocked-control audit)\n" " --audit-surface-clearance (sample biome terrain/caps against the resolved ceiling)\n" " --auto-surface-camera (place the 27-tile cube on this seed's surface)\n" @@ -149,6 +186,21 @@ namespace else if(arg=="--audit-resolved-layout-boundaries")options.AuditResolvedLayoutBoundaries=true; else if(arg=="--audit-resolved-layout-passages")options.AuditResolvedLayoutPassages=true; else if(arg=="--audit-surface-clearance")options.AuditSurfaceClearance=true; + else if(arg=="--audit-strate-transitions")options.AuditStrateTransitions=true; + else if(arg.starts_with("--transition-mode=")) + { + const std::string_view mode=arg.substr(std::string_view("--transition-mode=").size()); + if(mode=="gradient")options.TransitionModeOverride=0; + else if(mode=="hard")options.TransitionModeOverride=1; + else if(mode=="interleaved")options.TransitionModeOverride=2; + else {std::fprintf(stderr,"Invalid transition mode: %.*s\n",static_cast(mode.size()),mode.data());return false;} + } + else if(arg.starts_with("--transition-width-chunks=")) + { + if(!readInt("--transition-width-chunks=",options.TransitionWidthChunksOverride) + ||options.TransitionWidthChunksOverride<0||options.TransitionWidthChunksOverride>64) + {std::fprintf(stderr,"transition width chunks must be in 0..64\n");return false;} + } else if(arg=="--stream-benchmark")options.StreamBenchmark=true; else if(arg=="--stream-pipeline")options.StreamPipeline=true; else if(arg=="--stream-hash")options.StreamHash=true; @@ -303,6 +355,10 @@ int main(int argc,char** argv) if(options.FloatingIslandsRecipe &&!StandaloneWorldAssemblyPrototype::SelectTopStrateAsFloatingIslands(ownerJsonRoot,error)) {std::fprintf(stderr,"Floating-island recipe selection failed: %s\n",error.c_str());return 2;} + if((options.TransitionModeOverride>=0||options.TransitionWidthChunksOverride>=0) + &&!OverrideTransitions(ownerJsonRoot,options.TransitionModeOverride, + options.TransitionWidthChunksOverride,error)) + {std::fprintf(stderr,"Transition fixture override failed: %s\n",error.c_str());return 2;} if(!StandaloneOwnerLoader::Resolve(ownerJsonRoot,*ownerWorld,error)) {std::fprintf(stderr,"Owner data load failed: %s\n",error.c_str());return 2;} std::printf("[ColdTimeline] stage=owner_data_load end_ms=%.3f\n", @@ -551,6 +607,6 @@ int main(int argc,char** argv) options.RegionCrossing,options.AuditTileShapeReadiness, options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence, options.AuditRouteParity,options.AuditResolvedLayoutBoundaries, - options.AuditResolvedLayoutPassages, + options.AuditResolvedLayoutPassages,options.AuditStrateTransitions, &resolvedLayout); } diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index d3ce0dc..8a2a223 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -240,6 +240,172 @@ namespace StandaloneStreamBenchmark return Hash; } + inline bool AuditResolvedStrateTransitions(const VoxelForgeCore::World& Field, + const FStandaloneOwnerWorld& Owner, + const VoxelForgeCore::ResolvedLayoutSnapshotV3& Layout) + { + constexpr std::int64_t XYExtentMicrometres=256000000; + constexpr std::int64_t XYStepMicrometres=8000000; + const std::int64_t coreMicrometres=std::max(1,Owner.CoreVoxelSizeMicrons); + const std::int64_t zStepMicrometres=std::max(1,coreMicrometres/2); + const std::int64_t minimumBandMicrometres=2*coreMicrometres; + std::uint64_t interfacesPassed=0,totalColumns=0,passageColumnsSkipped=0; + std::uint64_t holeColumns=0,airSamples=0,solidSamples=0; + bool negativeWidthRejected=false,negativeThinBandRejected=false; + + for(std::size_t interfaceIndex=0;interfaceIndex+1::max(); + std::int64_t maximumOffset=std::numeric_limits::min(); + std::int64_t maximumCrossingDepth=0; + std::int64_t minimumUpperThickness=std::numeric_limits::max(); + std::int64_t minimumLowerThickness=std::numeric_limits::max(); + std::uint64_t columns=0,skipped=0,holes=0,air=0,solid=0; + bool firstHoleReported=false; + const std::int64_t seam=upper.bedrock.bottomMicrometres; + const auto passageCoversColumn=[&](std::int64_t xUm,std::int64_t yUm, + std::int64_t zMinimumUm,std::int64_t zMaximumUm) + { + constexpr double Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; + for(const auto& passage:Layout.passages) + { + if(passage.upperStrateIndex!=upper.strateIndex + ||passage.lowerStrateIndex!=lower.strateIndex)continue; + for(std::size_t pointIndex=1;pointIndexzMaximumUm)continue; + const double dx=bx-ax,dy=by-ay; + const double lengthSquared=dx*dx+dy*dy; + const double t=lengthSquared>0.0 + ?std::clamp(((xUm-ax)*dx+(yUm-ay)*dy)/lengthSquared,0.0,1.0):0.0; + const double ex=xUm-(ax+t*dx),ey=yUm-(ay+t*dy); + if(ex*ex+ey*ey<=radius*radius)return true; + } + } + return false; + }; + + for(std::int64_t iy=-XYExtentMicrometres/XYStepMicrometres; + iy<=XYExtentMicrometres/XYStepMicrometres;++iy) + for(std::int64_t ix=-XYExtentMicrometres/XYStepMicrometres; + ix<=XYExtentMicrometres/XYStepMicrometres;++ix) + { + const std::int64_t xUm=ix*XYStepMicrometres; + const std::int64_t yUm=iy*XYStepMicrometres; + const auto bands=VoxelForgeCore::EvaluateResolvedLayoutTransitionBandsV1( + upper,lower,Owner.Seed,Layout.strateLayoutChunkSizeMicrometres,xUm,yUm); + const std::int64_t offset=bands.upperBedrockBottomMicrometres-seam; + minimumOffset=std::min(minimumOffset,offset); + maximumOffset=std::max(maximumOffset,offset); + maximumCrossingDepth=std::max(maximumCrossingDepth, + bands.crossingDepthMicrometres); + minimumUpperThickness=std::min(minimumUpperThickness, + bands.upperBedrockTopMicrometres-bands.upperBedrockBottomMicrometres); + minimumLowerThickness=std::min(minimumLowerThickness, + bands.lowerCeilingTopMicrometres-bands.lowerCeilingBottomMicrometres); + const std::int64_t zMinimum=std::min(bands.upperBedrockBottomMicrometres, + bands.lowerCeilingBottomMicrometres); + const std::int64_t zMaximum=std::max(bands.upperBedrockTopMicrometres, + bands.lowerCeilingTopMicrometres); + if(passageCoversColumn(xUm,yUm,zMinimum,zMaximum)){++skipped;continue;} + + ++columns; + bool hasHole=false; + for(std::int64_t zUm=zMinimum+zStepMicrometres/2; + zUm(xUm)/coreMicrometres, + static_cast(yUm)/coreMicrometres, + static_cast(zUm)/coreMicrometres}; + const float density=Field.densityAt(point); + if(!std::isfinite(density)||density>=0.0f) + { + if(!firstHoleReported) + { + std::printf("[StrateTransitionFirstHole] x_um=%lld y_um=%lld z_um=%lld z_min_um=%lld z_max_um=%lld density=%.9g dispatch=%s\n", + static_cast(xUm),static_cast(yUm), + static_cast(zUm),static_cast(zMinimum), + static_cast(zMaximum),density, + Field.densityDispatchPathAt(point).c_str()); + firstHoleReported=true; + } + hasHole=true;++air; + } + else ++solid; + } + if(hasHole)++holes; + } + + const std::int64_t boundaryPeakToPeak=maximumOffset-minimumOffset; + const std::int64_t allowedWidth=upper.transitionMode=="Hard" + ||upper.transitionFallsBackToHard?0:upper.transitionWidthMicrometres; + if(interfaceIndex==0) + { + auto wide=Layout; + wide.strates[0].transitionMode="Interleaved"; + wide.strates[0].transitionFallsBackToHard=false; + wide.strates[0].transitionWidthMicrometres=8000000; + wide.strates[0].transitionInterpenetrationMicrometres=8000001; + std::vector issues; + std::string error; + negativeWidthRejected=!wide.validate( + VoxelForgeCore::ResolvedLayoutValidationModeV3::strict,issues,error); + + auto thin=Layout; + auto& bedrock=thin.strates[0].bedrock; + bedrock.topMicrometres=bedrock.bottomMicrometres+minimumBandMicrometres-1; + thin.strates[0].content.bottomMicrometres=bedrock.topMicrometres; + negativeThinBandRejected=!thin.validate( + VoxelForgeCore::ResolvedLayoutValidationModeV3::strict,issues,error); + } + const bool passed=columns>0&&holes==0&&maximumCrossingDepth<=allowedWidth + &&minimumUpperThickness>=minimumBandMicrometres + &&minimumLowerThickness>=minimumBandMicrometres; + interfacesPassed+=passed?1u:0u; + totalColumns+=columns;passageColumnsSkipped+=skipped; + holeColumns+=holes;airSamples+=air;solidSamples+=solid; + std::printf("[StrateTransitionInterface] upper=%d lower=%d mode=%s width_um=%lld max_crossing_depth_um=%lld boundary_peak_to_peak_um=%lld allowed_um=%lld upper_min_band_um=%lld lower_min_band_um=%lld columns=%llu passage_columns=%llu hole_columns=%llu air_samples=%llu status=%s\n", + upper.strateIndex,lower.strateIndex,upper.transitionMode.c_str(), + static_cast(upper.transitionWidthMicrometres), + static_cast(maximumCrossingDepth), + static_cast(boundaryPeakToPeak),static_cast(allowedWidth), + static_cast(minimumUpperThickness),static_cast(minimumLowerThickness), + static_cast(columns),static_cast(skipped), + static_cast(holes),static_cast(air), + passed?"PASS":"FAIL"); + } + const bool controlsPassed=negativeWidthRejected&&negativeThinBandRejected; + std::printf("[StrateTransitionNegativeControls] wider_than_declared=%s thinner_than_minimum=%s minimum_band_um=%lld status=%s\n", + negativeWidthRejected?"REJECTED":"ACCEPTED", + negativeThinBandRejected?"REJECTED":"ACCEPTED", + static_cast(minimumBandMicrometres),controlsPassed?"PASS":"FAIL"); + const bool passed=!Layout.strates.empty()&&interfacesPassed+1==Layout.strates.size() + &&holeColumns==0&&airSamples==0&&controlsPassed; + std::printf("[StrateTransitionAuditSummary] interfaces=%llu passed=%llu columns=%llu skipped_passage_columns=%llu hole_columns=%llu air_samples=%llu solid_samples=%llu status=%s\n", + static_cast(Layout.strates.size()-1), + static_cast(interfacesPassed), + static_cast(totalColumns), + static_cast(passageColumnsSkipped), + static_cast(holeColumns), + static_cast(airSamples), + static_cast(solidSamples),passed?"PASS":"FAIL"); + return passed; + } + inline int RunPipelinedStreamingBenchmark(std::shared_ptr OwnerWorld, const VoxelForgeCore::ResolvedWorldSnapshot& CoreSnapshot, const FVector& CameraPositionVoxels,int32 WorkerCount,int32 MaxLevel, @@ -256,6 +422,7 @@ namespace StandaloneStreamBenchmark bool bAuditBasePageEquivalence=false,bool bAuditRouteParity=false, bool bAuditResolvedLayoutBoundaries=false, bool bAuditResolvedLayoutPassages=false, + bool bAuditStrateTransitions=false, const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr) { using Clock=std::chrono::steady_clock; @@ -441,6 +608,14 @@ namespace StandaloneStreamBenchmark Streamer.Stop(); std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2; } + if(bAuditStrateTransitions) + { + if(ResolvedLayout==nullptr||!CoreSnapshot.resolvedLayout()) + {Streamer.Stop();std::fprintf(stderr,"[StrateTransitionAudit] missing resolved-layout snapshot\n");return 2;} + const bool passed=AuditResolvedStrateTransitions(Shape,World,*ResolvedLayout); + Streamer.Stop(); + return passed?0:11; + } if(bAuditResolvedLayoutPassages) { if(ResolvedLayout==nullptr||!CoreSnapshot.resolvedLayout()) diff --git a/Standalone/Source/WorldAssemblyPrototype.h b/Standalone/Source/WorldAssemblyPrototype.h index e7d3eb3..b01a142 100644 --- a/Standalone/Source/WorldAssemblyPrototype.h +++ b/Standalone/Source/WorldAssemblyPrototype.h @@ -801,13 +801,21 @@ namespace StandaloneWorldAssemblyPrototype s.transitionBlendChunks=slot.Definition->TransitionBlendChunks; s.transitionWidthMicrometres=static_cast(s.transitionBlendChunks)*owner.StrateLayoutChunkSizeMicrometres; s.transitionWarpAmplitudeMicrometres=s.transitionMode=="Interleaved" - ?2*static_cast(owner.StrateLayoutChunkSizeMicrometres):0; - s.transitionInterpenetrationMicrometres=0; - s.transitionHonouredInCore=s.transitionMode=="Hard"; - s.transitionFallsBackToHard=s.transitionMode!="Hard"; + ?std::min(s.transitionWidthMicrometres/2, + static_cast(owner.StrateLayoutChunkSizeMicrometres)):0; + s.transitionInterpenetrationMicrometres=s.transitionMode=="Interleaved" + ?s.transitionWidthMicrometres + :s.transitionMode=="Gradient"?s.transitionWidthMicrometres/4:0; + s.transitionFallsBackToHard=s.transitionMode!="Hard" + &&s.transitionWidthMicrometres<=0; + s.transitionHonouredInCore=s.transitionMode=="Hard"||!s.transitionFallsBackToHard; s.transitionDispatchPath=s.transitionFallsBackToHard - ?"declared Gradient/Interleaved recorded; Core uses explicit Hard fallback with zero interpenetration" - :"declared Hard transition applied at the strate interface"; + ?"declared zero-width transition uses explicit Hard fallback" + :s.transitionMode=="Hard" + ?"declared Hard transition preserves the flat shared bedrock/ceiling interface" + :s.transitionMode=="Interleaved" + ?"shared composition overlaps both facing bands with deterministic domain-warped fingers inside the declared width" + :"shared composition moves both band faces together with the smooth declared-width transition field"; s.managerLookup="UVoxelStrateManager selects the slot from the director-resolved layout chunk width"; s.managerChunkWidthMicrometres=owner.StrateLayoutChunkSizeMicrometres; s.dispatchChunkWidthMicrometres=owner.StrateLayoutChunkSizeMicrometres;