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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
53a1e69af7
commit
4afea7c802
15 files changed
+1000
-92
No files matched your search
+10
@@ -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
|
||||
|
||||
+14
-6
@@ -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
|
||||
|
||||
|
||||
@@ -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<std::uint32_t>(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
|
||||
{
|
||||
|
||||
@@ -174,6 +174,12 @@ namespace floating_island_detail
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.topFlattenQ16), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.taperEndMinimumQ16), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.taperEndMaximumQ16), 8);
|
||||
if (recipe.geometryVersion >= 2)
|
||||
{
|
||||
HashWord(hash, recipe.geometryVersion, 4);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.crustDepthBonusQ16), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.rootDepthRatioQ16), 8);
|
||||
}
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.blendRadiusSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.contourWarpSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.roughnessSubvoxels), 8);
|
||||
@@ -233,7 +239,7 @@ namespace floating_island_detail
|
||||
|
||||
inline bool ValidateRecipe(const FloatingIslandRecipeV1& recipe, std::string& error)
|
||||
{
|
||||
const std::array<std::int64_t, 23> values = {
|
||||
const std::array<std::int64_t, 25> 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<double>(Roll(recipe.seed, island.stableId, 0x524f5547ull)
|
||||
const double phase = static_cast<double>(Roll(recipe.seed, featureId, 0x524f5547ull)
|
||||
& 0xffffffffull) * (2.0 * Pi / 4294967296.0);
|
||||
const double xv = x / static_cast<double>(Q);
|
||||
const double yv = y / static_cast<double>(Q);
|
||||
const double zv = z / static_cast<double>(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<double>(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<double>(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<double>(recipe.roughnessSubvoxels)
|
||||
* std::clamp(0.42 * noise + 0.58 * fine, -1.0, 1.0);
|
||||
}
|
||||
|
||||
inline double IslandTopSurface(const FloatingIslandRecipeV1& recipe,
|
||||
@@ -355,9 +382,86 @@ namespace floating_island_detail
|
||||
return static_cast<double>(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<std::int32_t>(Roll(recipe.seed, island.stableId,
|
||||
0x524f4f54434e54ull) & 1ull);
|
||||
}
|
||||
|
||||
inline double UnitHash(std::uint64_t hash)
|
||||
{ return static_cast<double>(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<std::array<double, 2>, 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<std::uint64_t>(rootIndex);
|
||||
const std::uint64_t roll = Roll(recipe.seed, island.stableId, salt);
|
||||
const double radius = static_cast<double>(island.radiusSubvoxels);
|
||||
const double rootDepth = radius * Q16ToDouble(recipe.rootDepthRatioQ16);
|
||||
const double crustBottom = static_cast<double>(IslandCrustBottom(recipe, island));
|
||||
const double orbit = radius * (0.17 + 0.07 * UnitHash(roll));
|
||||
const double rootX = static_cast<double>(island.centerSubvoxels[0])
|
||||
+ pattern[static_cast<std::size_t>(rootIndex)][0] * orbit;
|
||||
const double rootY = static_cast<double>(island.centerSubvoxels[1])
|
||||
+ pattern[static_cast<std::size_t>(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<double>(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<double>(island.centerSubvoxels[0]);
|
||||
const double dy = y - static_cast<double>(island.centerSubvoxels[1]);
|
||||
@@ -374,12 +478,42 @@ namespace floating_island_detail
|
||||
const double top = IslandTopSurface(recipe, island,
|
||||
static_cast<double>(island.centerSubvoxels[0]) + dx,
|
||||
static_cast<double>(island.centerSubvoxels[1]) + dy);
|
||||
const double sdf = std::max({z - top, radial - envelope, bottomZ - z});
|
||||
return sdf + Roughness(recipe, island, x, y, z);
|
||||
return ApplySurfaceDetail(recipe, island.stableId,
|
||||
std::max({z - top, radial - envelope, bottomZ - z}), x, y, z);
|
||||
}
|
||||
|
||||
inline double ConnectorDensity(const FloatingIslandConnectorV1& edge,
|
||||
double x, double y, double z)
|
||||
const double dx = x - static_cast<double>(island.centerSubvoxels[0]);
|
||||
const double dy = y - static_cast<double>(island.centerSubvoxels[1]);
|
||||
const double radial = std::hypot(dx, dy);
|
||||
const double topZ = static_cast<double>(island.topZSubvoxels);
|
||||
const double radius = static_cast<double>(island.radiusSubvoxels);
|
||||
const double effectiveRadius = std::max(0.0,
|
||||
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<double>(IslandCrustBottom(recipe, island))
|
||||
+ radius * 0.16 * edge * edge;
|
||||
const double top = IslandTopSurface(recipe, island,
|
||||
static_cast<double>(island.centerSubvoxels[0]) + dx,
|
||||
static_cast<double>(island.centerSubvoxels[1]) + dy);
|
||||
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<double>(IslandCrustBottom(recipe, island)) + radius * 0.45
|
||||
? 0.25 : 1.0;
|
||||
return ApplySurfaceDetail(recipe, island.stableId, sdf, x, y, z, topDetailScale);
|
||||
}
|
||||
|
||||
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<double>(edge.startSubvoxels[0]);
|
||||
const double ay = static_cast<double>(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<double>(edge.endSubvoxels[2]) - az) * t;
|
||||
return std::max({horizontal, z - deck,
|
||||
deck - static_cast<double>(edge.thicknessSubvoxels) - z});
|
||||
const double arch = recipe.geometryVersion >= 2
|
||||
? 2.0 * static_cast<double>(edge.thicknessSubvoxels) * (4.0 * t * (1.0 - t))
|
||||
: 0.0;
|
||||
const double upper = z - deck;
|
||||
const double lower = deck - static_cast<double>(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<double>(x), static_cast<double>(y), static_cast<double>(z)), blend);
|
||||
bool topSurface = false;
|
||||
double alongFraction = 0.0;
|
||||
double connector = ConnectorDensity(recipe, edge,
|
||||
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z),
|
||||
&topSurface, &alongFraction);
|
||||
if (recipe.geometryVersion >= 2)
|
||||
connector = ApplySurfaceDetail(recipe, edge.stableId, connector,
|
||||
static_cast<double>(x), static_cast<double>(y), static_cast<double>(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<double>(island.topZSubvoxels - island.bottomZSubvoxels)
|
||||
/ (2.0 * static_cast<double>(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<std::int64_t>(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<double>(from.radiusSubvoxels) * 0.62;
|
||||
const double toReach = static_cast<double>(to.radiusSubvoxels) * 0.62;
|
||||
const double landingFraction = recipe.geometryVersion >= 2 ? 0.38 : 0.62;
|
||||
const double fromReach = static_cast<double>(from.radiusSubvoxels) * landingFraction;
|
||||
const double toReach = static_cast<double>(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> 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<FloatingIslandManifestV1> PlanFloatingIslandCluster(
|
||||
radii[i] = recipe.minimumRadiusSubvoxels
|
||||
+ (recipe.maximumRadiusSubvoxels - recipe.minimumRadiusSubvoxels) * radiusRoll / 65535;
|
||||
}
|
||||
std::vector<std::array<std::int64_t, 2>> 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<std::int64_t>(middle) * recipe.spacingSubvoxels
|
||||
+ static_cast<std::int64_t>(i) * recipe.spacingSubvoxels,
|
||||
recipe.layoutOriginYSubvoxels + yOffset};
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::int64_t along = (static_cast<std::int64_t>(i)
|
||||
- static_cast<std::int64_t>(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<double>(recipe.spacingSubvoxels)
|
||||
const double centerDistance = recipe.geometryVersion < 2
|
||||
? static_cast<double>(recipe.spacingSubvoxels)
|
||||
: std::hypot(static_cast<double>(primaryXY[b][0] - primaryXY[a][0]),
|
||||
static_cast<double>(primaryXY[b][1] - primaryXY[a][1]));
|
||||
const double gap = centerDistance
|
||||
- 0.78 * (static_cast<double>(radii[a]) + static_cast<double>(radii[b]));
|
||||
return std::max(0.0, gap * maxGrade * levelFactor);
|
||||
};
|
||||
@@ -983,22 +1190,16 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
|
||||
static_cast<std::int64_t>(std::ceil(riseLimit(static_cast<std::uint32_t>(i),
|
||||
static_cast<std::uint32_t>(i + 1)))));
|
||||
}
|
||||
const std::int64_t rowJitter = recipe.spacingSubvoxels / 10;
|
||||
const std::int64_t startX = recipe.layoutOriginXSubvoxels
|
||||
- static_cast<std::int64_t>(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<std::int64_t>(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<std::int64_t>(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<FloatingIslandManifestV1> PlanFloatingIslandCluster(
|
||||
const std::uint64_t site = 0x300000000ull + branch;
|
||||
if (floating_island_detail::UnitQ16(recipe.seed, site, 0x53495445524f4c4cull)
|
||||
>= static_cast<std::uint32_t>(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::int64_t>(
|
||||
std::llround(0.78 * static_cast<double>(host.radiusSubvoxels + recipe.minimumRadiusSubvoxels)));
|
||||
const std::int64_t rise = static_cast<std::int64_t>(std::ceil(std::max(0.0,
|
||||
static_cast<double>(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<std::int64_t>(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<std::int64_t>(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<FloatingIslandManifestV1> 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<double>::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<double>(island.topZSubvoxels - island.bottomZSubvoxels)
|
||||
/ (2.0 * static_cast<double>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -143,10 +143,11 @@ namespace
|
||||
|
||||
float LegacyDensity(const FStandaloneOwnerWorld& owner, const Point& q)
|
||||
{
|
||||
constexpr double CoreToLegacyVoxelScale = 2.0;
|
||||
return owner.Generator->GetDensityAt(
|
||||
static_cast<float>(q[0] / static_cast<double>(Q)),
|
||||
static_cast<float>(q[1] / static_cast<double>(Q)),
|
||||
static_cast<float>(q[2] / static_cast<double>(Q)));
|
||||
static_cast<float>(CoreToLegacyVoxelScale * q[0] / static_cast<double>(Q)),
|
||||
static_cast<float>(CoreToLegacyVoxelScale * q[1] / static_cast<double>(Q)),
|
||||
static_cast<float>(CoreToLegacyVoxelScale * q[2] / static_cast<double>(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<MeshResult, 4>& meshes,
|
||||
const std::array<std::string, 3>& 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<double>(Q)
|
||||
<< ',' << anchor.surfaceQ[1] / static_cast<double>(Q) << ','
|
||||
<< anchor.surfaceQ[2] / static_cast<double>(Q) << "],\n \"legacy_anchor_plateau_samples\":"
|
||||
<< ",\n \"legacy_anchor_owner_voxels\":[" << ownerAnchorQ[0] / static_cast<double>(Q)
|
||||
<< ',' << ownerAnchorQ[1] / static_cast<double>(Q) << ','
|
||||
<< ownerAnchorQ[2] / static_cast<double>(Q) << ']'
|
||||
<< ",\n \"comparison_anchor_core_voxels\":[" << coreAnchorQ[0] / static_cast<double>(Q)
|
||||
<< ',' << coreAnchorQ[1] / static_cast<double>(Q) << ','
|
||||
<< coreAnchorQ[2] / static_cast<double>(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<double>(Q)
|
||||
<< ",\"radius_voxels\":[" << manifest.recipe.minimumRadiusSubvoxels / static_cast<double>(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<double>(Q)
|
||||
<< ",\"crust_depth_bonus_q16\":" << manifest.recipe.crustDepthBonusQ16
|
||||
<< ",\"root_depth_ratio_q16\":" << manifest.recipe.rootDepthRatioQ16
|
||||
<< ",\"roughness_voxels\":" << manifest.recipe.roughnessSubvoxels / static_cast<double>(Q)
|
||||
<< ",\"blend_voxels\":" << manifest.recipe.blendRadiusSubvoxels / static_cast<double>(Q) << "},\n \"stable_island_ids\":[";
|
||||
<< ",\"blend_voxels\":" << manifest.recipe.blendRadiusSubvoxels / static_cast<double>(Q)
|
||||
<< ",\"bridge_half_width_voxels\":" << manifest.recipe.bridgeHalfWidthSubvoxels / static_cast<double>(Q)
|
||||
<< ",\"bridge_thickness_voxels\":" << manifest.recipe.bridgeThicknessSubvoxels / static_cast<double>(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<double>(island.topZSubvoxels - island.bottomZSubvoxels)
|
||||
/ (2.0 * static_cast<double>(island.radiusSubvoxels));
|
||||
out << "{\"id\":\"" << Hex64(island.stableId) << "\",\"ordinal\":" << island.ordinal
|
||||
<< ",\"center_voxels\":[" << island.centerSubvoxels[0] / static_cast<double>(Q)
|
||||
<< ',' << island.centerSubvoxels[1] / static_cast<double>(Q)
|
||||
<< ',' << island.centerSubvoxels[2] / static_cast<double>(Q)
|
||||
<< "],\"radius_voxels\":" << island.radiusSubvoxels / static_cast<double>(Q)
|
||||
<< ",\"top_voxels\":" << island.topZSubvoxels / static_cast<double>(Q)
|
||||
<< ",\"bottom_voxels\":" << island.bottomZSubvoxels / static_cast<double>(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<double>(Q) << ','
|
||||
<< route.firstBlockedPositionSubvoxels[1] / static_cast<double>(Q) << ','
|
||||
<< route.firstBlockedPositionSubvoxels[2] / static_cast<double>(Q) << '\n';
|
||||
const std::size_t debugBegin = route.firstUnsupportedSegment == ~std::uint64_t{0}
|
||||
? 0 : static_cast<std::size_t>(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<double>(Q) << ','
|
||||
<< point[1] / static_cast<double>(Q) << ','
|
||||
<< point[2] / static_cast<double>(Q) << '\n';
|
||||
}
|
||||
const double maxGrade = static_cast<double>(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<double>(Q)
|
||||
- constraints.capsuleHeightSubvoxels / (2.0 * Q);
|
||||
std::cout << "unsupported_route_probe=" << unsupported[0] / static_cast<double>(Q)
|
||||
<< ',' << unsupported[1] / static_cast<double>(Q) << ','
|
||||
<< unsupported[2] / static_cast<double>(Q) << " foot=" << foot << '\n';
|
||||
const double probeRadius = constraints.supportProbeRadiusSubvoxels / static_cast<double>(Q);
|
||||
const std::array<std::array<double, 2>, 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<MeshResult, 4> 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<double>(Q) << ','
|
||||
<< "legacy_anchor_owner_voxels=" << anchor.surfaceQ[0] / static_cast<double>(Q) << ','
|
||||
<< anchor.surfaceQ[1] / static_cast<double>(Q) << ','
|
||||
<< anchor.surfaceQ[2] / static_cast<double>(Q)
|
||||
<< " comparison_anchor_core_voxels=" << coreAnchorQ[0] / static_cast<double>(Q) << ','
|
||||
<< coreAnchorQ[1] / static_cast<double>(Q) << ','
|
||||
<< coreAnchorQ[2] / static_cast<double>(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)
|
||||
|
||||
@@ -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`.
|
||||
@@ -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("<I", raw, 4)[0]
|
||||
stride = struct.calcsize("<12f")
|
||||
if len(raw) != 8 + count * stride:
|
||||
raise RuntimeError(f"truncated field mesh: {path}")
|
||||
triangles = [struct.unpack_from("<12f", raw, 8 + i * stride) for i in range(count)]
|
||||
vertices = [tri[0:3] for tri in triangles]
|
||||
vertices.extend(tri[3:6] for tri in triangles)
|
||||
vertices.extend(tri[6:9] for tri in triangles)
|
||||
return triangles, vertices
|
||||
|
||||
|
||||
def normalize(vector):
|
||||
size = math.sqrt(sum(value * value for value in vector))
|
||||
return tuple(value / size for value in vector) if size > 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()
|
||||
@@ -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]]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Reference in new issue
Block a user