diff --git a/CODEMAP.md b/CODEMAP.md index eed92ec..6a76ac7 100644 --- a/CODEMAP.md +++ b/CODEMAP.md @@ -150,6 +150,7 @@ remains `0`, and lateral regions remain gated off. | `../../VoxelForgeCore/Private/Manifest/VFCore_*.cpp` | Eighteen one-source UBT shims that include canonical files under `Standalone/`; they do not copy Core implementation. The API-boundary row includes `Core/src/VoxelForgeCore.cpp` directly. | | `../../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/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`. | | `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). | @@ -833,6 +834,15 @@ World assembler field types and `AuditHeightfieldBounds`, `AuditHeightfieldShelf `AuditHeightfieldRoute` are declared in [`Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`](Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h) and implemented in [`Standalone/Core/src/WorldAssemblerInternal.h`](Standalone/Core/src/WorldAssemblerInternal.h). +`FloatingIslandRecipeV1`, `FloatingIslandManifestV1`, `FloatingIslandFieldV1`, +`AuditFloatingIslandBounds`, `AuditFloatingIslandRoute`, and `AuditFloatingIslandThickness` are in the +same public header; their canonical implementation is +[`Standalone/Core/src/FloatingIslandsInternal.h`](Standalone/Core/src/FloatingIslandsInternal.h). +M3 round 2 added geometry version 2's thick crust, lobed roots, bounded detail and arched spans while +preserving version-1 field and hash behavior. The standalone audit driver remains under +[`Standalone/Research/WorldAssemblerM3/Round1/FloatingIslandsAudit.cpp`](Standalone/Research/WorldAssemblerM3/Round1/FloatingIslandsAudit.cpp); +round-2 matched capture source and results are under +[`Standalone/Research/WorldAssemblerM3/Round2/`](Standalone/Research/WorldAssemblerM3/Round2/). `RefineLod8NativeCap` in [`Standalone/Core/src/VoxelForgeCore.cpp`](Standalone/Core/src/VoxelForgeCore.cpp) replaces coarse LOD8 ceiling surfaces with the planned 2D cap field while retaining ceiling skirts. The standalone SurfaceWorld plan composer and capture/audit entry point live in diff --git a/Standalone/Core/API.md b/Standalone/Core/API.md index aee46af..a83d569 100644 --- a/Standalone/Core/API.md +++ b/Standalone/Core/API.md @@ -153,16 +153,24 @@ radius range, top flattening, taper, bounded contour warp, roughness, blend, con route constraints. `PlanFloatingIslandCluster` emits stable island and connector IDs, a connected cluster graph, conservative influence AABBs, and a centerline route. At least the primary chain is always planned; `islandDensityQ16` controls up to two optional side sites. Connectors carry a sharp-tag -ready bit for a later hybrid-mesher adapter. +ready bit for a later hybrid-mesher adapter. The schema accepts geometry version 1 and 2. Manifests +without a geometry version retain version-1 evaluation and canonical hashes. `FloatingIslandFieldV1::densitySubvoxels` evaluates the canonical island and connector field. `interval` returns unknown for boxes overlapping any influence AABB and a certified all-air range -outside them. AABBs include contour displacement, roughness, taper height and smoothing reach. +outside them. Geometry version 2 builds a flat walkable crust plus deterministic lobed rock roots, +keeps bounded oblique surface detail inside its certified envelope, and arches the connector +undersides. AABBs include root depth, contour displacement, detail, arch sag and smoothing reach. `AuditFloatingIslandBounds` checks sampled boxes and void beside connectors and includes a deliberately -shrunken island-bound negative control. `AuditFloatingIslandRoute` samples the player capsule and -support along the serialized route. The current field block is not yet composed into -`WorldAssemblyManifest` or the Unreal adapter; M3 round 1 proves the standalone Core representation. -The frozen owner evaluator is used as a visual and timing oracle, not as a hash target. +shrunken island-bound negative control. `AuditFloatingIslandThickness` reports each island's +`(topZ - bottomZ) / (2 * radius)` and checks a caller-supplied minimum; `AuditFloatingIslandRoute` +samples the player capsule and support along the serialized route. M3 round 2 passed at a minimum +depth-to-diameter ratio of 0.75 against a 0.70 threshold, while a 0.30 thin-pad control failed. + +The family is still separate from `WorldAssemblyManifest` and the Unreal adapter. M3 rounds 1–2 prove +the standalone Core representation, bounded geometry, route, determinism and matched legacy timing. +The frozen owner evaluator is a visual and timing oracle, not a hash target. Unreal import and cooked +collision are planned for M3 round 3. ## Queries and tiles diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 7699cd2..2d8b011 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -301,29 +301,36 @@ namespace VoxelForgeCore struct FloatingIslandRecipeV1 { std::uint32_t schemaVersion = 1; + // Profile 1 preserves the original shallow pads. Profile 2 adds a thick crust, + // bounded lobed roots and arched spans without changing the feature-family schema. + std::uint32_t geometryVersion = 2; std::uint32_t unitsPerVoxel = static_cast(WorldAssemblySubvoxelsPerVoxel); std::uint32_t seed = 0; std::int64_t layoutOriginXSubvoxels = 0; std::int64_t layoutOriginYSubvoxels = 0; - std::int64_t layoutCenterTopZSubvoxels = -128 * WorldAssemblySubvoxelsPerVoxel; - std::int64_t layoutMinimumZSubvoxels = -256 * WorldAssemblySubvoxelsPerVoxel; + std::int64_t layoutCenterTopZSubvoxels = -64 * WorldAssemblySubvoxelsPerVoxel; + std::int64_t layoutMinimumZSubvoxels = -128 * WorldAssemblySubvoxelsPerVoxel; std::int64_t layoutMaximumZSubvoxels = 0; - std::uint32_t primaryIslandCount = 5; + std::uint32_t primaryIslandCount = 3; std::int64_t islandDensityQ16 = 1ll << 15; - std::int64_t spacingSubvoxels = 112 * WorldAssemblySubvoxelsPerVoxel; - std::int64_t minimumRadiusSubvoxels = 40 * WorldAssemblySubvoxelsPerVoxel; - std::int64_t maximumRadiusSubvoxels = 48 * WorldAssemblySubvoxelsPerVoxel; + // Legacy owner lengths use 12.5 cm voxels; Core uses 25 cm, so halve the owner lengths. + // Les longueurs owner utilisent des voxels de 12,5 cm ; Core utilise 25 cm, donc les diviser par deux. + std::int64_t spacingSubvoxels = 56 * WorldAssemblySubvoxelsPerVoxel; + std::int64_t minimumRadiusSubvoxels = 12 * WorldAssemblySubvoxelsPerVoxel; + std::int64_t maximumRadiusSubvoxels = 24 * WorldAssemblySubvoxelsPerVoxel; std::int64_t thicknessRatioQ16 = 39322; // 0.60 - std::int64_t verticalJitterQ16 = 26214; // 0.40 - std::int64_t topFlattenQ16 = 51118; // 0.78 + std::int64_t verticalJitterQ16 = 39322; // 0.60 + std::int64_t topFlattenQ16 = 39322; // 0.60 std::int64_t taperEndMinimumQ16 = 29491; // 0.45 std::int64_t taperEndMaximumQ16 = 45875; // 0.70 - std::int64_t blendRadiusSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel; - std::int64_t contourWarpSubvoxels = 15 * WorldAssemblySubvoxelsPerVoxel; - std::int64_t roughnessSubvoxels = WorldAssemblySubvoxelsPerVoxel / 2; - std::int64_t boundarySealSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel; - std::int64_t bridgeHalfWidthSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel; - std::int64_t bridgeThicknessSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel; + std::int64_t crustDepthBonusQ16 = 9830; // 0.15 radius, profile 2 only + std::int64_t rootDepthRatioQ16 = 49152; // 0.75 radius, profile 2 only + std::int64_t blendRadiusSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel; + std::int64_t contourWarpSubvoxels = 6 * WorldAssemblySubvoxelsPerVoxel + 77; // 6.3 + std::int64_t roughnessSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel / 2; + std::int64_t boundarySealSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel; + std::int64_t bridgeHalfWidthSubvoxels = 6 * WorldAssemblySubvoxelsPerVoxel; + std::int64_t bridgeThicknessSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel; WorldAssemblyRouteConstraintsV1 routeConstraints; std::uint64_t contentHash = 0; }; @@ -409,6 +416,16 @@ namespace VoxelForgeCore bool passed = false; }; + struct FloatingIslandThicknessAuditResult + { + std::uint64_t islandsTested = 0; + std::uint64_t islandsBelowMinimum = 0; + std::uint64_t minimumIslandStableId = 0; + double minimumDepthToDiameterRatio = 0.0; + double requiredDepthToDiameterRatio = 0.70; + bool passed = false; + }; + struct FloatingIslandInfluenceBounds { bool finite = false; @@ -451,6 +468,8 @@ namespace VoxelForgeCore std::uint64_t seed = 0x49534c414e444231ull); VFCORE_API FloatingIslandRouteAuditResult AuditFloatingIslandRoute( const FloatingIslandFieldV1& field, std::int64_t routeOffsetSubvoxels = 0); + VFCORE_API FloatingIslandThicknessAuditResult AuditFloatingIslandThickness( + const FloatingIslandPlanV1& plan, double minimumDepthToDiameterRatio = 0.70); struct HeightfieldPointSample { diff --git a/Standalone/Core/src/FloatingIslandsInternal.h b/Standalone/Core/src/FloatingIslandsInternal.h index 35ec1ac..e9e339a 100644 --- a/Standalone/Core/src/FloatingIslandsInternal.h +++ b/Standalone/Core/src/FloatingIslandsInternal.h @@ -174,6 +174,12 @@ namespace floating_island_detail HashWord(hash, static_cast(recipe.topFlattenQ16), 8); HashWord(hash, static_cast(recipe.taperEndMinimumQ16), 8); HashWord(hash, static_cast(recipe.taperEndMaximumQ16), 8); + if (recipe.geometryVersion >= 2) + { + HashWord(hash, recipe.geometryVersion, 4); + HashWord(hash, static_cast(recipe.crustDepthBonusQ16), 8); + HashWord(hash, static_cast(recipe.rootDepthRatioQ16), 8); + } HashWord(hash, static_cast(recipe.blendRadiusSubvoxels), 8); HashWord(hash, static_cast(recipe.contourWarpSubvoxels), 8); HashWord(hash, static_cast(recipe.roughnessSubvoxels), 8); @@ -233,7 +239,7 @@ namespace floating_island_detail inline bool ValidateRecipe(const FloatingIslandRecipeV1& recipe, std::string& error) { - const std::array values = { + const std::array values = { recipe.layoutOriginXSubvoxels, recipe.layoutOriginYSubvoxels, recipe.layoutCenterTopZSubvoxels, recipe.layoutMinimumZSubvoxels, recipe.layoutMaximumZSubvoxels, recipe.islandDensityQ16, @@ -241,22 +247,28 @@ namespace floating_island_detail recipe.maximumRadiusSubvoxels, recipe.thicknessRatioQ16, recipe.verticalJitterQ16, recipe.topFlattenQ16, recipe.taperEndMinimumQ16, recipe.taperEndMaximumQ16, + recipe.crustDepthBonusQ16, recipe.rootDepthRatioQ16, recipe.blendRadiusSubvoxels, recipe.contourWarpSubvoxels, recipe.roughnessSubvoxels, recipe.boundarySealSubvoxels, recipe.bridgeHalfWidthSubvoxels, recipe.bridgeThicknessSubvoxels, recipe.routeConstraints.capsuleRadiusSubvoxels, recipe.routeConstraints.capsuleHeightSubvoxels, recipe.routeConstraints.maximumGradeQ16}; - if (recipe.schemaVersion != 1 || recipe.unitsPerVoxel != Q + if (recipe.schemaVersion != 1 || (recipe.geometryVersion != 1 && recipe.geometryVersion != 2) + || (recipe.geometryVersion == 1 + && (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0)) + || recipe.unitsPerVoxel != Q || recipe.primaryIslandCount < 3 || recipe.primaryIslandCount > 9 || recipe.layoutMaximumZSubvoxels <= recipe.layoutMinimumZSubvoxels || recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels > 2048 * Q || std::any_of(values.begin(), values.end(), [](auto value) { return !ValidScalar(value); })) { error = "floating-island recipe version, scale, layout or scalar range is invalid"; return false; } const auto& constraints = recipe.routeConstraints; + const std::int64_t minimumSpacing = recipe.geometryVersion >= 2 ? 32 * Q : 64 * Q; + const std::int64_t minimumRadius = recipe.geometryVersion >= 2 ? 12 * Q : 24 * Q; if (recipe.islandDensityQ16 < 6554 || recipe.islandDensityQ16 > (1ll << 15) - || recipe.spacingSubvoxels < 64 * Q || recipe.spacingSubvoxels > 256 * Q - || recipe.minimumRadiusSubvoxels < 24 * Q + || recipe.spacingSubvoxels < minimumSpacing || recipe.spacingSubvoxels > 256 * Q + || recipe.minimumRadiusSubvoxels < minimumRadius || recipe.maximumRadiusSubvoxels < recipe.minimumRadiusSubvoxels || recipe.maximumRadiusSubvoxels > 64 * Q || recipe.thicknessRatioQ16 < 29491 || recipe.thicknessRatioQ16 > 49152 @@ -264,6 +276,9 @@ namespace floating_island_detail || recipe.topFlattenQ16 < 39322 || recipe.topFlattenQ16 > 52429 || recipe.taperEndMinimumQ16 < 26214 || recipe.taperEndMaximumQ16 > 52429 || recipe.taperEndMinimumQ16 > recipe.taperEndMaximumQ16 + || (recipe.geometryVersion >= 2 + && (recipe.crustDepthBonusQ16 < 6554 || recipe.crustDepthBonusQ16 > 16384 + || recipe.rootDepthRatioQ16 < 32768 || recipe.rootDepthRatioQ16 > 65536)) || recipe.blendRadiusSubvoxels < 2 * Q || recipe.blendRadiusSubvoxels > 6 * Q || recipe.contourWarpSubvoxels <= 0 || recipe.contourWarpSubvoxels > recipe.maximumRadiusSubvoxels / 2 @@ -325,18 +340,30 @@ namespace floating_island_detail } inline double Roughness(const FloatingIslandRecipeV1& recipe, - const FloatingIslandAnchorV1& island, - double x, double y, double z) + std::uint64_t featureId, double x, double y, double z) { - const double phase = static_cast(Roll(recipe.seed, island.stableId, 0x524f5547ull) + const double phase = static_cast(Roll(recipe.seed, featureId, 0x524f5547ull) & 0xffffffffull) * (2.0 * Pi / 4294967296.0); const double xv = x / static_cast(Q); const double yv = y / static_cast(Q); const double zv = z / static_cast(Q); + if (recipe.geometryVersion < 2) + { + const double oldNoise = 0.52 * std::sin(xv * 0.071 + yv * 0.053 + zv * 0.089 + phase) + + 0.31 * std::sin(xv * 0.127 - yv * 0.097 + zv * 0.061 - phase * 0.73) + + 0.17 * std::cos(xv * 0.043 + yv * 0.139 - zv * 0.113 + phase * 1.39); + return static_cast(recipe.roughnessSubvoxels) * oldNoise; + } + // Four oblique bands span about 7 m down to 0.9 m at 25 cm per voxel. + // Their absolute weights sum to one, keeping displacement inside the 3-voxel envelope. const double noise = 0.52 * std::sin(xv * 0.071 + yv * 0.053 + zv * 0.089 + phase) + 0.31 * std::sin(xv * 0.127 - yv * 0.097 + zv * 0.061 - phase * 0.73) + 0.17 * std::cos(xv * 0.043 + yv * 0.139 - zv * 0.113 + phase * 1.39); - return static_cast(recipe.roughnessSubvoxels) * noise; + const double fine = 0.46 * std::sin(xv * 0.22 + yv * 0.31 - zv * 0.17 + phase * 0.41) + + 0.32 * std::cos(xv * 0.38 - yv * 0.21 + zv * 0.29 - phase * 0.67) + + 0.22 * std::sin(xv * 0.74 + yv * 0.53 + zv * 0.61 + phase * 1.17); + return static_cast(recipe.roughnessSubvoxels) + * std::clamp(0.42 * noise + 0.58 * fine, -1.0, 1.0); } inline double IslandTopSurface(const FloatingIslandRecipeV1& recipe, @@ -355,31 +382,138 @@ namespace floating_island_detail return static_cast(island.topZSubvoxels) - drop; } + inline std::int64_t ScaledQ16(std::int64_t value, std::int64_t scaleQ16) + { return (value * scaleQ16 + Q16 / 2) / Q16; } + + inline std::int64_t IslandCrustBottom(const FloatingIslandRecipeV1& recipe, + const FloatingIslandAnchorV1& island) + { + const std::int64_t ratio = recipe.thicknessRatioQ16 + + (recipe.geometryVersion >= 2 ? recipe.crustDepthBonusQ16 : 0); + return island.topZSubvoxels - ScaledQ16(island.radiusSubvoxels, ratio); + } + + inline std::int64_t IslandBottom(const FloatingIslandRecipeV1& recipe, + std::int64_t topZ, std::int64_t radius) + { + const std::int64_t crustRatio = recipe.thicknessRatioQ16 + + (recipe.geometryVersion >= 2 ? recipe.crustDepthBonusQ16 : 0); + const std::int64_t rootRatio = recipe.geometryVersion >= 2 + ? recipe.rootDepthRatioQ16 : 0; + return topZ - ScaledQ16(radius, crustRatio) - ScaledQ16(radius, rootRatio); + } + + inline std::int32_t RootCount(const FloatingIslandRecipeV1& recipe, + const FloatingIslandAnchorV1& island) + { + return 3 + static_cast(Roll(recipe.seed, island.stableId, + 0x524f4f54434e54ull) & 1ull); + } + + inline double UnitHash(std::uint64_t hash) + { return static_cast(hash & 0xffffull) / 65535.0; } + + inline double RootLobeDensity(const FloatingIslandRecipeV1& recipe, + const FloatingIslandAnchorV1& island, + std::int32_t rootIndex, double x, double y, double z) + { + constexpr std::array, 4> pattern{{ + {{-0.92, -0.12}}, {{0.28, 0.98}}, {{0.83, -0.42}}, {{-0.16, -0.94}}}}; + const std::uint64_t salt = 0x524f4f540000ull + static_cast(rootIndex); + const std::uint64_t roll = Roll(recipe.seed, island.stableId, salt); + const double radius = static_cast(island.radiusSubvoxels); + const double rootDepth = radius * Q16ToDouble(recipe.rootDepthRatioQ16); + const double crustBottom = static_cast(IslandCrustBottom(recipe, island)); + const double orbit = radius * (0.17 + 0.07 * UnitHash(roll)); + const double rootX = static_cast(island.centerSubvoxels[0]) + + pattern[static_cast(rootIndex)][0] * orbit; + const double rootY = static_cast(island.centerSubvoxels[1]) + + pattern[static_cast(rootIndex)][1] * orbit; + const double shortRootVariation = UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x4c454eull)); + const double lengthRatio = rootIndex == 0 ? 1.0 + : Q16ToDouble(island.taperEndQ16) * (0.82 + 0.18 * shortRootVariation); + const double lowerDepth = rootDepth * lengthRatio; + const double embedDepth = rootDepth * (rootIndex == 0 ? 0.30 : 0.22); + const double verticalRadius = 0.5 * (lowerDepth + embedDepth); + const double centerZ = crustBottom + 0.5 * (embedDepth - lowerDepth); + const double rx = radius * (0.15 + 0.055 * UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x5258ull))); + const double ry = radius * (0.15 + 0.065 * UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x5259ull))); + const double q = (x - rootX) * (x - rootX) / (rx * rx) + + (y - rootY) * (y - rootY) / (ry * ry) + + (z - centerZ) * (z - centerZ) / (verticalRadius * verticalRadius); + return (std::sqrt(std::max(q, 0.0)) - 1.0) * std::min({rx, ry, verticalRadius}); + } + + inline double ApplySurfaceDetail(const FloatingIslandRecipeV1& recipe, + std::uint64_t featureId, double structural, + double x, double y, double z, double detailScale = 1.0) + { + if (recipe.geometryVersion < 2) + return structural + Roughness(recipe, featureId, x, y, z); + const double amplitude = static_cast(recipe.roughnessSubvoxels) * detailScale; + const double band = 2.0 * amplitude; + if (!(amplitude > 0.0) || std::abs(structural) >= band) return structural; + const double fade = 1.0 - SmoothStep(std::abs(structural) / band); + return structural + Roughness(recipe, featureId, x, y, z) * detailScale * fade; + } + inline double IslandDensity(const FloatingIslandRecipeV1& recipe, const FloatingIslandAnchorV1& island, double x, double y, double z) { + if (recipe.geometryVersion < 2) + { + const double dx = x - static_cast(island.centerSubvoxels[0]); + const double dy = y - static_cast(island.centerSubvoxels[1]); + const double radial = std::hypot(dx, dy); + const double topZ = static_cast(island.topZSubvoxels); + const double bottomZ = static_cast(island.bottomZSubvoxels); + const double verticalSpan = std::max(topZ - bottomZ, 1.0); + const double height = std::clamp((z - bottomZ) / verticalSpan, 0.0, 1.0); + const double taperEnd = Q16ToDouble(island.taperEndQ16); + const double taper = SmoothStep(std::clamp(height / std::max(taperEnd, 0.01), 0.0, 1.0)); + const double effectiveRadius = std::max(0.0, + static_cast(island.radiusSubvoxels) + LobeOffset(recipe, island, dx, dy, z)); + const double envelope = effectiveRadius * taper; + const double top = IslandTopSurface(recipe, island, + static_cast(island.centerSubvoxels[0]) + dx, + static_cast(island.centerSubvoxels[1]) + dy); + return ApplySurfaceDetail(recipe, island.stableId, + std::max({z - top, radial - envelope, bottomZ - z}), x, y, z); + } + const double dx = x - static_cast(island.centerSubvoxels[0]); const double dy = y - static_cast(island.centerSubvoxels[1]); const double radial = std::hypot(dx, dy); const double topZ = static_cast(island.topZSubvoxels); - const double bottomZ = static_cast(island.bottomZSubvoxels); - const double verticalSpan = std::max(topZ - bottomZ, 1.0); - const double height = std::clamp((z - bottomZ) / verticalSpan, 0.0, 1.0); - const double taperEnd = Q16ToDouble(island.taperEndQ16); - const double taper = SmoothStep(std::clamp(height / std::max(taperEnd, 0.01), 0.0, 1.0)); + const double radius = static_cast(island.radiusSubvoxels); const double effectiveRadius = std::max(0.0, - static_cast(island.radiusSubvoxels) + LobeOffset(recipe, island, dx, dy, z)); - const double envelope = effectiveRadius * taper; + radius + LobeOffset(recipe, island, dx, dy, z)); + const double edge = std::clamp(radial / std::max(effectiveRadius, 1.0), 0.0, 1.0); + const double lowerSurface = static_cast(IslandCrustBottom(recipe, island)) + + radius * 0.16 * edge * edge; const double top = IslandTopSurface(recipe, island, static_cast(island.centerSubvoxels[0]) + dx, static_cast(island.centerSubvoxels[1]) + dy); - const double sdf = std::max({z - top, radial - envelope, bottomZ - z}); - return sdf + Roughness(recipe, island, x, y, z); + const double topDistance = z - top; + const double sideDistance = radial - effectiveRadius; + const double undersideDistance = lowerSurface - z; + double sdf = std::max({topDistance, sideDistance, undersideDistance}); + const std::int32_t rootCount = RootCount(recipe, island); + const double rootBlend = std::clamp(radius * 0.12, 2.0 * Q, 5.0 * Q); + for (std::int32_t root = 0; root < rootCount; ++root) + sdf = SmoothMin(sdf, RootLobeDensity(recipe, island, root, x, y, z), rootBlend); + const double topDetailScale = topDistance >= std::max(sideDistance, undersideDistance) + || z >= static_cast(IslandCrustBottom(recipe, island)) + radius * 0.45 + ? 0.25 : 1.0; + return ApplySurfaceDetail(recipe, island.stableId, sdf, x, y, z, topDetailScale); } - inline double ConnectorDensity(const FloatingIslandConnectorV1& edge, - double x, double y, double z) + inline double ConnectorDensity(const FloatingIslandRecipeV1& recipe, + const FloatingIslandConnectorV1& edge, + double x, double y, double z, + bool* topSurface = nullptr, + double* alongFraction = nullptr) { const double ax = static_cast(edge.startSubvoxels[0]); const double ay = static_cast(edge.startSubvoxels[1]); @@ -397,9 +531,15 @@ namespace floating_island_detail const double outside = std::hypot(std::max(qx, 0.0), std::max(qy, 0.0)); const double horizontal = outside + std::min(std::max(qx, qy), 0.0); const double t = std::clamp(along / length, 0.0, 1.0); + if (alongFraction) *alongFraction = t; const double deck = az + (static_cast(edge.endSubvoxels[2]) - az) * t; - return std::max({horizontal, z - deck, - deck - static_cast(edge.thicknessSubvoxels) - z}); + const double arch = recipe.geometryVersion >= 2 + ? 2.0 * static_cast(edge.thicknessSubvoxels) * (4.0 * t * (1.0 - t)) + : 0.0; + const double upper = z - deck; + const double lower = deck - static_cast(edge.thicknessSubvoxels) - arch - z; + if (topSurface) *topSurface = upper >= std::max(horizontal, lower); + return std::max({horizontal, upper, lower}); } inline std::int64_t FieldDensity(const FloatingIslandRecipeV1& recipe, @@ -416,8 +556,16 @@ namespace floating_island_detail for (const auto& edge : plan.clusterEdges) { if (edge.fromIslandId != island.stableId) continue; - group = SmoothMin(group, ConnectorDensity(edge, - static_cast(x), static_cast(y), static_cast(z)), blend); + bool topSurface = false; + double alongFraction = 0.0; + double connector = ConnectorDensity(recipe, edge, + static_cast(x), static_cast(y), static_cast(z), + &topSurface, &alongFraction); + if (recipe.geometryVersion >= 2) + connector = ApplySurfaceDetail(recipe, edge.stableId, connector, + static_cast(x), static_cast(y), static_cast(z), + topSurface || alongFraction < 0.12 || alongFraction > 0.88 ? 0.25 : 1.0); + group = SmoothMin(group, connector, blend); } result = std::min(result, group); } @@ -439,11 +587,14 @@ namespace floating_island_detail FloatingIslandConnectorV1& edge) { const std::int64_t pad = recipe.blendRadiusSubvoxels + recipe.roughnessSubvoxels; + const std::int64_t archDepth = recipe.geometryVersion >= 2 + ? 2 * edge.thicknessSubvoxels : 0; const std::int64_t xyPad = edge.halfWidthSubvoxels + pad; edge.influenceMinimumSubvoxels = { std::min(edge.startSubvoxels[0], edge.endSubvoxels[0]) - xyPad, std::min(edge.startSubvoxels[1], edge.endSubvoxels[1]) - xyPad, std::min(edge.startSubvoxels[2], edge.endSubvoxels[2]) - edge.thicknessSubvoxels - pad}; + edge.influenceMinimumSubvoxels[2] -= archDepth; edge.influenceMaximumSubvoxels = { std::max(edge.startSubvoxels[0], edge.endSubvoxels[0]) + xyPad, std::max(edge.startSubvoxels[1], edge.endSubvoxels[1]) + xyPad, @@ -483,6 +634,10 @@ namespace floating_island_detail || island.taperEndQ16 > recipe.taperEndMaximumQ16 || island.centerSubvoxels[2] != island.topZSubvoxels - island.topHalfSubvoxels) { error = "floating-island anchor has an invalid or unstable identity or profile"; return false; } + if (recipe.geometryVersion >= 2 + && static_cast(island.topZSubvoxels - island.bottomZSubvoxels) + / (2.0 * static_cast(island.radiusSubvoxels)) < 0.70) + { error = "floating-island crust/root depth is below its minimum width ratio"; return false; } for (std::size_t axis = 0; axis < 3; ++axis) { if (!ValidScalar(island.centerSubvoxels[axis]) @@ -527,6 +682,7 @@ namespace floating_island_detail || edge.influenceMaximumSubvoxels[1] < std::max(edge.startSubvoxels[1], edge.endSubvoxels[1]) + xyPad || edge.influenceMinimumSubvoxels[2] > std::min(edge.startSubvoxels[2], edge.endSubvoxels[2]) - edge.thicknessSubvoxels - pad + - (recipe.geometryVersion >= 2 ? 2 * edge.thicknessSubvoxels : 0) || edge.influenceMaximumSubvoxels[2] < std::max(edge.startSubvoxels[2], edge.endSubvoxels[2]) + pad) { error = "floating-island connector bound omits its swept bridge, blend or thickness"; return false; } adjacency[edge.fromIslandId].push_back(edge.toIslandId); @@ -593,8 +749,7 @@ namespace floating_island_detail * radiusRoll / 65535; island.topHalfSubvoxels = std::max(1, island.radiusSubvoxels / 5); island.topZSubvoxels = topZ; - island.bottomZSubvoxels = topZ - - island.radiusSubvoxels * recipe.thicknessRatioQ16 / Q16; + island.bottomZSubvoxels = IslandBottom(recipe, topZ, island.radiusSubvoxels); island.centerSubvoxels = {x, y, topZ - island.topHalfSubvoxels}; island.taperEndQ16 = recipe.taperEndMinimumQ16 + (recipe.taperEndMaximumQ16 - recipe.taperEndMinimumQ16) @@ -619,14 +774,16 @@ namespace floating_island_detail // Land each connector well inside the broad top so the full capsule footprint remains // supported across the bridge-to-island transition, including bounded surface roughness. // Les connecteurs recouvrent le plateau pour soutenir toute la capsule au raccord. - const double fromReach = static_cast(from.radiusSubvoxels) * 0.62; - const double toReach = static_cast(to.radiusSubvoxels) * 0.62; + const double landingFraction = recipe.geometryVersion >= 2 ? 0.38 : 0.62; + const double fromReach = static_cast(from.radiusSubvoxels) * landingFraction; + const double toReach = static_cast(to.radiusSubvoxels) * landingFraction; const double sx = from.centerSubvoxels[0] + ux * fromReach; const double sy = from.centerSubvoxels[1] + uy * fromReach; const double ex = to.centerSubvoxels[0] - ux * toReach; const double ey = to.centerSubvoxels[1] - uy * toReach; - const double sz = IslandTopSurface(recipe, from, sx, sy); - const double ez = IslandTopSurface(recipe, to, ex, ey); + const double routeDeckLift = recipe.geometryVersion >= 2 ? 1.0 * Q : 0.0; + const double sz = IslandTopSurface(recipe, from, sx, sy) + routeDeckLift; + const double ez = IslandTopSurface(recipe, to, ex, ey) + routeDeckLift; edge.startSubvoxels = {RoundQ(sx), RoundQ(sy), RoundQ(sz)}; edge.endSubvoxels = {RoundQ(ex), RoundQ(ey), RoundQ(ez)}; edge.halfWidthSubvoxels = recipe.bridgeHalfWidthSubvoxels; @@ -734,6 +891,23 @@ std::optional FloatingIslandManifestV1::FromJson( const auto& recipeValue = root.Get("recipe"); const auto& planValue = root.Get("plan"); std::uint64_t manifestHash = 0, recipeHash = 0, planHash = 0; + const auto& geometryVersionValue = recipeValue.Get("geometry_version"); + if (geometryVersionValue.IsNumber()) + { + if (!floating_island_detail::ReadU32(geometryVersionValue, manifest.recipe.geometryVersion) + || !floating_island_detail::ReadInteger(recipeValue.Get("crust_depth_bonus_q16"), + manifest.recipe.crustDepthBonusQ16) + || !floating_island_detail::ReadInteger(recipeValue.Get("root_depth_ratio_q16"), + manifest.recipe.rootDepthRatioQ16)) + { error = "floating-island geometry profile is incomplete"; return std::nullopt; } + } + else + { + // Frozen M3 round-1 manifests omit profile metadata and retain their original field. + manifest.recipe.geometryVersion = 1; + manifest.recipe.crustDepthBonusQ16 = 0; + manifest.recipe.rootDepthRatioQ16 = 0; + } if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema) || !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash) || !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion) @@ -867,8 +1041,12 @@ std::string FloatingIslandManifestV1::ToJson() const << ",\"vertical_jitter_q16\":" << recipe.verticalJitterQ16 << ",\"top_flatten_q16\":" << recipe.topFlattenQ16 << ",\"taper_end_min_q16\":" << recipe.taperEndMinimumQ16 - << ",\"taper_end_max_q16\":" << recipe.taperEndMaximumQ16 - << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels + << ",\"taper_end_max_q16\":" << recipe.taperEndMaximumQ16; + if (recipe.geometryVersion >= 2) + out << ",\"geometry_version\":" << recipe.geometryVersion + << ",\"crust_depth_bonus_q16\":" << recipe.crustDepthBonusQ16 + << ",\"root_depth_ratio_q16\":" << recipe.rootDepthRatioQ16; + out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels << ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels << ",\"roughness_q\":" << recipe.roughnessSubvoxels << ",\"boundary_seal_q\":" << recipe.boundarySealSubvoxels @@ -961,11 +1139,40 @@ std::optional PlanFloatingIslandCluster( radii[i] = recipe.minimumRadiusSubvoxels + (recipe.maximumRadiusSubvoxels - recipe.minimumRadiusSubvoxels) * radiusRoll / 65535; } + std::vector> primaryXY(count); + const std::int64_t rowJitter = recipe.spacingSubvoxels / 10; + for (std::uint32_t i = 0; i < count; ++i) + { + const std::uint64_t site = 0x100000000ull + i; + if (recipe.geometryVersion < 2) + { + const std::int64_t yOffset = i == middle ? 0 + : floating_island_detail::SignedRange(recipe.seed, site, 0x594a4954544552ull, rowJitter); + primaryXY[i] = {recipe.layoutOriginXSubvoxels + - static_cast(middle) * recipe.spacingSubvoxels + + static_cast(i) * recipe.spacingSubvoxels, + recipe.layoutOriginYSubvoxels + yOffset}; + } + else + { + const std::int64_t along = (static_cast(i) + - static_cast(middle)) * recipe.spacingSubvoxels * 7 / 10; + const std::int64_t side = (i % 2 == 0 ? 1 : -1) * recipe.spacingSubvoxels / 3; + const std::int64_t jitter = floating_island_detail::SignedRange( + recipe.seed, site, 0x594a4954544552ull, rowJitter / 2); + primaryXY[i] = {recipe.layoutOriginXSubvoxels + along, + recipe.layoutOriginYSubvoxels + side + jitter}; + } + } const double maxGrade = floating_island_detail::Q16ToDouble(recipe.routeConstraints.maximumGradeQ16); const double levelFactor = floating_island_detail::Q16ToDouble(recipe.verticalJitterQ16) * 1.5; const auto riseLimit = [&](std::uint32_t a, std::uint32_t b) { - const double gap = static_cast(recipe.spacingSubvoxels) + const double centerDistance = recipe.geometryVersion < 2 + ? static_cast(recipe.spacingSubvoxels) + : std::hypot(static_cast(primaryXY[b][0] - primaryXY[a][0]), + static_cast(primaryXY[b][1] - primaryXY[a][1])); + const double gap = centerDistance - 0.78 * (static_cast(radii[a]) + static_cast(radii[b])); return std::max(0.0, gap * maxGrade * levelFactor); }; @@ -983,22 +1190,16 @@ std::optional PlanFloatingIslandCluster( static_cast(std::ceil(riseLimit(static_cast(i), static_cast(i + 1))))); } - const std::int64_t rowJitter = recipe.spacingSubvoxels / 10; - const std::int64_t startX = recipe.layoutOriginXSubvoxels - - static_cast(middle) * recipe.spacingSubvoxels; for (std::uint32_t i = 0; i < count; ++i) { const std::uint64_t site = 0x100000000ull + i; - const std::int64_t yOffset = i == middle ? 0 - : floating_island_detail::SignedRange(recipe.seed, site, 0x594a4954544552ull, rowJitter); auto island = floating_island_detail::MakeIsland(recipe, i, - startX + static_cast(i) * recipe.spacingSubvoxels, - recipe.layoutOriginYSubvoxels + yOffset, tops[i], site); + primaryXY[i][0], primaryXY[i][1], tops[i], site); island.radiusSubvoxels = radii[i]; island.topHalfSubvoxels = std::max(1, island.radiusSubvoxels / 5); island.centerSubvoxels[2] = island.topZSubvoxels - island.topHalfSubvoxels; - island.bottomZSubvoxels = island.topZSubvoxels - - island.radiusSubvoxels * recipe.thicknessRatioQ16 / floating_island_detail::Q16; + island.bottomZSubvoxels = floating_island_detail::IslandBottom(recipe, + island.topZSubvoxels, island.radiusSubvoxels); floating_island_detail::SetIslandBounds(recipe, island); islands.push_back(island); } @@ -1016,26 +1217,41 @@ std::optional PlanFloatingIslandCluster( const std::uint64_t site = 0x300000000ull + branch; if (floating_island_detail::UnitQ16(recipe.seed, site, 0x53495445524f4c4cull) >= static_cast(recipe.islandDensityQ16)) continue; - const std::uint32_t hostIndex = branch == 0 ? 0 : count - 1; + const std::uint32_t hostIndex = recipe.geometryVersion < 2 + ? (branch == 0 ? 0 : count - 1) + : (branch == 0 ? middle : std::min(middle + 1, count - 1)); const auto& host = islands[hostIndex]; + const std::int64_t xOffset = recipe.geometryVersion < 2 ? 0 + : (branch == 0 ? -1 : 1) * recipe.spacingSubvoxels * 9 / 20; const std::int64_t yOffset = branch == 0 ? -recipe.spacingSubvoxels : recipe.spacingSubvoxels; const std::int64_t gap = recipe.spacingSubvoxels - static_cast( std::llround(0.78 * static_cast(host.radiusSubvoxels + recipe.minimumRadiusSubvoxels))); const std::int64_t rise = static_cast(std::ceil(std::max(0.0, static_cast(gap) * maxGrade * levelFactor))); - const std::int64_t topZ = host.topZSubvoxels - + floating_island_detail::SignedRange(recipe.seed, site, 0x4252414e43484c56ull, rise); + const std::int64_t sideRise = recipe.geometryVersion < 2 ? rise + : std::min(24 * floating_island_detail::Q, + (recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels) + * recipe.verticalJitterQ16 / (8 * floating_island_detail::Q16)); + std::int64_t topZ = host.topZSubvoxels + + floating_island_detail::SignedRange(recipe.seed, site, 0x4252414e43484c56ull, sideRise); const std::uint32_t radiusRoll = floating_island_detail::UnitQ16(recipe.seed, site, 0x524144495553ull); const std::int64_t branchRadius = recipe.minimumRadiusSubvoxels + (recipe.maximumRadiusSubvoxels - recipe.minimumRadiusSubvoxels) * radiusRoll / 65535; + if (recipe.geometryVersion >= 2) + { + const std::int64_t maximumTop = recipe.layoutMaximumZSubvoxels + - recipe.boundarySealSubvoxels - branchRadius / 5; + const std::int64_t minimumTop = recipe.layoutMinimumZSubvoxels + + recipe.boundarySealSubvoxels + floating_island_detail::IslandBottom(recipe, 0, branchRadius) * -1; + topZ = std::clamp(topZ, minimumTop, maximumTop); + } auto island = floating_island_detail::MakeIsland(recipe, recipe.primaryIslandCount + branch, - host.centerSubvoxels[0], host.centerSubvoxels[1] + yOffset, topZ, site); + host.centerSubvoxels[0] + xOffset, host.centerSubvoxels[1] + yOffset, topZ, site); island.radiusSubvoxels = branchRadius; island.topHalfSubvoxels = std::max(1, branchRadius / 5); island.centerSubvoxels[2] = topZ - island.topHalfSubvoxels; - island.bottomZSubvoxels = topZ - branchRadius * recipe.thicknessRatioQ16 - / floating_island_detail::Q16; + island.bottomZSubvoxels = floating_island_detail::IslandBottom(recipe, topZ, branchRadius); floating_island_detail::SetIslandBounds(recipe, island); const std::uint64_t islandId = island.stableId; islands.push_back(island); @@ -1074,7 +1290,8 @@ std::optional PlanFloatingIslandCluster( point.stableFeatureId = floating_island_detail::StableId(recipe.seed, 0x500000000ull + routeOrdinal, 0x524f555445ull); point.positionSubvoxels = {x, y, *surface + capsuleHalf - + recipe.routeConstraints.minimumClearanceSubvoxels}; + + recipe.routeConstraints.minimumClearanceSubvoxels + + (recipe.geometryVersion >= 2 ? floating_island_detail::Q / 8 : 0)}; point.radiusSubvoxels = routeRadius; route.push_back(point); ++routeOrdinal; @@ -1471,4 +1688,37 @@ FloatingIslandRouteAuditResult AuditFloatingIslandRoute(const FloatingIslandFiel return result; } +FloatingIslandThicknessAuditResult AuditFloatingIslandThickness( + const FloatingIslandPlanV1& plan, double minimumDepthToDiameterRatio) +{ + FloatingIslandThicknessAuditResult result; + result.requiredDepthToDiameterRatio = minimumDepthToDiameterRatio; + if (plan.islands.empty() || !std::isfinite(minimumDepthToDiameterRatio) + || minimumDepthToDiameterRatio <= 0.0) return result; + result.minimumDepthToDiameterRatio = std::numeric_limits::infinity(); + for (const auto& island : plan.islands) + { + ++result.islandsTested; + if (island.radiusSubvoxels <= 0 || island.topZSubvoxels <= island.bottomZSubvoxels) + { + ++result.islandsBelowMinimum; + result.minimumDepthToDiameterRatio = 0.0; + result.minimumIslandStableId = island.stableId; + continue; + } + const double ratio = static_cast(island.topZSubvoxels - island.bottomZSubvoxels) + / (2.0 * static_cast(island.radiusSubvoxels)); + if (ratio < result.minimumDepthToDiameterRatio) + { + result.minimumDepthToDiameterRatio = ratio; + result.minimumIslandStableId = island.stableId; + } + if (ratio + 1.0e-12 < minimumDepthToDiameterRatio) ++result.islandsBelowMinimum; + } + if (!std::isfinite(result.minimumDepthToDiameterRatio)) result.minimumDepthToDiameterRatio = 0.0; + result.passed = result.islandsTested == plan.islands.size() + && result.islandsBelowMinimum == 0; + return result; +} + } diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/LUNA_DIALOGUE.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/LUNA_DIALOGUE.md index 54d7b78..5155e60 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/LUNA_DIALOGUE.md +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/LUNA_DIALOGUE.md @@ -1027,3 +1027,39 @@ files are listed there. No `Source/` or `Standalone/Source/` file, owner project commit, push, or stash was touched. — Luna + +## Luna, M3 round 2 — FloatingIslands look pass + +Hi Claude, + +Round 2 replaces the thin profile with a broad crust and three or four deterministic tapered roots +per island. The previous underside pinch came from tapering the cross-section to zero at its bottom +plane; version 2 uses a broad underside and independent root lobes. Bounded contour warp and surface +roughness add broken edges inside the certified detail bounds. One side island sits above and away +from the three-island route; the route spans are broad, arched and sharp-tag-ready. The main route is +still a connected backbone, and the native cluster is intentionally denser than the owner's sampled +field. + +The owner settings describe a stochastic site process, not a fixed count: density is 0.5 per hashed +XY cell, spacing is 112 legacy voxels and radius fields are 24–48 voxels. I used the production +12.5 cm legacy scale against 25 cm Core, mapping lengths by 0.5. This gives 14 m spacing and 3–6 m +radii. The frozen probe metadata and a radius comment instead assume 25 cm; Round 3 must reconcile +that at the snapshot producer before finalizing the Unreal import contract. + +The five fresh matched views are in the [contact sheet](../../WorldAssemblerM3/Round2/results/captures/contact_sheet.jpg). +All images are below 300 KB. Bounds passed 515 boxes with zero misses and the shrunken-bound control +was detected. All four islands measured at least 0.75 depth/diameter; the 0.30 thin-pad control +failed. The route passed 254 capsule centers / 69,088 field samples; its raised control failed at all +254 centers. W1/W8/W16 hashes match. On 32,768 same-physical-location queries, native was 38.06 ms +vs 64.64 ms legacy, with 1,380 vs 130 solid samples. + +The full report, scale caveat, captures, run ledger and Round 3 plan are in +[`WorldAssemblerM3/Round2/REPORT.md`](../../WorldAssemblerM3/Round2/REPORT.md). It lists one failed +Core compile, eight failed audit iterations and one corrected renderer failure; the final build and +audit passed, with no crash or `0xC0000142` retry. The old M1 and SurfaceWorld manifest hashes still +match; the M2 mesher hash matrix was not replayed. No editor/viewer, project build, `.uasset`, +`Source/` edit, commit, push or stash was used. + +New source file this round: `RenderFloatingIslandRound2.py`. + +— Luna diff --git a/Standalone/Research/WorldAssemblerM3/Round1/FloatingIslandsAudit.cpp b/Standalone/Research/WorldAssemblerM3/Round1/FloatingIslandsAudit.cpp index 23d3a02..e00165b 100644 --- a/Standalone/Research/WorldAssemblerM3/Round1/FloatingIslandsAudit.cpp +++ b/Standalone/Research/WorldAssemblerM3/Round1/FloatingIslandsAudit.cpp @@ -143,10 +143,11 @@ namespace float LegacyDensity(const FStandaloneOwnerWorld& owner, const Point& q) { + constexpr double CoreToLegacyVoxelScale = 2.0; return owner.Generator->GetDensityAt( - static_cast(q[0] / static_cast(Q)), - static_cast(q[1] / static_cast(Q)), - static_cast(q[2] / static_cast(Q))); + static_cast(CoreToLegacyVoxelScale * q[0] / static_cast(Q)), + static_cast(CoreToLegacyVoxelScale * q[1] / static_cast(Q)), + static_cast(CoreToLegacyVoxelScale * q[2] / static_cast(Q))); } OracleAnchor FindLegacyAnchor(const FStandaloneOwnerWorld& owner) @@ -425,9 +426,13 @@ namespace void WriteJson(const std::filesystem::path& path, const FloatingIslandManifestV1& manifest, const FloatingIslandBoundsAuditResult& bounds, const FloatingIslandRouteAuditResult& route, const FloatingIslandRouteAuditResult& raised, const SampleStats& sample, + const FloatingIslandThicknessAuditResult& thickness, + const FloatingIslandThicknessAuditResult& thinThickness, const OracleAnchor& anchor, const std::array& meshes, const std::array& fieldHashes, const ExistingHashes& oldHashes) { + const Point ownerAnchorQ = anchor.surfaceQ; + const Point coreAnchorQ{ownerAnchorQ[0] / 2, ownerAnchorQ[1] / 2, ownerAnchorQ[2] / 2}; std::ofstream out(path, std::ios::trunc); if (!out) throw std::runtime_error("cannot create audit result JSON"); out << std::setprecision(12); @@ -437,13 +442,20 @@ namespace << "\",\n \"unchanged_existing_hashes\":{\"m1_round1_manifest\":\"" << oldHashes.round1Manifest << "\",\"surfaceworld_round4_manifest\":\"" << oldHashes.surfaceWorldRound4Manifest << "\"}" + << ",\n \"geometry_version\":" << manifest.recipe.geometryVersion + << ",\n \"owner_voxel_size_cm\":12.5,\n \"core_voxel_size_cm\":25" + << ",\n \"owner_to_core_length_scale\":0.5" << ",\n \"plan_islands\":" << manifest.plan.islands.size() << ",\n \"plan_connectors\":" << manifest.plan.clusterEdges.size() << ",\n \"route_points\":" << manifest.plan.route.size() - << ",\n \"legacy_anchor_voxels\":[" << anchor.surfaceQ[0] / static_cast(Q) - << ',' << anchor.surfaceQ[1] / static_cast(Q) << ',' - << anchor.surfaceQ[2] / static_cast(Q) << "],\n \"legacy_anchor_plateau_samples\":" + << ",\n \"legacy_anchor_owner_voxels\":[" << ownerAnchorQ[0] / static_cast(Q) + << ',' << ownerAnchorQ[1] / static_cast(Q) << ',' + << ownerAnchorQ[2] / static_cast(Q) << ']' + << ",\n \"comparison_anchor_core_voxels\":[" << coreAnchorQ[0] / static_cast(Q) + << ',' << coreAnchorQ[1] / static_cast(Q) << ',' + << coreAnchorQ[2] / static_cast(Q) << "],\n \"legacy_anchor_plateau_samples\":" << anchor.supportScore << ",\n \"recipe_parameters\":{\"seed\":" << manifest.recipe.seed + << ",\"primary_island_count\":" << manifest.recipe.primaryIslandCount << ",\"density_q16\":" << manifest.recipe.islandDensityQ16 << ",\"spacing_voxels\":" << manifest.recipe.spacingSubvoxels / static_cast(Q) << ",\"radius_voxels\":[" << manifest.recipe.minimumRadiusSubvoxels / static_cast(Q) @@ -452,20 +464,53 @@ namespace << ",\"vertical_jitter_q16\":" << manifest.recipe.verticalJitterQ16 << ",\"top_flatten_q16\":" << manifest.recipe.topFlattenQ16 << ",\"contour_warp_voxels\":" << manifest.recipe.contourWarpSubvoxels / static_cast(Q) + << ",\"crust_depth_bonus_q16\":" << manifest.recipe.crustDepthBonusQ16 + << ",\"root_depth_ratio_q16\":" << manifest.recipe.rootDepthRatioQ16 << ",\"roughness_voxels\":" << manifest.recipe.roughnessSubvoxels / static_cast(Q) - << ",\"blend_voxels\":" << manifest.recipe.blendRadiusSubvoxels / static_cast(Q) << "},\n \"stable_island_ids\":["; + << ",\"blend_voxels\":" << manifest.recipe.blendRadiusSubvoxels / static_cast(Q) + << ",\"bridge_half_width_voxels\":" << manifest.recipe.bridgeHalfWidthSubvoxels / static_cast(Q) + << ",\"bridge_thickness_voxels\":" << manifest.recipe.bridgeThicknessSubvoxels / static_cast(Q) + << "},\n \"stable_island_ids\":["; for (std::size_t i = 0; i < manifest.plan.islands.size(); ++i) { if (i) out << ','; out << '\"' << Hex64(manifest.plan.islands[i].stableId) << '\"'; } out << "],\n \"stable_connector_ids\":["; for (std::size_t i = 0; i < manifest.plan.clusterEdges.size(); ++i) { if (i) out << ','; out << '\"' << Hex64(manifest.plan.clusterEdges[i].stableId) << '\"'; } + out << "],\n \"island_profiles\":["; + for (std::size_t i = 0; i < manifest.plan.islands.size(); ++i) + { + if (i) out << ','; + const auto& island = manifest.plan.islands[i]; + const double ratio = static_cast(island.topZSubvoxels - island.bottomZSubvoxels) + / (2.0 * static_cast(island.radiusSubvoxels)); + out << "{\"id\":\"" << Hex64(island.stableId) << "\",\"ordinal\":" << island.ordinal + << ",\"center_voxels\":[" << island.centerSubvoxels[0] / static_cast(Q) + << ',' << island.centerSubvoxels[1] / static_cast(Q) + << ',' << island.centerSubvoxels[2] / static_cast(Q) + << "],\"radius_voxels\":" << island.radiusSubvoxels / static_cast(Q) + << ",\"top_voxels\":" << island.topZSubvoxels / static_cast(Q) + << ",\"bottom_voxels\":" << island.bottomZSubvoxels / static_cast(Q) + << ",\"depth_to_diameter\":" << ratio << '}'; + } out << "],\n \"bounds\":{\"passed\":" << (bounds.passed ? "true" : "false") << ",\"negative_control_detected\":" << (bounds.negativeControlDetected ? "true" : "false") << ",\"boxes\":" << bounds.boxesTested << ",\"samples\":" << bounds.pointSamples << ",\"unknown\":" << bounds.unknownBounds << ",\"missed\":" << bounds.missedBounds << ",\"island_boxes\":" << bounds.islandBoundsTested << ",\"connector_boxes\":" << bounds.connectorBoundsTested - << ",\"void_boxes_proved_air\":" << bounds.voidBoxesProvedAir << "},\n \"route\":{\"passed\":" + << ",\"void_boxes_proved_air\":" << bounds.voidBoxesProvedAir << "},\n \"thickness\":{\"passed\":" + << (thickness.passed ? "true" : "false") + << ",\"islands_tested\":" << thickness.islandsTested + << ",\"islands_below_minimum\":" << thickness.islandsBelowMinimum + << ",\"minimum_depth_to_diameter_ratio\":" << thickness.minimumDepthToDiameterRatio + << ",\"required_depth_to_diameter_ratio\":" << thickness.requiredDepthToDiameterRatio + << ",\"minimum_island_id\":\"" << Hex64(thickness.minimumIslandStableId) << "\"}" + << ",\n \"thin_pad_negative_control\":{\"detected\":" + << ((!thinThickness.passed && thinThickness.islandsBelowMinimum == thinThickness.islandsTested) + ? "true" : "false") + << ",\"failed_islands\":" << thinThickness.islandsBelowMinimum + << ",\"minimum_depth_to_diameter_ratio\":" << thinThickness.minimumDepthToDiameterRatio << "}" + << ",\n \"route\":{\"passed\":" << (route.passed ? "true" : "false") << ",\"segments\":" << route.segmentsTested << ",\"centers\":" << route.centerSamples << ",\"capsule_samples\":" << route.capsuleFieldSamples << ",\"blocked\":" << route.blockedCapsuleSamples @@ -494,7 +539,7 @@ namespace out << "{\"name\":\"" << names[i] << "\",\"grid_samples\":" << meshes[i].gridSamples << ",\"triangles\":" << meshes[i].triangles << ",\"bytes\":" << meshes[i].bytes << '}'; } - out << "],\n \"route_points_voxels\":["; + out << "],\n \"route_points_core_voxels\":["; for (std::size_t i = 0; i < manifest.plan.route.size(); ++i) { if (i) out << ','; @@ -531,30 +576,33 @@ int main(int argc, char** argv) const OracleAnchor anchor = FindLegacyAnchor(owner); if (anchor.supportScore < 9) throw std::runtime_error("legacy scan found no broad FloatingIslands top in the frozen strate range"); + const Point coreAnchorQ{anchor.surfaceQ[0] / 2, + anchor.surfaceQ[1] / 2, anchor.surfaceQ[2] / 2}; FloatingIslandRecipeV1 recipe; recipe.seed = 0; - recipe.layoutOriginXSubvoxels = anchor.surfaceQ[0]; - recipe.layoutOriginYSubvoxels = anchor.surfaceQ[1]; - recipe.layoutCenterTopZSubvoxels = anchor.surfaceQ[2]; - recipe.layoutMinimumZSubvoxels = anchor.surfaceQ[2] - 96 * Q; - recipe.layoutMaximumZSubvoxels = anchor.surfaceQ[2] + 96 * Q; - recipe.primaryIslandCount = 5; + recipe.layoutOriginXSubvoxels = coreAnchorQ[0]; + recipe.layoutOriginYSubvoxels = coreAnchorQ[1]; + recipe.layoutCenterTopZSubvoxels = coreAnchorQ[2]; + recipe.geometryVersion = 2; + recipe.layoutMinimumZSubvoxels = coreAnchorQ[2] - 80 * Q; + recipe.layoutMaximumZSubvoxels = coreAnchorQ[2] + 48 * Q; + recipe.primaryIslandCount = 3; recipe.islandDensityQ16 = 32768; - recipe.spacingSubvoxels = 112 * Q; - recipe.minimumRadiusSubvoxels = 24 * Q; - recipe.maximumRadiusSubvoxels = 48 * Q; + recipe.spacingSubvoxels = 56 * Q; + recipe.minimumRadiusSubvoxels = 12 * Q; + recipe.maximumRadiusSubvoxels = 24 * Q; recipe.thicknessRatioQ16 = 39322; recipe.verticalJitterQ16 = 39322; recipe.topFlattenQ16 = 39322; recipe.taperEndMinimumQ16 = 29491; recipe.taperEndMaximumQ16 = 45875; - recipe.blendRadiusSubvoxels = 6 * Q; - recipe.contourWarpSubvoxels = VoxQ((24.0 + 48.0) * 0.35 * 0.5); - recipe.roughnessSubvoxels = 3 * Q; - recipe.boundarySealSubvoxels = 4 * Q; - recipe.bridgeHalfWidthSubvoxels = 4 * Q; - recipe.bridgeThicknessSubvoxels = 3 * Q; + recipe.blendRadiusSubvoxels = 3 * Q; + recipe.contourWarpSubvoxels = VoxQ(6.3); + recipe.roughnessSubvoxels = VoxQ(1.5); + recipe.boundarySealSubvoxels = 2 * Q; + recipe.bridgeHalfWidthSubvoxels = 6 * Q; + recipe.bridgeThicknessSubvoxels = 4 * Q; const auto beginPlanning = Clock::now(); auto planned = PlanFloatingIslandCluster(recipe, error); @@ -579,6 +627,12 @@ int main(int argc, char** argv) const auto bounds = AuditFloatingIslandBounds(field, 512); const auto route = AuditFloatingIslandRoute(field); const auto raisedRoute = AuditFloatingIslandRoute(field, 8 * Q); + const auto thickness = AuditFloatingIslandThickness(planned->plan); + auto thinPlan = planned->plan; + for (auto& island : thinPlan.islands) + island.bottomZSubvoxels = island.topZSubvoxels + - island.radiusSubvoxels * planned->recipe.thicknessRatioQ16 / 65536; + const auto thinThickness = AuditFloatingIslandThickness(thinPlan); const auto influence = field.influenceBounds(); const auto points = MakeSamples(influence, 32768, 0x4d33524f554e4431ull); const auto w1 = HashWithWorkers(field, owner, points, 1, false); @@ -588,6 +642,9 @@ int main(int argc, char** argv) if (w1.hash != w8.hash || w1.hash != w16.hash) throw std::runtime_error("native field sample hash differs between W1/W8/W16"); if (!bounds.passed) throw std::runtime_error("native island bounds audit or negative control failed"); + if (!thickness.passed || thinThickness.passed + || thinThickness.islandsBelowMinimum != thinThickness.islandsTested) + throw std::runtime_error("native island thickness audit or thin-pad negative control failed"); std::cout << "route_diagnostics pass=" << route.passed << " segments=" << route.segmentsTested << " centers=" << route.centerSamples << " capsule_samples=" << route.capsuleFieldSamples << " blocked=" << route.blockedCapsuleSamples << " support_samples=" << route.supportProbeSamples @@ -602,6 +659,22 @@ int main(int argc, char** argv) if (!route.passed) { const auto& routePoints = planned->plan.route; + if (route.firstBlockedSegment != ~std::uint64_t{0}) + std::cout << "first_blocked_position_voxels=" + << route.firstBlockedPositionSubvoxels[0] / static_cast(Q) << ',' + << route.firstBlockedPositionSubvoxels[1] / static_cast(Q) << ',' + << route.firstBlockedPositionSubvoxels[2] / static_cast(Q) << '\n'; + const std::size_t debugBegin = route.firstUnsupportedSegment == ~std::uint64_t{0} + ? 0 : static_cast(route.firstUnsupportedSegment); + for (std::size_t i = debugBegin > 1 ? debugBegin - 1 : 0; + i < std::min(routePoints.size(), debugBegin + 3); ++i) + { + const auto& point = routePoints[i].positionSubvoxels; + std::cout << "route_point=" << i << ' ' + << point[0] / static_cast(Q) << ',' + << point[1] / static_cast(Q) << ',' + << point[2] / static_cast(Q) << '\n'; + } const double maxGrade = static_cast(planned->recipe.routeConstraints.maximumGradeQ16) / 65536.0; for (std::size_t i = 0; i + 1 < routePoints.size(); ++i) { @@ -628,6 +701,9 @@ int main(int argc, char** argv) const auto& unsupported = route.firstUnsupportedCenterSubvoxels; const double foot = unsupported[2] / static_cast(Q) - constraints.capsuleHeightSubvoxels / (2.0 * Q); + std::cout << "unsupported_route_probe=" << unsupported[0] / static_cast(Q) + << ',' << unsupported[1] / static_cast(Q) << ',' + << unsupported[2] / static_cast(Q) << " foot=" << foot << '\n'; const double probeRadius = constraints.supportProbeRadiusSubvoxels / static_cast(Q); const std::array, 5> supportPoints{{ {0.0,0.0},{probeRadius,0.0},{-probeRadius,0.0},{0.0,probeRadius},{0.0,-probeRadius}}}; @@ -645,6 +721,16 @@ int main(int argc, char** argv) if (density < 0) { firstSolidDepth = depth; break; } } std::cout << "support_probe=" << probeIndex << " first_solid_depth_vox=" << firstSolidDepth << '\n'; + std::cout << "support_profile=" << probeIndex << ':'; + for (double depth = 0.5; depth <= 5.0; depth += 0.5) + { + const auto density = field.densitySubvoxels( + std::llround(unsupported[0] + supportPoints[probeIndex][0] * Q), + std::llround(unsupported[1] + supportPoints[probeIndex][1] * Q), + std::llround((foot - depth) * Q)); + std::cout << ' ' << depth << '=' << density; + } + std::cout << '\n'; } for (std::size_t i = 0; i < planned->plan.clusterEdges.size(); ++i) { @@ -662,12 +748,15 @@ int main(int argc, char** argv) && raisedRoute.blockedCapsuleSamples == 0)) throw std::runtime_error("raised-route negative control did not detect unsupported traversal"); - const std::int64_t ox = anchor.surfaceQ[0], oy = anchor.surfaceQ[1], oz = anchor.surfaceQ[2]; std::array meshes{}; - const Point clusterMin{ox - 320 * Q, oy - 208 * Q, oz - 80 * Q}; - const Point clusterMax{ox + 320 * Q, oy + 208 * Q, oz + 16 * Q}; - const Point detailMin{ox - 64 * Q, oy - 64 * Q, oz - 48 * Q}; - const Point detailMax{ox + 64 * Q, oy + 64 * Q, oz + 10 * Q}; + const auto clusterBounds = field.influenceBounds(); + if (!clusterBounds.finite) throw std::runtime_error("native cluster mesh bounds are not finite"); + const Point clusterMin{clusterBounds.minimumSubvoxels[0] - 8 * Q, + clusterBounds.minimumSubvoxels[1] - 8 * Q, clusterBounds.minimumSubvoxels[2] - 8 * Q}; + const Point clusterMax{clusterBounds.maximumSubvoxels[0] + 8 * Q, + clusterBounds.maximumSubvoxels[1] + 8 * Q, clusterBounds.maximumSubvoxels[2] + 8 * Q}; + const Point detailMin{coreAnchorQ[0] - 64 * Q, coreAnchorQ[1] - 64 * Q, coreAnchorQ[2] - 80 * Q}; + const Point detailMax{coreAnchorQ[0] + 64 * Q, coreAnchorQ[1] + 64 * Q, coreAnchorQ[2] + 16 * Q}; meshes[0] = ExtractMesh(outputDir / "cluster_legacy.vfmesh", clusterMin, clusterMax, 4 * Q, [&](const Point& q) { return SampleLegacy(owner, q); }); meshes[1] = ExtractMesh(outputDir / "cluster_native.vfmesh", clusterMin, clusterMax, 4 * Q, @@ -681,11 +770,14 @@ int main(int argc, char** argv) throw std::runtime_error("matched legacy/native mesh extraction produced an empty field"); WriteJson(outputDir / "audit.json", *planned, bounds, route, raisedRoute, - w16, anchor, meshes, hashes, oldHashes); + w16, thickness, thinThickness, anchor, meshes, hashes, oldHashes); std::cout << std::setprecision(4) - << "legacy_anchor_voxels=" << anchor.surfaceQ[0] / static_cast(Q) << ',' + << "legacy_anchor_owner_voxels=" << anchor.surfaceQ[0] / static_cast(Q) << ',' << anchor.surfaceQ[1] / static_cast(Q) << ',' << anchor.surfaceQ[2] / static_cast(Q) + << " comparison_anchor_core_voxels=" << coreAnchorQ[0] / static_cast(Q) << ',' + << coreAnchorQ[1] / static_cast(Q) << ',' + << coreAnchorQ[2] / static_cast(Q) << " plateau_samples=" << anchor.supportScore << '\n' << "recipe_hash=" << Hex64(planned->recipe.contentHash) << " plan_hash=" << Hex64(planned->plan.contentHash) @@ -708,6 +800,12 @@ int main(int argc, char** argv) << "raised_route_detected=" << (!raisedRoute.passed && raisedRoute.unsupportedCenterSamples > 0) << " unsupported=" << raisedRoute.unsupportedCenterSamples << " blocked=" << raisedRoute.blockedCapsuleSamples << '\n' + << "thickness_pass=" << thickness.passed << " islands=" << thickness.islandsTested + << " min_depth_diameter=" << thickness.minimumDepthToDiameterRatio + << " threshold=" << thickness.requiredDepthToDiameterRatio + << " thin_pad_detected=" << (!thinThickness.passed + && thinThickness.islandsBelowMinimum == thinThickness.islandsTested) + << " thin_ratio=" << thinThickness.minimumDepthToDiameterRatio << '\n' << "field_hashes W1=" << w1.hash << " W8=" << w8.hash << " W16=" << w16.hash << " legacy_ms=" << w16.legacyMilliseconds << " native_ms=" << w16.nativeMilliseconds << " mismatch_percent=" << (100.0 * w16.signMismatches / 32768.0) diff --git a/Standalone/Research/WorldAssemblerM3/Round2/REPORT.md b/Standalone/Research/WorldAssemblerM3/Round2/REPORT.md new file mode 100644 index 0000000..015ce3e --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Round2/REPORT.md @@ -0,0 +1,172 @@ +# M3 Round 2 — FloatingIslands look and standalone gates + +**Result:** geometry version 2 gives the islands a deep, rough underside with separated tapered +roots, variable-height placement, and broad arched connectors. Fresh matched CPU captures include +overview, underside, edge/cliff, player-eye and route views. The measurable bounds, thickness, +capsule-route and determinism gates pass. The native sample remains much denser than the owner +field, so this is a deliberate routeable recipe rather than a visual-parity claim. + +## Owner recipe and scale + +The legacy settings do not request a fixed island count. `IslandDensity=0.5` is a per-hashed-XY-site +existence chance. The owner parameters are spacing 112 legacy voxels, radius fields 24–48 legacy +voxels, thickness ratio 0.6, vertical jitter/top flatten 0.6, roughness 3, blend radius 6 and boundary +seal 4. The production `Source` constant and plugin-root `CLAUDE.md` say a legacy voxel is 12.5 cm; +Core uses 25 cm. Round 2 therefore queries the legacy field at twice each Core coordinate and maps +owner lengths to Core lengths at 0.5 scale: + +| Setting | Owner | Round 2 Core | Physical intent at production scale | +|---|---:|---:|---:| +| Site spacing | 112 voxels | 56 voxels | 14 m | +| Radius | 24–48 voxels | 12–24 voxels | 3–6 m radius | +| Roughness | 3 voxels | 1.5 voxels | 0.375 m amplitude bound | +| Blend radius | 6 voxels | 3 voxels | 0.75 m | +| Boundary seal | 4 voxels | 2 voxels | 0.5 m | + +There is a scale conflict to resolve before importing this recipe. The frozen owner probe metadata +and `Standalone/Source` mirror say 25 cm, and the radius comment describes 12–24 m diameters at that +scale. Production `Source` and `CLAUDE.md` say 12.5 cm, which makes the same radius fields 6–12 m +diameters. The audit uses the production 12.5 cm field, with legacy anchor `(48, 16, -1904.035)` +mapped to Core `(24, 8, -952.016)`. Round 3 must fix the snapshot producer/metadata and publish one +import scale contract; it must not silently choose between these interpretations. + +The native planner requires three route-connected primary islands, then uses the 0.5 density roll +for optional off-route sites; this seed made one, for four islands and three connectors. That is a +stated routeability constraint, not an owner fixed-count setting. Geometry version 2 adds a crust +depth bonus of 0.15 radius and a root depth of 0.75 radius to the owner 0.6-ratio body. Total measured +depth is 0.75 of island diameter. The deeper roots and guaranteed route account for the local density +increase below. + +## Visual review + +The round-1 reading was substantially right. Its low thickness ratio and `SmoothStep` taper drove the +underside radius to zero at the bottom plane. The vanishing cross-section made the underside gradient +collapse at a shared point and produced the radial crease/pinch. The old profile also had no +independent roots, and its main route lay on a nearly straight chain. + +Version 2 gives the crust a broad, gently bowed bottom surface, then smooth-unions three or four +seeded ellipsoid roots per island. Their lengths taper independently, so they do not converge at one +apex. The bounded 6.3-Core-voxel contour warp breaks the silhouettes; 1.5-voxel surface roughness is +faded to zero across a three-voxel surface band. The certified boxes include the contour warp, +roughness and blend reach. The underside and edge captures show the additional mass and root lobes. + +The three primary islands form a bent, vertically fitted traversal. This seed also produces one +higher side island that is not on the route. There are still three connectors because the route has +to cross the three primary sites; they are 3 m wide, 1 m thick at the ends, and their underside sags +by up to 2 m at midspan. Each is marked sharp-tag-ready. The captures show a rock-span profile rather +than the old narrow straight rods, although the route-connected backbone remains visually legible as +a chain. The route-island tops vary by about 0.67 m; the off-route island is about 1.46 m above the +middle route island. + +The underside and edge captures are close views of the middle island so the larger cluster does not +set their scale. The player-eye view is an offline perspective render from 1.8 m above the route +floor, using the 25 cm Core voxel scale and the approximately 1.8 m player height. These are +field-mesh review images, not Unreal camera, traversal, or collision evidence. + +| View | Capture | +|---|---| +| All five matched views | [Contact sheet](results/captures/contact_sheet.jpg) | +| Cluster overview | [overview_matched.jpg](results/captures/overview_matched.jpg) | +| Underside and roots | [underside_matched.jpg](results/captures/underside_matched.jpg) | +| Thick crust and edge/cliff | [edge_cliff_matched.jpg](results/captures/edge_cliff_matched.jpg) | +| Player-eye toward next island | [player_eye_matched.jpg](results/captures/player_eye_matched.jpg) | +| Planned route | [route_matched.jpg](results/captures/route_matched.jpg) | + +The CPU renderer lowered its process priority. All images are 640×258 pixels individually; the +contact sheet is 640×1,290 pixels and 71,668 bytes. Every JPEG is below 300 KB. Legacy/native pairs +use the same target, camera and scale. The legacy side remains much smaller because it is the exact +stochastic owner field at the same physical anchor, not a hand-expanded parity target. + +## Standalone gates + +The final `Target All` Core build and the audit wrapper passed. The wrapper samples the same 32,768 +physical query locations for legacy and native timing. + +| Gate | Final result | +|---|---:| +| Recipe / plan / manifest hashes | `5b8f27274bad89ba` / `ac33c9460b16337a` / `0db30684eaefa75e` | +| Bounds | 515 boxes, 13,851 point samples, 74 unknown, **0 missed**; shrunken-box control detected | +| Thickness | 4 islands; minimum depth/diameter 0.75 vs 0.70 required; all pass | +| Thin-pad control | Ratio 0.2999; all 4 islands fail, control detected | +| Route | 28 segments, 254 capsule centers, 69,088 capsule samples, 1,430 support probes; 0 blocked, 0 unsupported, 0 grade failures | +| Minimum sampled density clearance | 78 subvoxels (0.305 Core voxel) | +| Raised-route control (+8 voxels) | Detected: 254 unsupported centers, 0 blocked | +| Field hash at W1 / W8 / W16 | `566b6115f1aaeb71` for all three; repeated plan stable | +| Query timing, 32,768 locations | Legacy 64.64 ms; native 38.06 ms (0.589× legacy time, about 1.70× faster) | +| Local sign / occupancy difference | 4.437% sign mismatch; 130 legacy-solid vs 1,380 native-solid samples | + +The sample contains 10.6× as many native-solid points as legacy (1,352 native-only and 102 +legacy-only). This is a local sparse-field sample, not a global volume estimate. It confirms the +native density difference visible in the matched images. The extra route backbone, thick crust and +deep roots are the stated reasons; the native field is not being presented as owner occupancy parity. + +The audit reparsed the unchanged existing M1 round-1 manifest hash `c87ae279b0e26f53` and +SurfaceWorld round-4 manifest hash `8933693d9560ba9f`. No existing Core world evaluator, +SurfaceWorld recipe or mesher implementation was edited. The complete M2 mesher hash matrix was not +replayed in this round; the M2 report records the prior fixture mesh hash `9d58e0603f1bf9c7` and +cliff tile hash `fc7bd7a3cf997992`. + +Full audit data is in [audit.json](results/audit.json); the successful wrapper ledger is +[run_ledger.txt](results/run_ledger.txt). Mesh extracts used for the captures and audit are in the +same `results` directory. + +## Round 3 — Unreal delivery plan + +1. Reconcile the 12.5 cm production field with the 25 cm frozen-probe metadata at the snapshot + producer. Add one explicit owner-to-Core length scale and reject payloads whose declared scale + conflicts with it. +2. Add the recipe and seeded plan to the existing optional `world_assembly` import path. Parse via + `ResolvedWorldSnapshot::FromJson`; validate schema/geometry versions, recipe/plan/manifest hashes, + stable IDs, bounds and scale before generation. Preserve round-trip JSON and stable IDs. +3. Prove imported generation in a null-RHI BuildHost commandlet. Compare canonical plan/field hashes + at W1/W8/W16 and exercise malformed-hash, unknown-version and mismatched-scale controls. +4. Build the islands and sharp-tag-ready spans through the selected mesher path. Check chunk borders, + LOD transitions, route visibility and finite bounds in Unreal. Keep the Core field available to + server queries without requiring render meshes. +5. Cook collision and sweep the actual player capsule over the imported route. Check floor support, + clearance, grade and connector landings against cooked collision, then hand the owner the imported + build, capture set, collision evidence, resolved scale note and exact source/hash manifest. + +Round 2 did not add Unreal import, cooked collision, BuildHost proof or owner hand-off evidence. + +## Run ledger and scope + +The final build used four compile workers at BelowNormal priority and completed in about 51 seconds. +The final audit completed in 1.65 seconds and peaked at 58.8 MiB private memory; the CPU renderer +completed in about 2 seconds. No run approached the 15-minute limit, no process crash occurred, and +there was no `0xC0000142` retry. The build emitted the existing French `vswhere.exe`-not-found +warning and `VoxelStrateManager.cpp` double-to-float conversion warnings before succeeding. + +Every failed iteration before that final pass: + +| Failed run | Result and correction | +|---|---| +| Core `Target All` compile | Failed on one unqualified fixed-scale `Q`; qualified it and rebuilt successfully. | +| Audit 1 | One unsupported route center at segment 36. | +| Audit 2 | Diagnostic repeat showed support just beyond the 2.5-voxel search depth (solid by 3); extended/adjusted support fitting. | +| Audit 3 | Used a stale Core library after source changes and repeated the route failure; rebuilt Core before continuing. | +| Audit 4 | Raising the connector deck fixed support but caused 17 blocked capsule samples; reduced the route margin. | +| Audit 5 | No blocked samples, but one unsupported center at segment 49 near a connector end; tapered endpoint detail. | +| Audit 6 | After converting owner dimensions to production physical scale, the old 0.62-radius landing produced 17 blocked and 9 unsupported samples. | +| Audit 7 | Top-detail attenuation alone left those same counts. | +| Audit 8 | Diagnostic repeat located the route near a root-lobe edge; moving the landing to 0.38 radius and using a 1-voxel deck lift fixed it. | +| Renderer run | The first renderer pass stopped after four views when a raster row reached `HEIGHT`; clamped the row to `HEIGHT - 1`, then regenerated all five captures successfully. | + +The repeated pre-final audit processes peaked at 44.6–58.8 MiB. No editor, viewer or GPU was +launched. No owner project file, `.uasset` or `Source/` file was opened for writing or changed; no +project build was run. No commit, push or stash was made. + +### Source inventory + +New source file in this round: + +- `RenderFloatingIslandRound2.py` — CPU-only matched capture renderer. + +Modified source files: + +- `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` +- `Standalone/Core/src/FloatingIslandsInternal.h` +- `Standalone/Research/WorldAssemblerM3/Round1/FloatingIslandsAudit.cpp` (extended for round-2 physical-scale and visual audits) + +Documentation and dialogue changes are in `Standalone/Core/API.md`, `Standalone/DESIGN_WORLD_ASSEMBLER.md`, +`CODEMAP.md` and `Standalone/Research/OwnerShapeWorld/gpu_field_port/LUNA_DIALOGUE.md`. diff --git a/Standalone/Research/WorldAssemblerM3/Round2/RenderFloatingIslandRound2.py b/Standalone/Research/WorldAssemblerM3/Round2/RenderFloatingIslandRound2.py new file mode 100644 index 0000000..f311769 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Round2/RenderFloatingIslandRound2.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 +"""Small CPU-only matched views of the legacy and native FloatingIslands fields.""" +from __future__ import annotations + +import ctypes +import json +import math +import struct +from pathlib import Path + +from PIL import Image, ImageDraw + + +ROOT = Path(__file__).resolve().parent +RESULTS = ROOT / "results" +CAPTURES = RESULTS / "captures" +CAPTURES.mkdir(parents=True, exist_ok=True) +WIDTH, HEIGHT = 320, 226 +BACKGROUND = (236, 240, 242) +LEGACY_COLOR = (133, 127, 105) +NATIVE_COLOR = (78, 135, 151) +ROUTE_COLOR = (223, 72, 45) +TILE_COLOR = (178, 60, 52) +LOD_COLOR = (211, 136, 45) +Q = 256.0 + + +def lower_priority() -> None: + if hasattr(ctypes, "windll"): + ctypes.windll.kernel32.SetPriorityClass(ctypes.windll.kernel32.GetCurrentProcess(), 0x00004000) + + +def read_mesh(path: Path): + raw = path.read_bytes() + if len(raw) < 8 or raw[:4] != b"VFMI": + raise RuntimeError(f"invalid field mesh: {path}") + count = struct.unpack_from(" 1.0e-12 else (0.0, 0.0, 1.0) + + +def dot(a, b): + return sum(x * y for x, y in zip(a, b)) + + +def cross(a, b): + return (a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]) + + +def project(points, target, view_dir, scale=None): + direction = normalize(view_dir) + forward = tuple(-value for value in direction) + reference_up = (0.0, 1.0, 0.0) if abs(forward[2]) > 0.98 else (0.0, 0.0, 1.0) + right = normalize(cross(forward, reference_up)) + up = normalize(cross(right, forward)) + camera_points = [] + for point in points: + delta = tuple(point[i] - target[i] for i in range(3)) + camera_points.append((dot(delta, right), dot(delta, up), dot(delta, forward))) + if scale is None: + span_x = max((p[0] for p in camera_points), default=1.0) - min((p[0] for p in camera_points), default=0.0) + span_y = max((p[1] for p in camera_points), default=1.0) - min((p[1] for p in camera_points), default=0.0) + scale = 0.86 * min((WIDTH - 30) / max(span_x, 1.0e-6), (HEIGHT - 30) / max(span_y, 1.0e-6)) + pixels = [ + (WIDTH * 0.5 + point[0] * scale, 31 + (HEIGHT - 31) * 0.5 - point[1] * scale, point[2]) + for point in camera_points + ] + return pixels, scale, right, up, forward + + +def common_scale(mesh_pair, target, view_dir): + vertices = mesh_pair[0][1] + mesh_pair[1][1] + _, scale, _, _, _ = project(vertices, target, view_dir) + return scale + + +def projected_line(a, b, target, basis, scale): + right, up, forward = basis + points = [] + for point in (a, b): + delta = tuple(point[i] - target[i] for i in range(3)) + points.append((WIDTH * 0.5 + dot(delta, right) * scale, + 31 + (HEIGHT - 31) * 0.5 - dot(delta, up) * scale)) + return points + + +def render(mesh, mesh_pair, method, target, view_dir, title, subtitle, *, route=None, + guide=None, camera_eye=None, framing_radius=None): + triangles, vertices = mesh + perspective = camera_eye is not None + if perspective: + forward = normalize(tuple(target[i] - camera_eye[i] for i in range(3))) + reference_up = (0.0, 1.0, 0.0) if abs(forward[2]) > 0.98 else (0.0, 0.0, 1.0) + right = normalize(cross(forward, reference_up)) + up = normalize(cross(right, forward)) + scale = WIDTH * 0.91 + origin = camera_eye + else: + if framing_radius is None: + scale = common_scale(mesh_pair, target, view_dir) + else: + scale = 0.86 * min((WIDTH - 30) / (2.0 * framing_radius), + (HEIGHT - 30) / (2.0 * framing_radius)) + _, _, right, up, forward = project((), target, view_dir, scale) + origin = target + + def screen(point): + delta = tuple(point[i] - origin[i] for i in range(3)) + cx, cy, depth = dot(delta, right), dot(delta, up), dot(delta, forward) + if perspective: + if depth <= 1.0: + return None + return (WIDTH * 0.5 + cx * scale / depth, + 31 + (HEIGHT - 31) * 0.5 - cy * scale / depth, depth) + return (WIDTH * 0.5 + cx * scale, + 31 + (HEIGHT - 31) * 0.5 - cy * scale, depth) + + background = Image.new("RGB", (WIDTH, HEIGHT + 32), BACKGROUND) + draw = ImageDraw.Draw(background) + draw.rectangle((0, 0, WIDTH - 1, 31), fill=(34, 46, 54)) + draw.text((8, 4), title, fill=(250, 252, 253)) + draw.text((8, 17), subtitle, fill=(190, 207, 215)) + pixels = background.load() + zbuffer = [float("inf")] * (WIDTH * (HEIGHT - 31)) + camera_fill = forward if perspective else normalize(view_dir) + light = normalize((-0.25 + 1.2 * camera_fill[0], + -0.35 + 1.2 * camera_fill[1], + 0.45 + 1.2 * camera_fill[2])) + base = LEGACY_COLOR if method == "legacy" else NATIVE_COLOR + for tri in triangles: + points = [screen(tri[offset:offset + 3]) for offset in (0, 3, 6)] + if any(point is None for point in points): + continue + x0 = max(0, int(math.floor(min(p[0] for p in points)))) + x1 = min(WIDTH - 1, int(math.ceil(max(p[0] for p in points)))) + y0 = max(31, int(math.floor(min(p[1] for p in points)))) + y1 = min(HEIGHT - 1, int(math.ceil(max(p[1] for p in points)))) + if x0 > x1 or y0 > y1: + continue + (ax, ay, az), (bx, by, bz), (cx, cy, cz) = points + denominator = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy) + if abs(denominator) < 1.0e-10: + continue + normal = normalize(tri[9:12]) + shade = 0.50 + 0.50 * max(0.0, dot(normal, light)) + color = tuple(max(0, min(255, int(channel * shade))) for channel in base) + for y in range(y0, y1 + 1): + row = (y - 31) * WIDTH + for x in range(x0, x1 + 1): + sx, sy = x + 0.5, y + 0.5 + u = ((by - cy) * (sx - cx) + (cx - bx) * (sy - cy)) / denominator + v = ((cy - ay) * (sx - cx) + (ax - cx) * (sy - cy)) / denominator + w = 1.0 - u - v + if u < -1e-8 or v < -1e-8 or w < -1e-8: + continue + if perspective: + depth = 1.0 / (u / az + v / bz + w / cz) + else: + depth = u * az + v * bz + w * cz + index = row + x + if depth < zbuffer[index]: + zbuffer[index] = depth + pixels[x, y] = color + + basis = (right, up, forward) + if guide: + origin = guide["origin_voxels"] + top = guide["top_voxels"] + for x_delta, color, label in ((32.0, TILE_COLOR, "32v tile"), (64.0, LOD_COLOR, "64v LOD1")): + line = projected_line((origin[0] + x_delta, origin[1] - 190.0, top - 65.0), + (origin[0] + x_delta, origin[1] + 190.0, top + 14.0), + target, basis, scale) + draw.line(line, fill=color, width=2) + draw.text((max(2, min(WIDTH - 55, int(line[1][0]) + 2)), + max(33, min(HEIGHT - 11, int(line[1][1]) - 12))), label, fill=color) + if route: + points = [screen(point) for point in route] + points = [point[:2] for point in points if point is not None] + if len(points) >= 2: + draw.line(points, fill=(250, 248, 239), width=5, joint="curve") + draw.line(points, fill=ROUTE_COLOR, width=3, joint="curve") + for index in (0, len(points) - 1): + x, y = points[index] + draw.ellipse((x - 4, y - 4, x + 4, y + 4), fill=(250, 248, 239), outline=ROUTE_COLOR, width=2) + return background + + +def titled_pair(left, right, category): + pair = Image.new("RGB", (WIDTH * 2, HEIGHT + 32), (220, 226, 229)) + pair.paste(left, (0, 0)) + pair.paste(right, (WIDTH, 0)) + draw = ImageDraw.Draw(pair) + draw.rectangle((0, 0, WIDTH * 2 - 1, HEIGHT + 31), outline=(72, 84, 91), width=1) + draw.text((8, HEIGHT + 14), f"{category} | same target, camera and scale", fill=(45, 55, 61)) + return pair + + +def main(): + lower_priority() + data = json.loads((RESULTS / "audit.json").read_text(encoding="utf-8")) + anchor = data["comparison_anchor_core_voxels"] + route = data["route_points_core_voxels"] + profiles = data["island_profiles"] + middle_island = profiles[1] + cluster = { + "legacy": read_mesh(RESULTS / "cluster_legacy.vfmesh"), + "native": read_mesh(RESULTS / "cluster_native.vfmesh"), + } + detail = { + "legacy": read_mesh(RESULTS / "detail_legacy.vfmesh"), + "native": read_mesh(RESULTS / "detail_native.vfmesh"), + } + a = anchor + scenes = ( + ("overview", "Cluster overview", cluster, (a[0], a[1], a[2] - 28.0), (8.0, -10.0, 7.0), None, None), + ("underside", "Cluster underside and lobed roots", detail, + (middle_island["center_voxels"][0], middle_island["center_voxels"][1], + middle_island["bottom_voxels"] - 6.0), (0.12, -0.08, -1.0), None, None), + ("edge_cliff", "Thick crust and bounded rock detail", detail, + (middle_island["center_voxels"][0], middle_island["center_voxels"][1], + middle_island["top_voxels"] - middle_island["radius_voxels"] * 0.65), + (1.0, -0.25, 0.18), None, None), + ("route", "Supported route across the cluster", cluster, + (middle_island["center_voxels"][0], middle_island["center_voxels"][1], + middle_island["top_voxels"] - 18.0), (8.0, -10.0, 7.0), route, None), + ) + pairs = [] + for key, title, meshes, target, camera, path, grid in scenes: + framing_radius = 28.0 if key in ("underside", "edge_cliff") else None + left = render(meshes["legacy"], (meshes["legacy"], meshes["native"]), "legacy", + target, camera, title, "Legacy bridge | exact owner field", route=None, + guide=grid, framing_radius=framing_radius) + route_caption = "Core native | planner route" if path else "Core native | matched field view" + right = render(meshes["native"], (meshes["legacy"], meshes["native"]), "native", + target, camera, title, route_caption, route=path, guide=grid, + framing_radius=framing_radius) + pair = titled_pair(left, right, title) + out = CAPTURES / f"{key}_matched.jpg" + pair.save(out, quality=82, optimize=True) + pairs.append(pair) + print(f"capture={out.name} bytes={out.stat().st_size} size={pair.size[0]}x{pair.size[1]}") + + eye_index = 5 + eye_route = route[eye_index] + eye = (eye_route[0], eye_route[1], eye_route[2] + 3.68) + next_island = profiles[1] + look = (next_island["center_voxels"][0], next_island["center_voxels"][1], + next_island["top_voxels"] + 5.0) + perspective_pair = [] + for method, meshes in (("legacy", cluster), ("native", cluster)): + perspective_pair.append(render(meshes[method], (meshes["legacy"], meshes["native"]), method, + look, (0.0, 0.0, -1.0), "Island-edge player view", "Eye at 1.8 m | route span to next island", + route=None, camera_eye=eye)) + eye_pair = titled_pair(perspective_pair[0], perspective_pair[1], "Island-edge player view") + eye_path = CAPTURES / "player_eye_matched.jpg" + eye_pair.save(eye_path, quality=80, optimize=True) + pairs.insert(3, eye_pair) + print(f"capture={eye_path.name} bytes={eye_path.stat().st_size} size={eye_pair.size[0]}x{eye_pair.size[1]}") + + sheet = Image.new("RGB", (WIDTH * 2, (HEIGHT + 32) * len(pairs)), (218, 225, 228)) + for row, pair in enumerate(pairs): + sheet.paste(pair, (0, row * (HEIGHT + 32))) + sheet_path = CAPTURES / "contact_sheet.jpg" + sheet.save(sheet_path, quality=76, optimize=True) + oversized = [(path.name, path.stat().st_size) for path in sorted(CAPTURES.glob("*.jpg")) + if path.stat().st_size >= 300_000] + for path in sorted(CAPTURES.glob("*.jpg")): + print(f"capture_size={path.name} bytes={path.stat().st_size}") + if oversized: + raise RuntimeError(f"captures exceed the 300 KB limit: {oversized}") + print(f"contact_sheet={sheet_path.name} bytes={sheet_path.stat().st_size} views=5 matched_pairs=5") + + +if __name__ == "__main__": + main() diff --git a/Standalone/Research/WorldAssemblerM3/Round2/results/audit.json b/Standalone/Research/WorldAssemblerM3/Round2/results/audit.json new file mode 100644 index 0000000..3c409d5 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Round2/results/audit.json @@ -0,0 +1,29 @@ +{ + "recipe_hash":"5b8f27274bad89ba", + "plan_hash":"ac33c9460b16337a", + "manifest_hash":"0db30684eaefa75e", + "unchanged_existing_hashes":{"m1_round1_manifest":"c87ae279b0e26f53","surfaceworld_round4_manifest":"8933693d9560ba9f"}, + "geometry_version":2, + "owner_voxel_size_cm":12.5, + "core_voxel_size_cm":25, + "owner_to_core_length_scale":0.5, + "plan_islands":4, + "plan_connectors":3, + "route_points":29, + "legacy_anchor_owner_voxels":[48,16,-1904.03515625], + "comparison_anchor_core_voxels":[24,8,-952.015625], + "legacy_anchor_plateau_samples":25, + "recipe_parameters":{"seed":0,"primary_island_count":3,"density_q16":32768,"spacing_voxels":56,"radius_voxels":[12,24],"thickness_ratio_q16":39322,"vertical_jitter_q16":39322,"top_flatten_q16":39322,"contour_warp_voxels":6.30078125,"crust_depth_bonus_q16":9830,"root_depth_ratio_q16":49152,"roughness_voxels":1.5,"blend_voxels":3,"bridge_half_width_voxels":6,"bridge_thickness_voxels":4}, + "stable_island_ids":["e6e461e18b3a9c87","2bc1820baba047f6","adc259cbe2a395e9","7855fb022cc561c9"], + "stable_connector_ids":["eaa5dd310e846520","034023599b466c51","4962fa885545fb26"], + "island_profiles":[{"id":"e6e461e18b3a9c87","ordinal":0,"center_voxels":[-15.19921875,25.2734375,-957.5078125],"radius_voxels":14.08203125,"top_voxels":-954.69140625,"bottom_voxels":-975.81640625,"depth_to_diameter":0.750069348128},{"id":"2bc1820baba047f6","ordinal":1,"center_voxels":[24,-12.2265625,-954.8984375],"radius_voxels":14.42578125,"top_voxels":-952.015625,"bottom_voxels":-973.65625,"depth_to_diameter":0.75006769564},{"id":"adc259cbe2a395e9","ordinal":2,"center_voxels":[63.19921875,26.28515625,-958.57421875],"radius_voxels":20.23828125,"top_voxels":-954.52734375,"bottom_voxels":-984.88671875,"depth_to_diameter":0.750048253233},{"id":"7855fb022cc561c9","ordinal":3,"center_voxels":[-1.19921875,-68.2265625,-950.75390625],"radius_voxels":22.8125,"top_voxels":-946.19140625,"bottom_voxels":-980.41015625,"depth_to_diameter":0.75}], + "bounds":{"passed":true,"negative_control_detected":true,"boxes":515,"samples":13851,"unknown":74,"missed":0,"island_boxes":4,"connector_boxes":3,"void_boxes_proved_air":1}, + "thickness":{"passed":true,"islands_tested":4,"islands_below_minimum":0,"minimum_depth_to_diameter_ratio":0.75,"required_depth_to_diameter_ratio":0.7,"minimum_island_id":"7855fb022cc561c9"}, + "thin_pad_negative_control":{"detected":true,"failed_islands":4,"minimum_depth_to_diameter_ratio":0.299891686975}, + "route":{"passed":true,"segments":28,"centers":254,"capsule_samples":69088,"blocked":0,"support_samples":1430,"unsupported":0,"grade_failures":0,"minimum_clearance_subvoxels":78}, + "raised_route_negative_control":{"detected":true,"passed":false,"unsupported":254,"blocked":0}, + "determinism":{"W1":"566b6115f1aaeb71","W8":"566b6115f1aaeb71","W16":"566b6115f1aaeb71","plan_repeat":true,"hash_samples":32768}, + "timing":{"same_queries":32768,"legacy_ms":64.6405,"native_ms":38.0581,"native_over_legacy":0.588765557197,"sign_mismatches":1454,"sign_mismatch_percent":4.43725585938,"legacy_solid_samples":130,"native_solid_samples":1380,"legacy_only_solid_samples":102,"native_only_solid_samples":1352}, + "mesh":[{"name":"cluster_legacy","grid_samples":36613,"triangles":1532,"bytes":73544},{"name":"cluster_native","grid_samples":36613,"triangles":8764,"bytes":420680},{"name":"detail_legacy","grid_samples":207025,"triangles":4124,"bytes":197960},{"name":"detail_native","grid_samples":207025,"triangles":27794,"bytes":1334120}], + "route_points_core_voxels":[[-15.19921875,25.2734375,-950.4375],[-12.3984375,22.59375,-949.54296875],[-9.59765625,19.91796875,-949.453125],[-6.80078125,17.23828125,-949.3125],[-4,14.55859375,-949.23046875],[-1.19921875,11.87890625,-949.0234375],[1.6015625,9.203125,-948.734375],[4.40234375,6.5234375,-948.53515625],[7.19921875,3.84375,-948.328125],[10,1.16796875,-947.99609375],[12.80078125,-1.51171875,-947.6953125],[15.6015625,-4.19140625,-947.5390625],[18.3984375,-6.87109375,-947.33984375],[21.19921875,-9.546875,-947.93359375],[24,-12.2265625,-947.87109375],[26.80078125,-9.4765625,-946.87109375],[29.6015625,-6.7265625,-946.93359375],[32.3984375,-3.97265625,-947.31640625],[35.19921875,-1.22265625,-947.72265625],[38,1.52734375,-948.17578125],[40.80078125,4.27734375,-948.34375],[43.6015625,7.03125,-948.48046875],[46.3984375,9.78125,-948.72265625],[49.19921875,12.53125,-949.046875],[52,15.28125,-949.2109375],[54.80078125,18.03125,-949.265625],[57.59765625,20.78515625,-949.52734375],[60.3984375,23.53515625,-950.3984375],[63.19921875,26.28515625,-950.265625]] +} diff --git a/Standalone/Research/WorldAssemblerM3/Round2/results/captures/contact_sheet.jpg b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/contact_sheet.jpg new file mode 100644 index 0000000..fd22660 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/contact_sheet.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Round2/results/captures/edge_cliff_matched.jpg b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/edge_cliff_matched.jpg new file mode 100644 index 0000000..f8ae1d3 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/edge_cliff_matched.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Round2/results/captures/overview_matched.jpg b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/overview_matched.jpg new file mode 100644 index 0000000..42dec0e Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/overview_matched.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Round2/results/captures/player_eye_matched.jpg b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/player_eye_matched.jpg new file mode 100644 index 0000000..4bbd6e7 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/player_eye_matched.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Round2/results/captures/route_matched.jpg b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/route_matched.jpg new file mode 100644 index 0000000..6e4a894 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/route_matched.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Round2/results/captures/underside_matched.jpg b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/underside_matched.jpg new file mode 100644 index 0000000..806bc63 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Round2/results/captures/underside_matched.jpg differ