wip(assembler): M3 round 3 -- separated islands + Unreal island route (owner build 18:46)

Islands read as separate (91.89% open sky at mid-height), underside cavity closed,
rim teeth were capture shading. Island manifest import + BuildHost proof passed on the
pre-cache tree; M1 SurfaceWorld and M2 V1/V2 gates passed.

Known open: final XY-column cache compiled in the owner build but is unverified;
island LOD0 p95 ~155 ms misses the 133 ms sprint deadline (legacy ~43 ms); the island
manifest only fits the BuildHost fixture layout (owner's world: Core init fails,
empty world) -> round 4 builds plans inside Unreal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-10 20:53:03 +02:00
1 parent 4afea7c802
commit 48fa028086
37 files changed
+3788 -107

No files matched your search

+226 -35
View File
@@ -158,6 +158,10 @@ namespace floating_island_detail
std::uint64_t hash = StartHash();
HashWord(hash, recipe.schemaVersion, 4);
HashWord(hash, recipe.unitsPerVoxel, 4);
HashWord(hash, recipe.ownerVoxelSizeMicrons, 4);
HashWord(hash, recipe.coreVoxelSizeMicrons, 4);
HashWord(hash, recipe.ownerToCoreScaleNumerator, 4);
HashWord(hash, recipe.ownerToCoreScaleDenominator, 4);
HashWord(hash, recipe.seed, 4);
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutOriginXSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutOriginYSubvoxels), 8);
@@ -258,6 +262,10 @@ namespace floating_island_detail
|| (recipe.geometryVersion == 1
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|| recipe.unitsPerVoxel != Q
|| recipe.ownerVoxelSizeMicrons != 125000
|| recipe.coreVoxelSizeMicrons != 250000
|| recipe.ownerToCoreScaleNumerator != 1
|| recipe.ownerToCoreScaleDenominator != 2
|| recipe.primaryIslandCount < 3 || recipe.primaryIslandCount > 9
|| recipe.layoutMaximumZSubvoxels <= recipe.layoutMinimumZSubvoxels
|| recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels > 2048 * Q
@@ -339,6 +347,30 @@ namespace floating_island_detail
return static_cast<double>(recipe.contourWarpSubvoxels) * (0.50 * a + 0.30 * b + 0.20 * c);
}
inline double LobePhase(const FloatingIslandRecipeV1& recipe,
const FloatingIslandAnchorV1& island)
{
return static_cast<double>(Roll(recipe.seed, island.stableId, 0x4c4f4245ull)
& 0xffffffffull) * (2.0 * Pi / 4294967296.0);
}
inline double LobeOffsetWithPhase(const FloatingIslandRecipeV1& recipe,
double angle,double zSubvoxels,double phase)
{
const double z = zSubvoxels / static_cast<double>(Q);
const double a = std::sin(3.0 * angle + phase + z * 0.017);
const double b = std::sin(5.0 * angle - phase * 0.73 - z * 0.023);
const double c = std::sin(8.0 * angle + phase * 1.41 + z * 0.011);
return static_cast<double>(recipe.contourWarpSubvoxels) * (0.50 * a + 0.30 * b + 0.20 * c);
}
inline double LobeOffsetAtAngle(const FloatingIslandRecipeV1& recipe,
const FloatingIslandAnchorV1& island,
double angle,double zSubvoxels)
{
return LobeOffsetWithPhase(recipe,angle,zSubvoxels,LobePhase(recipe,island));
}
inline double Roughness(const FloatingIslandRecipeV1& recipe,
std::uint64_t featureId, double x, double y, double z)
{
@@ -382,6 +414,83 @@ namespace floating_island_detail
return static_cast<double>(island.topZSubvoxels) - drop;
}
struct FIslandColumnCacheEntry
{
bool bValid=false;
std::uint32_t Seed=0;
std::uint64_t StableId=0;
std::int64_t SampleX=0,SampleY=0,CenterX=0,CenterY=0,TopZ=0,Radius=0,TopHalf=0;
std::int64_t TopFlattenQ16=0,ContourWarp=0;
double DeltaX=0.0,DeltaY=0.0,Radial=0.0,Angle=0.0,LobePhase=0.0,TopSurface=0.0;
};
inline const FIslandColumnCacheEntry& IslandColumnCache(
const FloatingIslandRecipeV1& recipe,const FloatingIslandAnchorV1& island,
double x,double y)
{
// Density samples arrive on integer Core lattice columns; reuse the exact column metrics
// across Z samples without changing the field value or touching shared world state.
// Les echantillons suivent les colonnes Core entieres; reutiliser les memes metriques XY.
constexpr std::size_t Ways=4;
constexpr std::size_t SetCount=2048;
static_assert((SetCount&(SetCount-1))==0);
static thread_local std::array<FIslandColumnCacheEntry,SetCount*Ways> Cache{};
const std::int64_t SampleX=static_cast<std::int64_t>(x);
const std::int64_t SampleY=static_cast<std::int64_t>(y);
std::uint64_t IndexKey=island.stableId
^(static_cast<std::uint64_t>(SampleX)*0x9e3779b97f4a7c15ull)
^(static_cast<std::uint64_t>(SampleY)*0xc2b2ae3d27d4eb4full)
^(static_cast<std::uint64_t>(recipe.seed)<<23);
IndexKey^=IndexKey>>33;
IndexKey*=0xff51afd7ed558ccdull;
IndexKey^=IndexKey>>33;
const std::size_t Base=static_cast<std::size_t>(IndexKey&(SetCount-1))*Ways;
FIslandColumnCacheEntry* Replacement=&Cache[Base+((IndexKey>>11)&(Ways-1))];
for(std::size_t Way=0;Way<Ways;++Way)
{
FIslandColumnCacheEntry& Entry=Cache[Base+Way];
const bool bKeyMatches=Entry.bValid&&Entry.Seed==recipe.seed
&&Entry.StableId==island.stableId
&&Entry.SampleX==SampleX&&Entry.SampleY==SampleY
&&Entry.CenterX==island.centerSubvoxels[0]
&&Entry.CenterY==island.centerSubvoxels[1]
&&Entry.TopZ==island.topZSubvoxels
&&Entry.Radius==island.radiusSubvoxels
&&Entry.TopHalf==island.topHalfSubvoxels
&&Entry.TopFlattenQ16==recipe.topFlattenQ16
&&Entry.ContourWarp==recipe.contourWarpSubvoxels;
if(bKeyMatches)return Entry;
if(!Entry.bValid)Replacement=&Entry;
}
FIslandColumnCacheEntry& Entry=*Replacement;
Entry.bValid=true;
Entry.Seed=recipe.seed;
Entry.StableId=island.stableId;
Entry.SampleX=SampleX;
Entry.SampleY=SampleY;
Entry.CenterX=island.centerSubvoxels[0];
Entry.CenterY=island.centerSubvoxels[1];
Entry.TopZ=island.topZSubvoxels;
Entry.Radius=island.radiusSubvoxels;
Entry.TopHalf=island.topHalfSubvoxels;
Entry.TopFlattenQ16=recipe.topFlattenQ16;
Entry.ContourWarp=recipe.contourWarpSubvoxels;
Entry.DeltaX=x-static_cast<double>(island.centerSubvoxels[0]);
Entry.DeltaY=y-static_cast<double>(island.centerSubvoxels[1]);
Entry.Radial=std::hypot(Entry.DeltaX,Entry.DeltaY);
Entry.Angle=std::atan2(Entry.DeltaY,Entry.DeltaX);
Entry.LobePhase=LobePhase(recipe,island);
const double EffectiveRadius=std::max(1.0,static_cast<double>(island.radiusSubvoxels)
+LobeOffsetWithPhase(recipe,Entry.Angle,static_cast<double>(island.topZSubvoxels),
Entry.LobePhase));
const double Edge=std::clamp(Entry.Radial/EffectiveRadius,0.0,1.0);
const double Drop=(1.0-Q16ToDouble(recipe.topFlattenQ16))
*static_cast<double>(island.topHalfSubvoxels)*2.0*Edge*Edge;
Entry.TopSurface=static_cast<double>(island.topZSubvoxels)-Drop;
return Entry;
}
inline std::int64_t ScaledQ16(std::int64_t value, std::int64_t scaleQ16)
{ return (value * scaleQ16 + Q16 / 2) / Q16; }
@@ -406,42 +515,104 @@ namespace floating_island_detail
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);
// Keep four separate side lobes and a full-depth central buttress to close the measured
// underside cavity without driving the outer roots to one point.
// Garder quatre lobes lateraux et un renfort central profond pour fermer la cavite mesuree.
return 5;
}
inline double UnitHash(std::uint64_t hash)
{ return static_cast<double>(hash & 0xffffull) / 65535.0; }
struct FRootLobeParameters
{
double RootX=0.0,RootY=0.0,CenterZ=0.0;
double RadiusXSquared=1.0,RadiusYSquared=1.0,VerticalRadiusSquared=1.0;
double MinimumRadius=1.0;
};
struct FRootLobeCacheEntry
{
bool bValid=false;
std::uint32_t Seed=0;
std::uint64_t StableId=0;
std::int32_t RootIndex=0,GeometryVersion=0;
std::array<std::int64_t,3> Center{};
std::int64_t TopZ=0,Radius=0,TaperEndQ16=0;
std::int64_t ThicknessRatioQ16=0,CrustDepthBonusQ16=0,RootDepthRatioQ16=0;
FRootLobeParameters Parameters{};
};
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});
// Cache derived root constants per worker. The complete immutable input key keeps this
// value-only cache safe across worlds, epochs and worker schedules.
// Mettre en cache les constantes par worker; la cle complete evite tout partage d'etat.
constexpr std::size_t CacheSize=1024;
static_assert((CacheSize&(CacheSize-1))==0);
static thread_local std::array<FRootLobeCacheEntry,CacheSize> Cache{};
const std::uint64_t IndexKey=island.stableId^(island.stableId>>33)
^(static_cast<std::uint64_t>(recipe.seed)<<17)
^(static_cast<std::uint64_t>(static_cast<std::uint32_t>(rootIndex))*0x9e3779b97f4a7c15ull);
FRootLobeCacheEntry& Entry=Cache[static_cast<std::size_t>(IndexKey)&(CacheSize-1)];
const bool bKeyMatches=Entry.bValid&&Entry.Seed==recipe.seed
&&Entry.StableId==island.stableId&&Entry.RootIndex==rootIndex
&&Entry.GeometryVersion==recipe.geometryVersion
&&Entry.Center==island.centerSubvoxels
&&Entry.TopZ==island.topZSubvoxels&&Entry.Radius==island.radiusSubvoxels
&&Entry.TaperEndQ16==island.taperEndQ16
&&Entry.ThicknessRatioQ16==recipe.thicknessRatioQ16
&&Entry.CrustDepthBonusQ16==recipe.crustDepthBonusQ16
&&Entry.RootDepthRatioQ16==recipe.rootDepthRatioQ16;
if(!bKeyMatches)
{
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 bool centralButtress = rootIndex == 4;
const double orbit = centralButtress ? 0.0
: radius * (0.17 + 0.07 * UnitHash(roll));
const double rootX = static_cast<double>(island.centerSubvoxels[0])
+ (centralButtress ? 0.0 : pattern[static_cast<std::size_t>(rootIndex)][0] * orbit);
const double rootY = static_cast<double>(island.centerSubvoxels[1])
+ (centralButtress ? 0.0 : pattern[static_cast<std::size_t>(rootIndex)][1] * orbit);
const double shortRootVariation = UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x4c454eull));
const double lengthRatio = centralButtress ? 1.0 : (rootIndex == 0 ? 1.0
: Q16ToDouble(island.taperEndQ16) * (0.82 + 0.18 * shortRootVariation));
const double lowerDepth = rootDepth * lengthRatio;
const double embedDepth = rootDepth * (centralButtress ? 0.22 : (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 * (centralButtress ? 0.25
: 0.15 + 0.055 * UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x5258ull)));
const double ry = radius * (centralButtress ? 0.25
: 0.15 + 0.065 * UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x5259ull)));
Entry.bValid=true;
Entry.Seed=recipe.seed;
Entry.StableId=island.stableId;
Entry.RootIndex=rootIndex;
Entry.GeometryVersion=recipe.geometryVersion;
Entry.Center=island.centerSubvoxels;
Entry.TopZ=island.topZSubvoxels;
Entry.Radius=island.radiusSubvoxels;
Entry.TaperEndQ16=island.taperEndQ16;
Entry.ThicknessRatioQ16=recipe.thicknessRatioQ16;
Entry.CrustDepthBonusQ16=recipe.crustDepthBonusQ16;
Entry.RootDepthRatioQ16=recipe.rootDepthRatioQ16;
Entry.Parameters={rootX,rootY,centerZ,rx*rx,ry*ry,
verticalRadius*verticalRadius,std::min({rx,ry,verticalRadius})};
}
const FRootLobeParameters& Parameters=Entry.Parameters;
const double q = (x - Parameters.RootX) * (x - Parameters.RootX) / Parameters.RadiusXSquared
+ (y - Parameters.RootY) * (y - Parameters.RootY) / Parameters.RadiusYSquared
+ (z - Parameters.CenterZ) * (z - Parameters.CenterZ) / Parameters.VerticalRadiusSquared;
return (std::sqrt(std::max(q, 0.0)) - 1.0) * Parameters.MinimumRadius;
}
inline double ApplySurfaceDetail(const FloatingIslandRecipeV1& recipe,
@@ -482,19 +653,18 @@ namespace floating_island_detail
std::max({z - top, radial - envelope, bottomZ - z}), x, y, 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 FIslandColumnCacheEntry& Column=IslandColumnCache(recipe,island,x,y);
const double dx=Column.DeltaX;
const double dy=Column.DeltaY;
const double radial=Column.Radial;
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));
radius + LobeOffsetWithPhase(recipe,Column.Angle,z,Column.LobePhase));
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 top = Column.TopSurface;
const double topDistance = z - top;
const double sideDistance = radial - effectiveRadius;
const double undersideDistance = lowerSurface - z;
@@ -774,7 +944,9 @@ 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 landingFraction = recipe.geometryVersion >= 2 ? 0.38 : 0.62;
// Keep the narrow span exposed to open sky; a smaller fraction buries most of it in the island tops.
// Garder la passerelle etroite visible dans le vide ; une fraction plus faible l'enfouit dans les plateaux.
const double landingFraction = recipe.geometryVersion >= 2 ? 0.52 : 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;
@@ -912,6 +1084,10 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
|| !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion)
|| !floating_island_detail::ReadU32(recipeValue.Get("units_per_voxel"), manifest.recipe.unitsPerVoxel)
|| !floating_island_detail::ReadU32(recipeValue.Get("owner_voxel_size_microns"), manifest.recipe.ownerVoxelSizeMicrons)
|| !floating_island_detail::ReadU32(recipeValue.Get("core_voxel_size_microns"), manifest.recipe.coreVoxelSizeMicrons)
|| !floating_island_detail::ReadU32(recipeValue.Get("owner_to_core_scale_numerator"), manifest.recipe.ownerToCoreScaleNumerator)
|| !floating_island_detail::ReadU32(recipeValue.Get("owner_to_core_scale_denominator"), manifest.recipe.ownerToCoreScaleDenominator)
|| !floating_island_detail::ReadU32(recipeValue.Get("seed"), manifest.recipe.seed)
|| !floating_island_detail::ReadInteger(recipeValue.Get("origin_x_q"), manifest.recipe.layoutOriginXSubvoxels)
|| !floating_island_detail::ReadInteger(recipeValue.Get("origin_y_q"), manifest.recipe.layoutOriginYSubvoxels)
@@ -1026,6 +1202,10 @@ std::string FloatingIslandManifestV1::ToJson() const
<< floating_island_detail::HashText(value.contentHash) << "\",\"recipe\":{"
<< "\"schema_version\":" << recipe.schemaVersion
<< ",\"units_per_voxel\":" << recipe.unitsPerVoxel
<< ",\"owner_voxel_size_microns\":" << recipe.ownerVoxelSizeMicrons
<< ",\"core_voxel_size_microns\":" << recipe.coreVoxelSizeMicrons
<< ",\"owner_to_core_scale_numerator\":" << recipe.ownerToCoreScaleNumerator
<< ",\"owner_to_core_scale_denominator\":" << recipe.ownerToCoreScaleDenominator
<< ",\"seed\":" << recipe.seed
<< ",\"origin_x_q\":" << recipe.layoutOriginXSubvoxels
<< ",\"origin_y_q\":" << recipe.layoutOriginYSubvoxels
@@ -1190,6 +1370,17 @@ 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)))));
}
if (recipe.geometryVersion >= 2)
{
// Step the outer route islands around the middle island so the connected path still reads as separate floating masses.
// Decaler les iles externes autour de l'ile centrale pour distinguer les masses tout en gardant la route continue.
constexpr std::int64_t VerticalStagger = 3 * floating_island_detail::Q;
for (std::uint32_t i = 0; i < count; ++i)
{
if (i < middle) tops[i] -= VerticalStagger;
else if (i > middle) tops[i] += VerticalStagger;
}
}
for (std::uint32_t i = 0; i < count; ++i)
{
const std::uint64_t site = 0x100000000ull + i;
+154 -10
View File
@@ -936,6 +936,12 @@ public:
// edge interpolation and normals to the existing mesher.
float DensityAt(float X,float Y,float Z) const
{
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
{
++ThreadClassificationStats().NativeFloatFieldCalls;
return ApplyEditBrushes(IslandDensity,X,Y,Z);
}
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{
@@ -954,6 +960,13 @@ public:
float DensityAtLattice(int32 X,int32 Y,int32 Z) const
{
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),IslandDensity))
{
++ThreadClassificationStats().NativeLatticeFieldCalls;
return ApplyEditBrushes(IslandDensity,X,Y,Z);
}
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),NativeDensity))
@@ -975,6 +988,12 @@ public:
float DensityAtUnclamped(float X,float Y,float Z) const
{
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
{
++ThreadClassificationStats().NativeUnclampedFieldCalls;
return ApplyEditBrushes(IslandDensity,X,Y,Z);
}
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{
@@ -1007,11 +1026,18 @@ public:
Count=std::clamp(Count,0,8);
if(Count==0)return;
bool bHasLegacySurfaceSample=false;
bool bHasNativeIslandSample=false;
for(int32 Lane=0;Lane<Count;++Lane)
bHasLegacySurfaceSample|=UsesLegacySurfaceField(
static_cast<float>(XStart+Lane*XStep),static_cast<float>(Y),
static_cast<float>(Z));
if(bHasLegacySurfaceSample)
{
const float SampleX=static_cast<float>(XStart+Lane*XStep);
const float SampleY=static_cast<float>(Y);
const float SampleZ=static_cast<float>(Z);
bHasLegacySurfaceSample|=UsesLegacySurfaceField(SampleX,SampleY,SampleZ);
float IslandDensity=0.0f;
bHasNativeIslandSample|=NativeFloatingIslandDensityAt(
SampleX,SampleY,SampleZ,IslandDensity);
}
if(bHasLegacySurfaceSample||bHasNativeIslandSample)
{
for(int32 Lane=0;Lane<Count;++Lane)
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
@@ -1100,6 +1126,9 @@ private:
if(OutDetailDisplacement)*OutDetailDisplacement=0.0f;
if(OutCaveInfluence)*OutCaveInfluence=0.0f;
if(OutFloorInfluence)*OutFloorInfluence=0.0f;
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
return bApplyEdits?ApplyEditBrushes(IslandDensity,X,Y,Z):IslandDensity;
if(UsesLegacySurfaceField(X,Y,Z))
{
float NativeDensity=0.0f;
@@ -1191,17 +1220,22 @@ public:
int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad};
int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,Origin.Z+(Cells+1)*Step};
const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr;
bool bSurfaceOverlap=false,bOtherStrateOverlap=false,bBadPlan=false;
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr;
bool bSurfaceOverlap=false,bIslandOverlap=false,bOtherStrateOverlap=false;
bool bIslandOtherStrateOverlap=false,bBadPlan=false;
if(Owner&&Owner->Manager)
{
// Keep the delivered SurfaceWorld chunk mapping unchanged.
// Conserver le mapping de chunks SurfaceWorld deja livre.
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
/static_cast<float>(CHUNK_SIZE));
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax[2])
/static_cast<float>(CHUNK_SIZE));
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
{
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
==ECaveGeneratorType::SurfaceWorld)
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(
FIntVector(0,0,ChunkZ));
if(Type==ECaveGeneratorType::SurfaceWorld)
{
bSurfaceOverlap=true;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
@@ -1211,12 +1245,39 @@ public:
bBadPlan=true;
SurfacePlan=Candidate;
}
else bOtherStrateOverlap=true;
else if(Type!=ECaveGeneratorType::FloatingIslands)
bOtherStrateOverlap=true;
}
// Floating islands use the explicit 12.5 cm owner to 25 cm Core mapping:
// one legacy 4 m chunk occupies 16 Core voxels.
// Les iles flottantes suivent l'echelle explicite proprietaire/Core: 16 voxels Core.
constexpr int32 CoreVoxelsPerIslandChunk=CHUNK_SIZE/2;
const int32 FirstIslandChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
/static_cast<float>(CoreVoxelsPerIslandChunk));
const int32 LastIslandChunk=FMath::FloorToInt(static_cast<float>(BMax[2])
/static_cast<float>(CoreVoxelsPerIslandChunk));
for(int32 ChunkZ=FirstIslandChunk;ChunkZ<=LastIslandChunk;++ChunkZ)
{
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(
FIntVector(0,0,ChunkZ));
if(Type==ECaveGeneratorType::FloatingIslands)
{
bIslandOverlap=true;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
const auto* Candidate=FindWorldAssemblyFloatingIslandStratum(StrateIndex,ChunkZ);
if(!Candidate||(IslandPlan&&IslandPlan!=Candidate))bBadPlan=true;
IslandPlan=Candidate;
}
else bIslandOtherStrateOverlap=true;
}
}
const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false)
{
FBoxClassification Result{Verdict,bSurfaceOverlap&&!bOtherStrateOverlap,bUnknown};
const bool bNative=(bSurfaceOverlap&&!bOtherStrateOverlap)
||(bIslandOverlap&&!bIslandOtherStrateOverlap);
FBoxClassification Result{Verdict,bNative,bUnknown};
auto& Counts=ThreadClassificationStats();
Counts.Milliseconds+=std::chrono::duration<double,std::milli>(Clock::now()-Start).count();
if(!Result.bNativeSurface||bUnknown)++Counts.UnknownBoxes;
@@ -1227,7 +1288,9 @@ public:
};
// A box crossing recipes cannot use one field interval; unknown composition stays Mixed.
// Une boite qui traverse plusieurs recettes reste Mixed, car l'intervalle d'un seul champ ne suffit pas.
if(bBadPlan|| (bSurfaceOverlap&&bOtherStrateOverlap))
if(bBadPlan||(bSurfaceOverlap&&(bIslandOverlap||bOtherStrateOverlap))
||(bIslandOverlap&&(bIslandOtherStrateOverlap
||(!bSurfaceOverlap&&bOtherStrateOverlap))))
return Finish(EVoxelTileClass::Mixed,true);
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
@@ -1273,6 +1336,38 @@ public:
default:return Finish(EVoxelTileClass::Mixed,true);
}
}
if(bIslandOverlap)
{
if(!IslandPlan)return Finish(EVoxelTileClass::Mixed,true);
constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t CoreVoxelsPerLegacyChunk=CHUNK_SIZE/2;
const std::int64_t Bottom=static_cast<std::int64_t>(IslandPlan->bottomChunkZ)
*CoreVoxelsPerLegacyChunk*Scale;
const std::int64_t Top=(static_cast<std::int64_t>(IslandPlan->topChunkZ)+1)
*CoreVoxelsPerLegacyChunk*Scale;
const std::int64_t Seal=IslandPlan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t BoxBottom=static_cast<std::int64_t>(BMin[2])*Scale;
const std::int64_t BoxTop=static_cast<std::int64_t>(BMax[2])*Scale;
const bool bBottomSealOverlap=BoxBottom<Bottom+Seal&&BoxTop>=Bottom;
const bool bTopSealOverlap=BoxBottom<=Top&&BoxTop>Top-Seal;
if(bBottomSealOverlap)
return Finish(BoxBottom>=Bottom&&BoxTop<=Bottom+Seal
?EVoxelTileClass::AllSolid:EVoxelTileClass::Mixed);
if(bTopSealOverlap)
return Finish(BoxBottom>=Top-Seal&&BoxTop<=Top
?EVoxelTileClass::AllSolid:EVoxelTileClass::Mixed);
const VoxelForgeCore::FloatingIslandFieldV1 Block(
IslandPlan->manifest.recipe,IslandPlan->manifest.plan);
if(!Block.isValid())return Finish(EVoxelTileClass::Mixed,true);
const auto Interval=Block.interval(static_cast<int64>(BMin[0])*Scale,
static_cast<int64>(BMin[1])*Scale,static_cast<int64>(BMin[2])*Scale,
static_cast<int64>(BMax[0])*Scale,static_cast<int64>(BMax[1])*Scale,
static_cast<int64>(BMax[2])*Scale);
if(!Interval.known)return Finish(EVoxelTileClass::Mixed,true);
if(Interval.minimumDensitySubvoxels>=0)return Finish(EVoxelTileClass::AllAir);
if(Interval.maximumDensitySubvoxels<0)return Finish(EVoxelTileClass::AllSolid);
return Finish(EVoxelTileClass::Mixed);
}
const bool bRock=EvaluateFast((BMin[0]+BMax[0])*0.5,
(BMin[1]+BMax[1])*0.5,(BMin[2]+BMax[2])*0.5);
return Finish(bRock?EVoxelTileClass::AllSolid:EVoxelTileClass::AllAir);
@@ -1753,6 +1848,16 @@ private:
return It==WorldAssembly->plan.strates.end()?nullptr:&*It;
}
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* FindWorldAssemblyFloatingIslandStratum(
int32 StrateIndex,int32 ChunkZ) const
{
if(!WorldAssembly||StrateIndex<0)return nullptr;
const auto It=std::find_if(WorldAssembly->floatingIslands.begin(),
WorldAssembly->floatingIslands.end(),[StrateIndex,ChunkZ](const auto& Entry)
{return Entry.strateIndex==StrateIndex&&ChunkZ<=Entry.topChunkZ&&ChunkZ>=Entry.bottomChunkZ;});
return It==WorldAssembly->floatingIslands.end()?nullptr:&*It;
}
bool NativeSurfaceDensityAt(float X,float Y,float Z,float& OutDensity) const
{
if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
@@ -1778,6 +1883,45 @@ private:
return true;
}
bool NativeFloatingIslandDensityAt(float X,float Y,float Z,float& OutDensity) const
{
if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))return false;
// Convert Core query Z back to the owner's legacy chunk coordinate.
// Convertir Z du Core vers la coordonnee de chunk legacy du proprietaire.
constexpr int32 CoreVoxelsPerLegacyChunk=CHUNK_SIZE/2;
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CoreVoxelsPerLegacyChunk));
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
!=ECaveGeneratorType::FloatingIslands)return false;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
const auto* Plan=FindWorldAssemblyFloatingIslandStratum(StrateIndex,ChunkZ);
if(!Plan)return false;
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
constexpr double Maximum=1000000000000.0/Scale;
if(std::abs(static_cast<double>(X))>Maximum||std::abs(static_cast<double>(Y))>Maximum
||std::abs(static_cast<double>(Z))>Maximum)return false;
const auto ToFixed=[](float Value)
{return static_cast<int64>(std::llround(static_cast<double>(Value)
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));};
const VoxelForgeCore::FloatingIslandFieldV1 Field(
Plan->manifest.recipe,Plan->manifest.plan);
if(!Field.isValid())return false;
std::int64_t Density=Field.densitySubvoxels(ToFixed(X),ToFixed(Y),ToFixed(Z));
// A legacy 4 m chunk is 16 Core voxels at the published 12.5 cm to 25 cm scale.
// Un chunk legacy de 4 m represente 16 voxels Core avec l'echelle publiee.
const std::int64_t Bottom=static_cast<std::int64_t>(Plan->bottomChunkZ)
*CoreVoxelsPerLegacyChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const std::int64_t Top=(static_cast<std::int64_t>(Plan->topChunkZ)+1)
*CoreVoxelsPerLegacyChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t Zq=ToFixed(Z);
if(Zq>=Bottom&&Zq<Bottom+Seal)Density=std::min(Density,-std::max<std::int64_t>(1,Zq-Bottom));
if(Zq<=Top&&Zq>Top-Seal)Density=std::min(Density,-std::max<std::int64_t>(1,Top-Zq));
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
return std::isfinite(OutDensity);
}
bool UsesLegacySurfaceField(float X,float Y,float Z) const
{
if(!Owner||!Owner->Manager||!Owner->Generator)return false;
+150 -4
View File
@@ -8,6 +8,7 @@
#include <charconv>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <limits>
#include <sstream>
@@ -403,9 +404,91 @@ namespace world_assembly_detail
HashWord(hash, manifest.schemaVersion, 4);
HashWord(hash, manifest.recipe.contentHash, 8);
HashWord(hash, manifest.plan.contentHash, 8);
// Preserve the historical SurfaceWorld manifest digest when no island plan is attached.
if (!manifest.floatingIslands.empty())
{
HashWord(hash, 0x464c4f415449534cull, 8);
HashWord(hash, manifest.floatingIslands.size(), 4);
for (const auto& islands : manifest.floatingIslands)
{
HashWord(hash, static_cast<std::uint32_t>(islands.strateIndex), 4);
HashWord(hash, static_cast<std::uint32_t>(islands.topChunkZ), 4);
HashWord(hash, static_cast<std::uint32_t>(islands.bottomChunkZ), 4);
HashWord(hash, islands.manifest.contentHash, 8);
}
}
return hash;
}
inline void AppendJsonString(std::ostringstream& out, std::string_view value)
{
out << '"';
for (const unsigned char ch : value)
{
switch (ch)
{
case '"': out << "\\\""; break;
case '\\': out << "\\\\"; break;
case '\b': out << "\\b"; break;
case '\f': out << "\\f"; break;
case '\n': out << "\\n"; break;
case '\r': out << "\\r"; break;
case '\t': out << "\\t"; break;
default:
if (ch < 0x20) out << "\\u00" << std::hex << std::setw(2)
<< std::setfill('0') << static_cast<unsigned int>(ch) << std::dec;
else out << static_cast<char>(ch);
break;
}
}
out << '"';
}
inline void AppendJsonValue(std::ostringstream& out, const VFJson::Value& value)
{
if (std::holds_alternative<std::nullptr_t>(value.Data)) out << "null";
else if (const auto* boolean = std::get_if<bool>(&value.Data)) out << (*boolean ? "true" : "false");
else if (const auto* number = std::get_if<double>(&value.Data))
{
char buffer[64];
const auto result = std::to_chars(buffer, buffer + sizeof(buffer), *number,
std::chars_format::general, std::numeric_limits<double>::max_digits10);
if (result.ec == std::errc{}) out.write(buffer, result.ptr - buffer);
else out << '0';
}
else if (const auto* string = std::get_if<std::string>(&value.Data)) AppendJsonString(out, *string);
else if (const auto* array = std::get_if<VFJson::Value::Array>(&value.Data))
{
out << '[';
for (std::size_t i = 0; i < array->size(); ++i)
{ if (i) out << ','; AppendJsonValue(out, (*array)[i]); }
out << ']';
}
else
{
const auto& object = std::get<VFJson::Value::Object>(value.Data);
std::vector<std::string_view> keys;
keys.reserve(object.size());
for (const auto& entry : object) keys.push_back(entry.first);
std::sort(keys.begin(), keys.end());
out << '{';
for (std::size_t i = 0; i < keys.size(); ++i)
{
if (i) out << ',';
AppendJsonString(out, keys[i]); out << ':';
AppendJsonValue(out, object.at(std::string(keys[i])));
}
out << '}';
}
}
inline std::string JsonText(const VFJson::Value& value)
{
std::ostringstream out;
AppendJsonValue(out, value);
return out.str();
}
inline bool ParseRecipe(const VFJson::Value& value, WorldAssemblyRecipeV1& recipe,
std::string& error)
{
@@ -566,6 +649,26 @@ namespace world_assembly_detail
if (error.empty()) error = "world assembly manifest is missing a required field";
return std::nullopt;
}
const VFJson::Value& islandPlans = value.Get("floating_islands");
if (!std::holds_alternative<std::nullptr_t>(islandPlans.Data))
{
if (!islandPlans.IsArray() || islandPlans.AsArray().size() > MaxStrateCount)
{ error = "world assembly floating-island plans must be an array of at most sixty-four entries"; return std::nullopt; }
for (const VFJson::Value& islandValue : islandPlans.AsArray())
{
WorldAssemblyFloatingIslandPlanV1 entry;
if (!islandValue.IsObject()
|| !ReadI32(islandValue.Get("strate_index"), entry.strateIndex)
|| !ReadI32(islandValue.Get("top_chunk_z"), entry.topChunkZ)
|| !ReadI32(islandValue.Get("bottom_chunk_z"), entry.bottomChunkZ))
{ error = "world assembly floating-island slot is missing its resolved strate bounds"; return std::nullopt; }
const std::string nestedJson = JsonText(islandValue.Get("manifest"));
auto parsedIsland = FloatingIslandManifestV1::FromJson(nestedJson, error);
if (!parsedIsland) return std::nullopt;
entry.manifest = std::move(*parsedIsland);
manifest.floatingIslands.push_back(std::move(entry));
}
}
if (!manifest.validate(error)) return std::nullopt;
return manifest;
}
@@ -1292,8 +1395,10 @@ bool WorldAssemblyManifest::finalize(std::string& error)
if (schemaVersion != WorldAssemblySchemaVersion || plan.schemaVersion != WorldAssemblySchemaVersion
|| !world_assembly_detail::ValidateRecipe(recipe, error))
{ if (error.empty()) error = "world assembly manifest or plan version is unsupported"; return false; }
if (plan.strates.empty() || plan.strates.size() > world_assembly_detail::MaxStrateCount)
{ error = "world assembly plan must contain one to sixty-four finite strates"; return false; }
if ((plan.strates.empty() && floatingIslands.empty())
|| plan.strates.size() > world_assembly_detail::MaxStrateCount
|| floatingIslands.size() > world_assembly_detail::MaxStrateCount)
{ error = "world assembly must contain SurfaceWorld or floating-island plans within the sixty-four-strate limit"; return false; }
std::vector<std::int32_t> strateIds;
for (auto& stratum : plan.strates)
{
@@ -1323,6 +1428,25 @@ bool WorldAssemblyManifest::finalize(std::string& error)
strateIds.push_back(stratum.strateIndex);
stratum.contentHash = world_assembly_detail::HashStratum(stratum);
}
std::vector<std::int32_t> islandStrateIds;
for (auto& islands : floatingIslands)
{
if (islands.strateIndex < 0 || islands.topChunkZ < islands.bottomChunkZ
|| std::find(islandStrateIds.begin(), islandStrateIds.end(), islands.strateIndex) != islandStrateIds.end())
{ error = "world assembly contains invalid or duplicate floating-island strate bounds"; return false; }
if (!islands.manifest.validate(error)) return false;
if (islands.manifest.recipe.seed != recipe.seed)
{ error = "floating-island seed does not match the world assembly season seed"; return false; }
constexpr std::int64_t CoreVoxelsPerLegacyChunk = 16;
const std::int64_t bottomQ = static_cast<std::int64_t>(islands.bottomChunkZ)
* CoreVoxelsPerLegacyChunk * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t topQ = (static_cast<std::int64_t>(islands.topChunkZ) + 1)
* CoreVoxelsPerLegacyChunk * WorldAssemblySubvoxelsPerVoxel;
if (islands.manifest.recipe.layoutMinimumZSubvoxels < bottomQ
|| islands.manifest.recipe.layoutMaximumZSubvoxels > topQ)
{ error = "floating-island recipe layout escapes the resolved 12.5 cm legacy strate bounds"; return false; }
islandStrateIds.push_back(islands.strateIndex);
}
recipe.contentHash = world_assembly_detail::HashRecipe(recipe);
plan.contentHash = world_assembly_detail::HashPlan(plan);
contentHash = world_assembly_detail::HashManifest(*this);
@@ -1337,13 +1461,19 @@ bool WorldAssemblyManifest::validate(std::string& error) const
const std::uint64_t expectedManifest = contentHash;
std::vector<std::uint64_t> expectedStrates;
for (const auto& stratum : plan.strates) expectedStrates.push_back(stratum.contentHash);
std::vector<std::uint64_t> expectedIslandManifests;
for (const auto& islands : floatingIslands) expectedIslandManifests.push_back(islands.manifest.contentHash);
if (!copy.finalize(error)) return false;
if (expectedRecipe != copy.recipe.contentHash || expectedPlan != copy.plan.contentHash
|| expectedManifest != copy.contentHash || expectedStrates.size() != copy.plan.strates.size())
|| expectedManifest != copy.contentHash || expectedStrates.size() != copy.plan.strates.size()
|| expectedIslandManifests.size() != copy.floatingIslands.size())
{ error = "world assembly recipe, plan or manifest content hash does not match its fields"; return false; }
for (std::size_t i = 0; i < expectedStrates.size(); ++i)
if (expectedStrates[i] != copy.plan.strates[i].contentHash)
{ error = "world assembly stratum content hash does not match its fields"; return false; }
for (std::size_t i = 0; i < expectedIslandManifests.size(); ++i)
if (expectedIslandManifests[i] != copy.floatingIslands[i].manifest.contentHash)
{ error = "world assembly floating-island content hash does not match its fields"; return false; }
return true;
}
@@ -1474,7 +1604,23 @@ std::string WorldAssemblyManifest::ToJson() const
}
out << "]}";
}
out << "]}}";
if (value.floatingIslands.empty()) out << "]}}";
else
{
out << "]},\"floating_islands\":[";
for (std::size_t i = 0; i < value.floatingIslands.size(); ++i)
{
const auto& islands = value.floatingIslands[i];
if (i) out << ',';
const std::string nested = islands.manifest.ToJson();
if (nested.empty()) return {};
out << "{\"strate_index\":" << islands.strateIndex
<< ",\"top_chunk_z\":" << islands.topChunkZ
<< ",\"bottom_chunk_z\":" << islands.bottomChunkZ
<< ",\"manifest\":" << nested << '}';
}
out << "]}";
}
return out.str();
}