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 0000000..2794f8f Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux29/captures/fi-floor-gradient-above.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-gradient-side-cutaway.jpg b/Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-gradient-side-cutaway.jpg new file mode 100644 index 0000000..162f8f5 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-gradient-side-cutaway.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-hard-side-cutaway.jpg b/Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-hard-side-cutaway.jpg new file mode 100644 index 0000000..d461965 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-hard-side-cutaway.jpg differ 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 0000000..219c9dd Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux29/captures/interface-interleaved-side-cutaway.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux29/captures/sw-ceiling-gradient-below.jpg b/Standalone/Research/WorldAssemblerM3/Linux29/captures/sw-ceiling-gradient-below.jpg new file mode 100644 index 0000000..c43f4eb Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux29/captures/sw-ceiling-gradient-below.jpg differ diff --git a/Standalone/Source/StreamBenchmarkHeadless.cpp b/Standalone/Source/StreamBenchmarkHeadless.cpp index 45b2811..39547cf 100644 --- a/Standalone/Source/StreamBenchmarkHeadless.cpp +++ b/Standalone/Source/StreamBenchmarkHeadless.cpp @@ -69,6 +69,9 @@ namespace bool AuditResolvedLayoutBoundaries=false; bool AuditResolvedLayoutPassages=false; bool AuditSurfaceClearance=false; + bool AuditStrateTransitions=false; + int TransitionModeOverride=-1; + int TransitionWidthChunksOverride=-1; }; bool ParseInteger(std::string_view text,int& output) @@ -103,6 +106,38 @@ namespace return true; } + bool OverrideTransitions(VFJson::Value& root,int mode,int widthChunks,std::string& error) + { + auto* object=std::get_if(&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;