From 4afea7c802ffe012012c00fb1b59269a44c2ce61 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Sat, 10 Oct 2026 12:24:35 +0200 Subject: [PATCH] wip(assembler): M3 round 2 -- islands get real mass: deep crust, separate tapered roots, arched spans Pinch cause: SmoothStep taper drove the underside radius to zero at one point. Version 2: depth 0.75x diameter (thin-pad control at 0.30 fails), 3-4 seeded roots, bounded contour warp + roughness, one off-route side island, 3 m rock spans. Bounds/route/W1-W8-W16 pass. Open: legacy probe metadata says 25 cm but production legacy is 12.5 cm -- round 3 must fix the import scale contract. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ --- CODEMAP.md | 10 + Standalone/Core/API.md | 20 +- .../include/VoxelForgeCore/VoxelForgeCore.h | 47 ++- Standalone/Core/src/FloatingIslandsInternal.h | 346 +++++++++++++++--- .../gpu_field_port/LUNA_DIALOGUE.md | 36 ++ .../Round1/FloatingIslandsAudit.cpp | 160 ++++++-- .../WorldAssemblerM3/Round2/REPORT.md | 172 +++++++++ .../Round2/RenderFloatingIslandRound2.py | 286 +++++++++++++++ .../Round2/results/audit.json | 29 ++ .../Round2/results/captures/contact_sheet.jpg | Bin 0 -> 71668 bytes .../results/captures/edge_cliff_matched.jpg | Bin 0 -> 18342 bytes .../results/captures/overview_matched.jpg | Bin 0 -> 14552 bytes .../results/captures/player_eye_matched.jpg | Bin 0 -> 14013 bytes .../Round2/results/captures/route_matched.jpg | Bin 0 -> 16752 bytes .../results/captures/underside_matched.jpg | Bin 0 -> 18914 bytes 15 files changed, 1007 insertions(+), 99 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Round2/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Round2/RenderFloatingIslandRound2.py create mode 100644 Standalone/Research/WorldAssemblerM3/Round2/results/audit.json create mode 100644 Standalone/Research/WorldAssemblerM3/Round2/results/captures/contact_sheet.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Round2/results/captures/edge_cliff_matched.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Round2/results/captures/overview_matched.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Round2/results/captures/player_eye_matched.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Round2/results/captures/route_matched.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Round2/results/captures/underside_matched.jpg 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 0000000000000000000000000000000000000000..fd226601bcfb99f80704527924228232bd0d929d GIT binary patch literal 71668 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 D^gz33 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..42dec0e762eb255f95917716afba197043509a08 GIT binary patch literal 14552 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 ZNR))mDD1T4>iXkVVS2DUx_>+WzW`a-20Z`( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4bbd6e795e3bcbab8b4f4d61683f3751fe6a3cd7 GIT binary patch literal 14013 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 Z3WRu8Bn}3vb>ZF#BRsy#%l3Qbe*gq3>j(e< literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..806bc638b8184066c0541e430973f9396bad676e GIT binary patch literal 18914 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 t4gJ?0_