proto: liminal pass 2 - hills to the horizon, swept road over a cloud floor, pillar grid (L142)
Captures stream LOD0-8 (hills repeat at 80 m to the horizon). Road: cubic swept solid deck, 376.8 m, 8 m wide, max 12.06 deg, 17 supports, cloud floor ~45 m below; walk audit 0 blocked/unsupported, 300 m negative control fails as expected. 15x15 identical pillar grid at 76 m. Owner canonical hashes unchanged; fast tier, Clang/GCC green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
No files matched your search
@@ -891,6 +891,7 @@ namespace VoxelForgeCore
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV1 = 1;
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV2 = 2;
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV3 = 3;
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV4 = 4;
|
||||
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV1 = 1;
|
||||
inline constexpr std::uint32_t ResolvedLayoutOreVeinVocabularyVersionV1 = 1;
|
||||
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
|
||||
@@ -904,7 +905,8 @@ namespace VoxelForgeCore
|
||||
fillPillar = 3,
|
||||
fillDais = 4,
|
||||
fillMonolith = 5,
|
||||
fillPlatform = 6
|
||||
fillPlatform = 6,
|
||||
fillSweptDeckSegment = 7
|
||||
};
|
||||
|
||||
struct ResolvedLayoutStampRequestV1
|
||||
@@ -914,6 +916,8 @@ namespace VoxelForgeCore
|
||||
ResolvedSdfStampKindV1 kind = ResolvedSdfStampKindV1::carveEllipsoid;
|
||||
// A preferred center and a bounded deterministic search radius.
|
||||
std::array<std::int64_t, 3> anchorPositionMicrometres{};
|
||||
// Present only for fillSweptDeckSegment; defines its directed centerline.
|
||||
std::array<std::array<std::int64_t, 3>, 2> sweepEndpointsMicrometres{};
|
||||
std::int64_t placementRadiusMicrometres = 0;
|
||||
std::array<std::int64_t, 3> halfExtentsMicrometres{};
|
||||
std::int64_t smoothWidthMicrometres = 0;
|
||||
@@ -933,6 +937,7 @@ namespace VoxelForgeCore
|
||||
std::int32_t strateIndex = -1;
|
||||
ResolvedSdfStampKindV1 kind = ResolvedSdfStampKindV1::carveEllipsoid;
|
||||
std::array<std::int64_t, 3> positionMicrometres{};
|
||||
std::array<std::array<std::int64_t, 3>, 2> sweepEndpointsMicrometres{};
|
||||
std::array<std::int64_t, 3> boundsMinimumMicrometres{};
|
||||
std::array<std::int64_t, 3> boundsMaximumMicrometres{};
|
||||
std::array<std::int64_t, 3> halfExtentsMicrometres{};
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace
|
||||
{
|
||||
const auto value=static_cast<std::uint8_t>(kind);
|
||||
return value>=static_cast<std::uint8_t>(ResolvedSdfStampKindV1::carveEllipsoid)
|
||||
&&value<=static_cast<std::uint8_t>(ResolvedSdfStampKindV1::fillPlatform);
|
||||
&&value<=static_cast<std::uint8_t>(ResolvedSdfStampKindV1::fillSweptDeckSegment);
|
||||
}
|
||||
bool LayoutStampIsMotif(ResolvedSdfStampKindV1 kind)
|
||||
{return kind==ResolvedSdfStampKindV1::fillMonolith
|
||||
@@ -598,6 +598,14 @@ namespace
|
||||
<<",\"irregularity_wavelength_um\":"<<stamp.irregularityWavelengthMicrometres
|
||||
<<",\"irregularity_seed\":"<<stamp.irregularitySeed
|
||||
<<",\"yaw_quarter_turns\":"<<static_cast<unsigned>(stamp.yawQuarterTurns);
|
||||
if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
out<<",\"sweep_endpoints_um\":[["
|
||||
<<stamp.sweepEndpointsMicrometres[0][0]<<','
|
||||
<<stamp.sweepEndpointsMicrometres[0][1]<<','
|
||||
<<stamp.sweepEndpointsMicrometres[0][2]<<"],["
|
||||
<<stamp.sweepEndpointsMicrometres[1][0]<<','
|
||||
<<stamp.sweepEndpointsMicrometres[1][1]<<','
|
||||
<<stamp.sweepEndpointsMicrometres[1][2]<<"]]";
|
||||
if(s.stampVocabularyVersion>=ResolvedLayoutStampVocabularyVersionV3)
|
||||
out<<",\"rounding_radius_um\":"<<stamp.roundingRadiusMicrometres
|
||||
<<",\"minimum_spacing_um\":"<<stamp.minimumSpacingMicrometres
|
||||
@@ -1134,8 +1142,9 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
}
|
||||
else if(stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV1
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV2
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV3)
|
||||
add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1, 2 or 3");
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV3
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV4)
|
||||
add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1, 2, 3 or 4");
|
||||
std::vector<std::string> stampIds;
|
||||
for(const auto& stamp:stamps)
|
||||
{
|
||||
@@ -1145,6 +1154,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
else stampIds.push_back(stamp.stableId);
|
||||
const bool motif=LayoutStampIsMotif(stamp.kind);
|
||||
if(!LayoutStampKindValid(stamp.kind)||stamp.yawQuarterTurns>3
|
||||
||(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment
|
||||
&&stampVocabularyVersion<ResolvedLayoutStampVocabularyVersionV4)
|
||||
||(motif&&stampVocabularyVersion<ResolvedLayoutStampVocabularyVersionV3))
|
||||
add("stamp_kind","stamp has an unsupported kind or orientation");
|
||||
bool validExtents=true;
|
||||
@@ -1159,6 +1170,24 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
||(stamp.irregularityAmplitudeMicrometres>0
|
||||
&&stamp.irregularityWavelengthMicrometres<=0))
|
||||
add("stamp_parameters",stamp.stableId+" has invalid or unbounded geometry parameters");
|
||||
if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
{
|
||||
double lengthSquared=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const auto a=stamp.sweepEndpointsMicrometres[0][axis];
|
||||
const auto b=stamp.sweepEndpointsMicrometres[1][axis];
|
||||
if(a<-1000000000000ll||a>1000000000000ll
|
||||
||b<-1000000000000ll||b>1000000000000ll)
|
||||
add("stamp_sweep",stamp.stableId+" has an unbounded endpoint");
|
||||
const double delta=static_cast<double>(stamp.sweepEndpointsMicrometres[1][axis])
|
||||
-stamp.sweepEndpointsMicrometres[0][axis];
|
||||
lengthSquared+=delta*delta;
|
||||
}
|
||||
if(!(lengthSquared>0.0)
|
||||
||std::sqrt(lengthSquared)>2.0*stamp.halfExtentsMicrometres[0]+1.0)
|
||||
add("stamp_sweep",stamp.stableId+" has invalid endpoints or longitudinal extent");
|
||||
}
|
||||
if(motif)
|
||||
{
|
||||
const auto minimumExtent=*std::min_element(stamp.halfExtentsMicrometres.begin(),
|
||||
@@ -1186,16 +1215,39 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
const std::int64_t pad=motif
|
||||
?(stamp.smoothWidthMicrometres+5)/6+stamp.irregularityAmplitudeMicrometres
|
||||
:stamp.smoothWidthMicrometres+stamp.irregularityAmplitudeMicrometres;
|
||||
const std::array<std::int64_t,3> extents={
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?1:0]+pad,
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?0:1]+pad,
|
||||
stamp.halfExtentsMicrometres[2]+pad};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
if(stamp.boundsMinimumMicrometres[axis]
|
||||
!=stamp.positionMicrometres[axis]-extents[axis]
|
||||
||stamp.boundsMaximumMicrometres[axis]
|
||||
!=stamp.positionMicrometres[axis]+extents[axis])
|
||||
add("stamp_bounds",stamp.stableId+" conservative bounds do not match its parameters");
|
||||
if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
{
|
||||
double radiusSquared=0.0;
|
||||
for(const auto extent:stamp.halfExtentsMicrometres)
|
||||
{
|
||||
const double expanded=static_cast<double>(extent+pad);
|
||||
radiusSquared+=expanded*expanded;
|
||||
}
|
||||
const auto radius=static_cast<std::int64_t>(std::ceil(std::sqrt(radiusSquared)));
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const auto minimum=std::min(stamp.sweepEndpointsMicrometres[0][axis],
|
||||
stamp.sweepEndpointsMicrometres[1][axis]);
|
||||
const auto maximum=std::max(stamp.sweepEndpointsMicrometres[0][axis],
|
||||
stamp.sweepEndpointsMicrometres[1][axis]);
|
||||
if(stamp.boundsMinimumMicrometres[axis]!=minimum-radius
|
||||
||stamp.boundsMaximumMicrometres[axis]!=maximum+radius)
|
||||
add("stamp_bounds",stamp.stableId+" conservative sweep bounds do not match its parameters");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::array<std::int64_t,3> extents={
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?1:0]+pad,
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?0:1]+pad,
|
||||
stamp.halfExtentsMicrometres[2]+pad};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
if(stamp.boundsMinimumMicrometres[axis]
|
||||
!=stamp.positionMicrometres[axis]-extents[axis]
|
||||
||stamp.boundsMaximumMicrometres[axis]
|
||||
!=stamp.positionMicrometres[axis]+extents[axis])
|
||||
add("stamp_bounds",stamp.stableId+" conservative bounds do not match its parameters");
|
||||
}
|
||||
std::string safetyReason;
|
||||
if(!LayoutStampSafe(*this,stamp,&safetyReason))
|
||||
add("stamp_placement",stamp.stableId+" is unsafe: "+safetyReason);
|
||||
@@ -1497,6 +1549,13 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||
target[axis]=values[axis].Integer();
|
||||
};
|
||||
readTriplet("position_um",stamp.positionMicrometres);
|
||||
const auto& sweepEndpoints=v.Get("sweep_endpoints_um").AsArray();
|
||||
for(std::size_t endpoint=0;endpoint<2&&endpoint<sweepEndpoints.size();++endpoint)
|
||||
{
|
||||
const auto& values=sweepEndpoints[endpoint].AsArray();
|
||||
for(std::size_t axis=0;axis<3&&axis<values.size();++axis)
|
||||
stamp.sweepEndpointsMicrometres[endpoint][axis]=values[axis].Integer();
|
||||
}
|
||||
readTriplet("bounds_min_um",stamp.boundsMinimumMicrometres);
|
||||
readTriplet("bounds_max_um",stamp.boundsMaximumMicrometres);
|
||||
readTriplet("half_extents_um",stamp.halfExtentsMicrometres);
|
||||
@@ -1619,7 +1678,10 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
std::sort(ordered.begin(),ordered.end(),[](const auto& a,const auto& b)
|
||||
{return a.stableId<b.stableId;});
|
||||
ResolvedLayoutSnapshotV3 candidate=layout;
|
||||
candidate.stampVocabularyVersion=ResolvedLayoutStampVocabularyVersionV1;
|
||||
const bool hasSweptDeck=std::any_of(ordered.begin(),ordered.end(),[](const auto& request)
|
||||
{return request.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment;});
|
||||
candidate.stampVocabularyVersion=hasSweptDeck
|
||||
?ResolvedLayoutStampVocabularyVersionV4:ResolvedLayoutStampVocabularyVersionV1;
|
||||
candidate.stamps.clear();candidate.stamps.reserve(ordered.size());
|
||||
for(std::size_t requestIndex=0;requestIndex<ordered.size();++requestIndex)
|
||||
{
|
||||
@@ -1630,6 +1692,8 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
if(!LayoutStampKindValid(request.kind)||request.yawQuarterTurns>3
|
||||
||request.placementRadiusMicrometres<0
|
||||
||request.placementRadiusMicrometres>100000000
|
||||
||(request.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment
|
||||
&&request.placementRadiusMicrometres!=0)
|
||||
||request.smoothWidthMicrometres<0||request.smoothWidthMicrometres>10000000
|
||||
||request.irregularityAmplitudeMicrometres<0
|
||||
||request.irregularityAmplitudeMicrometres>10000000
|
||||
@@ -1648,6 +1712,23 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
for(const auto coordinate:request.anchorPositionMicrometres)
|
||||
if(coordinate<-1000000000000ll||coordinate>1000000000000ll)
|
||||
{error="stamp request "+request.stableId+" has an unbounded anchor";return false;}
|
||||
if(request.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
{
|
||||
double lengthSquared=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const auto a=request.sweepEndpointsMicrometres[0][axis];
|
||||
const auto b=request.sweepEndpointsMicrometres[1][axis];
|
||||
if(a<-1000000000000ll||a>1000000000000ll
|
||||
||b<-1000000000000ll||b>1000000000000ll)
|
||||
{error="stamp request "+request.stableId+" has an unbounded sweep endpoint";return false;}
|
||||
const double delta=static_cast<double>(b)-a;
|
||||
lengthSquared+=delta*delta;
|
||||
}
|
||||
if(!(lengthSquared>0.0)
|
||||
||std::sqrt(lengthSquared)>2.0*request.halfExtentsMicrometres[0]+1.0)
|
||||
{error="stamp request "+request.stableId+" has invalid swept-segment extents";return false;}
|
||||
}
|
||||
|
||||
// The shared JSON value type stores numbers as IEEE-754 doubles; keep the
|
||||
// serialized seed in its exact integer range so the canonical round trip holds.
|
||||
@@ -1666,6 +1747,7 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
{
|
||||
ResolvedLayoutStampV1 stamp;stamp.stableId=request.stableId;
|
||||
stamp.strateIndex=request.strateIndex;stamp.kind=request.kind;
|
||||
stamp.sweepEndpointsMicrometres=request.sweepEndpointsMicrometres;
|
||||
stamp.halfExtentsMicrometres=request.halfExtentsMicrometres;
|
||||
stamp.smoothWidthMicrometres=request.smoothWidthMicrometres;
|
||||
stamp.irregularityAmplitudeMicrometres=request.irregularityAmplitudeMicrometres;
|
||||
@@ -1687,14 +1769,36 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
stamp.positionMicrometres[axis]=request.anchorPositionMicrometres[axis]+offset[axis];
|
||||
const std::int64_t pad=stamp.smoothWidthMicrometres
|
||||
+stamp.irregularityAmplitudeMicrometres;
|
||||
const std::array<std::int64_t,3> extents={
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?1:0]+pad,
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?0:1]+pad,
|
||||
stamp.halfExtentsMicrometres[2]+pad};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
{
|
||||
stamp.boundsMinimumMicrometres[axis]=stamp.positionMicrometres[axis]-extents[axis];
|
||||
stamp.boundsMaximumMicrometres[axis]=stamp.positionMicrometres[axis]+extents[axis];
|
||||
double radiusSquared=0.0;
|
||||
for(const auto extent:stamp.halfExtentsMicrometres)
|
||||
{
|
||||
const double expanded=static_cast<double>(extent+pad);
|
||||
radiusSquared+=expanded*expanded;
|
||||
}
|
||||
const std::int64_t radius=static_cast<std::int64_t>(std::ceil(std::sqrt(radiusSquared)));
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const auto minimum=std::min(stamp.sweepEndpointsMicrometres[0][axis],
|
||||
stamp.sweepEndpointsMicrometres[1][axis]);
|
||||
const auto maximum=std::max(stamp.sweepEndpointsMicrometres[0][axis],
|
||||
stamp.sweepEndpointsMicrometres[1][axis]);
|
||||
stamp.boundsMinimumMicrometres[axis]=minimum-radius;
|
||||
stamp.boundsMaximumMicrometres[axis]=maximum+radius;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::array<std::int64_t,3> extents={
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?1:0]+pad,
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?0:1]+pad,
|
||||
stamp.halfExtentsMicrometres[2]+pad};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
stamp.boundsMinimumMicrometres[axis]=stamp.positionMicrometres[axis]-extents[axis];
|
||||
stamp.boundsMaximumMicrometres[axis]=stamp.positionMicrometres[axis]+extents[axis];
|
||||
}
|
||||
}
|
||||
if(!LayoutStampSafe(candidate,stamp))continue;
|
||||
candidate.stamps.push_back(std::move(stamp));placed=true;break;
|
||||
|
||||
@@ -3535,6 +3535,7 @@ private:
|
||||
{
|
||||
const VoxelForgeCore::ResolvedLayoutStampV1* record=nullptr;
|
||||
std::array<float,3> positionVoxels{},halfExtentsVoxels{};
|
||||
std::array<std::array<float,3>,2> sweepEndpointsVoxels{};
|
||||
float smoothWidthVoxels=0.0f;
|
||||
float irregularityAmplitudeVoxels=0.0f;
|
||||
float irregularityWavelengthVoxels=0.0f;
|
||||
@@ -3700,9 +3701,15 @@ private:
|
||||
StampSpatialBounds.push_back(bound);
|
||||
FStampRuntime runtime;runtime.record=&stamp;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
runtime.positionVoxels[axis]=static_cast<float>(
|
||||
static_cast<double>(stamp.positionMicrometres[axis])
|
||||
/Owner->CoreVoxelSizeMicrons);
|
||||
for(std::size_t endpoint=0;endpoint<2;++endpoint)
|
||||
runtime.sweepEndpointsVoxels[endpoint][axis]=static_cast<float>(
|
||||
static_cast<double>(stamp.sweepEndpointsMicrometres[endpoint][axis])
|
||||
/Owner->CoreVoxelSizeMicrons);
|
||||
}
|
||||
const std::size_t xAxis=stamp.yawQuarterTurns%2?1:0;
|
||||
const std::size_t yAxis=stamp.yawQuarterTurns%2?0:1;
|
||||
runtime.halfExtentsVoxels={
|
||||
@@ -7152,6 +7159,35 @@ private:
|
||||
std::min(0.35f,std::min({stamp.halfExtentsVoxels[0],
|
||||
stamp.halfExtentsVoxels[1],stamp.halfExtentsVoxels[2]})*0.15f));
|
||||
break;
|
||||
case VoxelForgeCore::ResolvedSdfStampKindV1::fillSweptDeckSegment:
|
||||
{
|
||||
std::array<float,3> tangent{},side{},up{},delta{};
|
||||
float lengthSquared=0.0f,horizontalSquared=0.0f;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
delta[axis]=stamp.sweepEndpointsVoxels[1][axis]
|
||||
-stamp.sweepEndpointsVoxels[0][axis];
|
||||
lengthSquared+=delta[axis]*delta[axis];
|
||||
if(axis<2)horizontalSquared+=delta[axis]*delta[axis];
|
||||
}
|
||||
const float segmentLength=std::sqrt(lengthSquared);
|
||||
const float horizontalLength=std::sqrt(horizontalSquared);
|
||||
if(!(segmentLength>1.0e-5f&&horizontalLength>1.0e-5f))
|
||||
return std::numeric_limits<float>::infinity();
|
||||
for(std::size_t axis=0;axis<3;++axis)tangent[axis]=delta[axis]/segmentLength;
|
||||
side={-delta[1]/horizontalLength,delta[0]/horizontalLength,0.0f};
|
||||
up={-tangent[2]*side[1],tangent[2]*side[0],
|
||||
tangent[0]*side[1]-tangent[1]*side[0]};
|
||||
const std::array<float,3> offset={
|
||||
x-stamp.positionVoxels[0],y-stamp.positionVoxels[1],z-stamp.positionVoxels[2]};
|
||||
const auto dot=[&](const std::array<float,3>& axis)
|
||||
{return offset[0]*axis[0]+offset[1]*axis[1]+offset[2]*axis[2];};
|
||||
const float rounded=std::min(0.35f,
|
||||
std::min(stamp.halfExtentsVoxels[1],stamp.halfExtentsVoxels[2])*0.15f);
|
||||
distance=StampRoundedBoxSdf(dot(tangent),dot(side),dot(up),
|
||||
stamp.halfExtentsVoxels,rounded);
|
||||
break;
|
||||
}
|
||||
default:return std::numeric_limits<float>::infinity();
|
||||
}
|
||||
if(stamp.irregularityAmplitudeVoxels>0.0f)
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the L142 liminal pictures and walk-audit controls."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
|
||||
ROOT / "Standalone/data/owner_world.json"))
|
||||
OUT_DEFAULT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l142"))
|
||||
|
||||
FLAT_FIELD = {
|
||||
"version": 1,
|
||||
"kind": "flatField",
|
||||
"minimum_arena_clearance_m": 0.0,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--out", type=Path, default=OUT_DEFAULT / "fixtures")
|
||||
args = parser.parse_args()
|
||||
args.out.mkdir(parents=True, exist_ok=True)
|
||||
base = json.loads(OWNER.read_text(encoding="utf-8"))
|
||||
|
||||
monolith = dict(base)
|
||||
monolith["surface_profile_override"] = dict(FLAT_FIELD)
|
||||
monolith["motif_family_override"] = {
|
||||
"version": 1,
|
||||
"strate_index": 0,
|
||||
"placement_policy": "seed,strate,stable_id",
|
||||
"motifs": [{
|
||||
"stable_id": "liminal/oversized-familiar-monolith-v1",
|
||||
"kind": "cappedCylinder",
|
||||
"radius_m": 1.6,
|
||||
"height_m": 26.0,
|
||||
"route_side_offset_m": 42.0,
|
||||
"placement_radius_m": 0.0,
|
||||
"fillet_radius_m": 0.6,
|
||||
"blend_width_m": 1.2,
|
||||
"minimum_spacing_m": 0.0,
|
||||
"yaw_quarter_turns": 0,
|
||||
"attachment": "seated_floor",
|
||||
}],
|
||||
}
|
||||
|
||||
hills = dict(base)
|
||||
hills["surface_profile_override"] = {
|
||||
"version": 1,
|
||||
"kind": "periodicHills",
|
||||
"amplitude_x_m": 2.2,
|
||||
"amplitude_y_m": 2.2,
|
||||
"wavelength_x_m": 80.0,
|
||||
"wavelength_y_m": 80.0,
|
||||
"minimum_arena_clearance_m": 0.0,
|
||||
}
|
||||
|
||||
road_override = {
|
||||
"stable_id": "liminal/road-clouds-v2",
|
||||
"strate_index": 0,
|
||||
"start_xy_m": [120.0, 340.0],
|
||||
"path_length_m": 360.0,
|
||||
"curve_control_offset_m": 84.0,
|
||||
"deck_height_above_field_m": 44.0,
|
||||
"rise_control_m": 22.0,
|
||||
"half_width_m": 4.0,
|
||||
"half_thickness_m": 1.0,
|
||||
"sample_spacing_m": 2.0,
|
||||
"support_spacing_m": 24.0,
|
||||
"support_radius_m": 0.55,
|
||||
}
|
||||
road = dict(base)
|
||||
road["surface_profile_override"] = dict(FLAT_FIELD)
|
||||
road["liminal_road_override"] = dict(road_override)
|
||||
|
||||
short_control = dict(base)
|
||||
short_control["surface_profile_override"] = dict(FLAT_FIELD)
|
||||
short_override = dict(road_override)
|
||||
short_override["path_length_m"] = 180.0
|
||||
short_control["liminal_road_override"] = short_override
|
||||
|
||||
pillars = dict(base)
|
||||
pillars["surface_profile_override"] = dict(FLAT_FIELD)
|
||||
pillars["liminal_pillar_grid_override"] = {
|
||||
"stable_id": "liminal/horizon-pillar-grid-v1",
|
||||
"strate_index": 0,
|
||||
"center_xy_m": [700.0, 800.0],
|
||||
"rows": 15,
|
||||
"columns": 15,
|
||||
"spacing_m": 76.0,
|
||||
"radius_m": 5.5,
|
||||
"height_m": 40.0,
|
||||
}
|
||||
|
||||
fixtures = (
|
||||
("flatField-monolith", monolith),
|
||||
("periodicHills", hills),
|
||||
("road-in-clouds", road),
|
||||
("road-short-control", short_control),
|
||||
("pillar-grid", pillars),
|
||||
)
|
||||
for name, fixture in fixtures:
|
||||
path = args.out / f"liminal-{name}.json"
|
||||
path.write_text(json.dumps(fixture, indent=2) + "\n", encoding="utf-8")
|
||||
print(f"[L142Fixture] name={name} file={path} status=PASS")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,433 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run L142 liminal determinism checks, walk audits and multi-LOD captures."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT_DEFAULT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l142"))
|
||||
CAPTURE_DEFAULT = Path(__file__).resolve().parent / "captures"
|
||||
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
|
||||
Q_PER_METRE = 1024.0
|
||||
FOG_COLOR = "0.66,0.73,0.77"
|
||||
|
||||
sys.path.insert(0, str(ROOT / "Standalone/Tools/verify"))
|
||||
from runner import bounded_run, current_stamp, first_stamp
|
||||
|
||||
|
||||
def fmt(values) -> str:
|
||||
return ",".join(f"{value:.6f}" for value in values)
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def run_step(name: str, command: list[object], log: Path, *, env: dict[str, str],
|
||||
stamped: bool = False, timeout_seconds: int = 1740,
|
||||
expected_exit: int = 0) -> str:
|
||||
expected_stamp = current_stamp() if stamped else None
|
||||
if stamped:
|
||||
binary = Path(str(command[0]))
|
||||
probe = bounded_run([str(binary), "--print-source-stamp"], timeout=15)
|
||||
if probe is None or probe.returncode != 0 or first_stamp(probe.stdout or "") != expected_stamp:
|
||||
raise RuntimeError(f"{name} refused a stale executable: {binary}")
|
||||
started = time.monotonic()
|
||||
log.parent.mkdir(parents=True, exist_ok=True)
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run([*LIMIT, f"timeout", f"{timeout_seconds}s",
|
||||
*map(str, command)], cwd=ROOT, env=env,
|
||||
stdout=stream, stderr=subprocess.STDOUT,
|
||||
timeout=timeout_seconds + 5, check=False)
|
||||
elapsed = time.monotonic() - started
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
print(f"[L142Run] step={name} elapsed_s={elapsed:.3f} exit={result.returncode} log={log}",
|
||||
flush=True)
|
||||
if stamped and (first_stamp(output) != expected_stamp or current_stamp() != expected_stamp):
|
||||
raise RuntimeError(f"{name} source stamp mismatch; see {log}")
|
||||
if result.returncode != expected_exit:
|
||||
raise RuntimeError(f"{name} exit={result.returncode}, expected={expected_exit}; see {log}")
|
||||
return output
|
||||
|
||||
|
||||
def route_frame(manifest_path: Path) -> tuple[tuple[float, float, float],
|
||||
tuple[float, float, float]]:
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
strata = [entry for entry in manifest["plan"]["strates"]
|
||||
if entry.get("route") and len(entry["route"]) >= 2]
|
||||
if not strata:
|
||||
raise RuntimeError(f"no planned SurfaceWorld route in {manifest_path}")
|
||||
route = strata[0]["route"]
|
||||
points = [tuple(float(value) / Q_PER_METRE for value in item["position_q"])
|
||||
for item in route]
|
||||
lengths = [math.dist(a, b) for a, b in zip(points, points[1:])]
|
||||
total = sum(lengths)
|
||||
if total <= 1.0e-6:
|
||||
raise RuntimeError(f"degenerate planned route in {manifest_path}")
|
||||
|
||||
def at(distance: float) -> tuple[float, float, float]:
|
||||
remaining = max(0.0, min(total, distance))
|
||||
for index, (a, b, length) in enumerate(zip(points, points[1:], lengths)):
|
||||
if remaining <= length or index == len(lengths) - 1:
|
||||
if length <= 1.0e-9:
|
||||
return b
|
||||
t = remaining / length
|
||||
return tuple(a[axis] + (b[axis] - a[axis]) * t for axis in range(3))
|
||||
remaining -= length
|
||||
return points[-1]
|
||||
|
||||
center = at(total * 0.5)
|
||||
before, after = at(total * 0.5 - 1.0), at(total * 0.5 + 1.0)
|
||||
dx, dy = after[0] - before[0], after[1] - before[1]
|
||||
length = math.hypot(dx, dy)
|
||||
if length <= 1.0e-9:
|
||||
raise RuntimeError(f"degenerate route tangent in {manifest_path}")
|
||||
return center, (dx / length, dy / length, 0.0)
|
||||
|
||||
|
||||
def camera_angles(camera, target) -> tuple[float, float]:
|
||||
dx, dy, dz = (target[index] - camera[index] for index in range(3))
|
||||
return math.degrees(math.atan2(dx, dy)), math.degrees(math.atan2(dz, math.hypot(dx, dy)))
|
||||
|
||||
|
||||
def point_toward(camera, target) -> tuple[float, ...]:
|
||||
yaw, pitch = camera_angles(camera, target)
|
||||
return (*camera, yaw, pitch)
|
||||
|
||||
|
||||
def stamp(layout: dict, stable_id: str) -> dict:
|
||||
for record in layout.get("stamps", []):
|
||||
if record.get("stable_id") == stable_id:
|
||||
return record
|
||||
raise RuntimeError(f"missing resolved stamp {stable_id}")
|
||||
|
||||
|
||||
def make_views(name: str, manifest_path: Path, layout_path: Path):
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
layout = json.loads(layout_path.read_text(encoding="utf-8"))
|
||||
center, tangent = route_frame(manifest_path)
|
||||
tx, ty, _ = tangent
|
||||
nx, ny = -ty, tx
|
||||
base_height = manifest["recipe"]["surface_profile"]["h0_q"] / Q_PER_METRE
|
||||
if name == "flatField-monolith":
|
||||
monolith = stamp(layout, "liminal/oversized-familiar-monolith-v1")
|
||||
sx, sy, _ = (value / 1.0e6 for value in monolith["position_um"])
|
||||
floor = monolith["floor_um"] / 1.0e6
|
||||
eye_camera = (sx - tx * 36.0, sy - ty * 36.0, floor + 1.6)
|
||||
eye_target = (sx, sy, floor + 10.5)
|
||||
wide_camera = (sx - tx * 42.0 - nx * 18.0,
|
||||
sy - ty * 42.0 - ny * 18.0, floor + 42.0)
|
||||
wide_target = (sx, sy, floor + 8.0)
|
||||
return {
|
||||
"eye": (eye_camera, eye_target, 20),
|
||||
"wide": (wide_camera, wide_target, 24),
|
||||
"height_center": floor,
|
||||
"height_range": 38.0,
|
||||
}
|
||||
if name == "periodicHills":
|
||||
# Route points are capsule centers: +0.72 m places the view at 1.60 m above ground.
|
||||
eye_camera = (center[0], center[1], center[2] + 0.72)
|
||||
eye_target = (center[0] - tx * 360.0, center[1] - ty * 360.0,
|
||||
center[2] + 0.72)
|
||||
wide_camera = (center[0] - tx * 36.0 - nx * 28.0,
|
||||
center[1] - ty * 36.0 - ny * 28.0, center[2] + 30.0)
|
||||
wide_target = (center[0] - tx * 310.0, center[1] - ty * 310.0,
|
||||
center[2] + 0.72)
|
||||
return {
|
||||
"eye": (eye_camera, eye_target, 20),
|
||||
"wide": (wide_camera, wide_target, 28),
|
||||
"height_center": base_height,
|
||||
"height_range": 6.0,
|
||||
}
|
||||
if name == "road-in-clouds":
|
||||
deck = sorted((row for row in layout.get("stamps", [])
|
||||
if row.get("stable_id", "").startswith("liminal/road-clouds-v2/deck/")),
|
||||
key=lambda row: row["stable_id"])
|
||||
if len(deck) < 2:
|
||||
raise RuntimeError("road layout has no resolved swept platform chain")
|
||||
points = [tuple(value / 1.0e6 for value in row["position_um"])
|
||||
for row in deck]
|
||||
tops = [point[2] + row["half_extents_um"][2] / 1.0e6
|
||||
for point, row in zip(points, deck)]
|
||||
index = max(1, len(points) // 12)
|
||||
camera_dx = points[index + 1][0] - points[index - 1][0]
|
||||
camera_dy = points[index + 1][1] - points[index - 1][1]
|
||||
camera_tangent_length = math.hypot(camera_dx, camera_dy)
|
||||
camera_tx, camera_ty = (camera_dx / camera_tangent_length,
|
||||
camera_dy / camera_tangent_length)
|
||||
camera_nx, camera_ny = -camera_ty, camera_tx
|
||||
eye_camera = (points[index][0] + camera_nx * 3.2,
|
||||
points[index][1] + camera_ny * 3.2, tops[index] + 1.6)
|
||||
eye_target = (points[index][0] + camera_tx * 90.0 + camera_nx * 60.0,
|
||||
points[index][1] + camera_ty * 90.0 + camera_ny * 60.0,
|
||||
tops[index] - 44.0)
|
||||
middle = len(points) // 2
|
||||
dx = points[middle + 1][0] - points[middle - 1][0]
|
||||
dy = points[middle + 1][1] - points[middle - 1][1]
|
||||
tangent_length = math.hypot(dx, dy)
|
||||
tx, ty = dx / tangent_length, dy / tangent_length
|
||||
nx, ny = -ty, tx
|
||||
wide_target_index = min(len(points) - 1, middle + 30)
|
||||
wide_camera = (points[middle][0] - tx * 35.0 + nx * 30.0,
|
||||
points[middle][1] - ty * 35.0 + ny * 30.0,
|
||||
tops[middle] + 25.0)
|
||||
wide_target = (points[wide_target_index][0], points[wide_target_index][1],
|
||||
tops[wide_target_index] - 5.0)
|
||||
return {
|
||||
"eye": (eye_camera, eye_target, 3),
|
||||
"wide": (wide_camera, wide_target, 3),
|
||||
"height_center": base_height + 23.0,
|
||||
"height_range": 60.0,
|
||||
"road_deck": tops[middle],
|
||||
"road_width": 2.0 * deck[middle]["half_extents_um"][1] / 1.0e6,
|
||||
}
|
||||
if name == "pillar-grid":
|
||||
pillars = [row for row in layout.get("stamps", [])
|
||||
if row.get("stable_id", "").startswith("liminal/horizon-pillar-grid-v1/pillar/")]
|
||||
if len(pillars) < 9:
|
||||
raise RuntimeError("pillar-grid layout has too few resolved supports")
|
||||
xs = [row["position_um"][0] / 1.0e6 for row in pillars]
|
||||
ys = [row["position_um"][1] / 1.0e6 for row in pillars]
|
||||
floor = min((row["position_um"][2] - row["half_extents_um"][2]) / 1.0e6
|
||||
for row in pillars)
|
||||
cx, cy = (min(xs) + max(xs)) * 0.5, (min(ys) + max(ys)) * 0.5
|
||||
eye_camera = (min(xs) - 78.0, cy, floor + 1.6)
|
||||
eye_target = (max(xs) + 90.0, cy, floor + 1.6)
|
||||
wide_camera = (cx - 280.0, cy - 420.0, floor + 230.0)
|
||||
wide_target = (cx, cy, floor + 6.0)
|
||||
return {
|
||||
"eye": (eye_camera, eye_target, 3),
|
||||
"wide": (wide_camera, wide_target, 3),
|
||||
"height_center": floor + 12.0,
|
||||
"height_range": 42.0,
|
||||
}
|
||||
raise RuntimeError(f"no capture framing for fixture {name}")
|
||||
|
||||
|
||||
def headless_command(binary: Path, fixture: Path, camera, mesh: Path,
|
||||
manifest: Path, layout: Path, workers: int,
|
||||
clip_radius: int, *, hash_tiles: bool = False,
|
||||
audit_road: bool = False, max_level: int = 0,
|
||||
far_lods: bool = False,
|
||||
max_clip_override: int | None = None) -> list[object]:
|
||||
max_clip_level = (max_clip_override if max_clip_override is not None else
|
||||
8 if far_lods else max_level)
|
||||
command: list[object] = [binary, fixture, f"--workers={workers}",
|
||||
"--seed=0", "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||
"--force-generate", f"--clip-radius={clip_radius}",
|
||||
f"--max-clip-level={max_clip_level}",
|
||||
f"--max-level={max_level}", "--render-distance-chunks=0",
|
||||
f"--camera={fmt(point_toward(camera, (camera[0], camera[1] + 1.0, camera[2])))}",
|
||||
f"--mesh-export={mesh}", f"--world-assembler-manifest-output={manifest}",
|
||||
f"--resolved-layout-output={layout}"]
|
||||
if hash_tiles:
|
||||
command.append("--stream-hash-tiles")
|
||||
if audit_road:
|
||||
command.append("--audit-liminal-road-walk")
|
||||
return command
|
||||
|
||||
|
||||
def determinism_digest(output: str, manifest_path: Path,
|
||||
layout_path: Path) -> tuple[str, str, str]:
|
||||
tile_hashes = sorted(line for line in output.splitlines()
|
||||
if line.startswith("[PipelineTileHash] "))
|
||||
if not manifest_path.is_file() or not layout_path.is_file() or not tile_hashes:
|
||||
raise RuntimeError("determinism run omitted manifest, layout or per-tile hashes")
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
layout = json.loads(layout_path.read_text(encoding="utf-8"))
|
||||
manifest_digest = (manifest["content_hash"],manifest["recipe"]["content_hash"],
|
||||
manifest["plan"]["content_hash"])
|
||||
return ("|".join(manifest_digest),layout["stable_identity"],"\n".join(tile_hashes))
|
||||
|
||||
|
||||
def run_determinism(name: str, fixture: Path, view, *, binaries: dict[str, Path],
|
||||
out: Path, env: dict[str, str]) -> None:
|
||||
camera, _, _ = view["eye"]
|
||||
clip_radius = 1
|
||||
max_clip = 0
|
||||
baseline = None
|
||||
cells = 0
|
||||
for compiler, workers in ((compiler, workers)
|
||||
for compiler in ("clang", "gcc")
|
||||
for workers in (1, 8, 12)):
|
||||
tag = f"{name}-{compiler}-w{workers}"
|
||||
run_dir = out / "determinism" / tag
|
||||
run_dir.mkdir(parents=True, exist_ok=True)
|
||||
mesh = run_dir / "feature.vfmesh"
|
||||
manifest = run_dir / "manifest.json"
|
||||
layout = run_dir / "layout.json"
|
||||
command = headless_command(binaries[compiler], fixture,camera,mesh,manifest,
|
||||
layout,workers,max_clip,hash_tiles=True)
|
||||
output = run_step(tag,command,out/"logs"/f"determinism-{tag}.log",
|
||||
env=env,stamped=True)
|
||||
digest = determinism_digest(output,manifest,layout)
|
||||
if baseline is None:
|
||||
baseline = digest
|
||||
elif digest != baseline:
|
||||
raise RuntimeError(f"{name} diverged at {compiler} W{workers}; see determinism logs")
|
||||
cells += 1
|
||||
print(f"[L142Determinism] fixture={name} compiler_worker_cells={cells} identical=yes status=PASS",
|
||||
flush=True)
|
||||
|
||||
|
||||
def export_and_capture(name: str, fixture: Path, views: dict, *,
|
||||
headless: Path, renderer: Path, out: Path,
|
||||
artifact_dir: Path, env: dict[str, str]) -> None:
|
||||
for view_name in ("eye", "wide"):
|
||||
camera, target, clip_radius = views[view_name]
|
||||
clip_radius = min(clip_radius,3)
|
||||
stem = f"{name}-{view_name}"
|
||||
mesh = out / "capture-meshes" / f"{stem}.vfmesh"
|
||||
manifest = out / "capture-manifests" / f"{stem}.json"
|
||||
layout = out / "capture-layouts" / f"{stem}.json"
|
||||
png = out / "capture-png" / f"{stem}.png"
|
||||
image = artifact_dir / f"{stem}.jpg"
|
||||
for path in (mesh,manifest,layout,png,image):
|
||||
path.parent.mkdir(parents=True,exist_ok=True)
|
||||
command = headless_command(headless,fixture,camera,mesh,manifest,layout,12,
|
||||
clip_radius,max_level=-1,far_lods=True,
|
||||
max_clip_override=(2 if name == "road-in-clouds" and view_name == "wide"
|
||||
else 3 if name == "road-in-clouds" else None))
|
||||
output = run_step(f"capture-{stem}-export",command,
|
||||
out/"logs"/f"capture-{stem}-export.log",env=env,stamped=True)
|
||||
if not mesh.is_file() or mesh.stat().st_size == 0:
|
||||
raise RuntimeError(f"{stem} produced an empty mesh")
|
||||
yaw, pitch = camera_angles(camera,target)
|
||||
height_center = views["height_center"]
|
||||
height_range = views["height_range"]
|
||||
if name == "road-in-clouds":
|
||||
fog_start,fog_end,fog_color=(24,260,"0.88,0.93,0.95")
|
||||
elif name == "pillar-grid":
|
||||
fog_start,fog_end,fog_color=220,1250,"0.82,0.88,0.91"
|
||||
elif name == "periodicHills":
|
||||
fog_start,fog_end,fog_color=210,1000,"0.78,0.84,0.88"
|
||||
else:
|
||||
fog_start,fog_end,fog_color=30,105,FOG_COLOR
|
||||
renderer_command = [renderer,mesh,png,"--size=1600x900","--fov-deg=72",
|
||||
f"--camera-m={fmt(camera)}",f"--yaw-deg={yaw:.6f}",
|
||||
f"--pitch-deg={pitch:.6f}","--tile-skirts=on","--shade=readable",
|
||||
"--headlamp=off","--height-tint=on",f"--height-center-m={height_center:.6f}",
|
||||
f"--height-range-m={height_range:.6f}","--light-direction=-0.42,-0.58,0.70",
|
||||
"--fog=on",f"--fog-start-m={fog_start}",f"--fog-end-m={fog_end}",
|
||||
f"--fog-color={fog_color}"]
|
||||
run_step(f"capture-{stem}-render",renderer_command,
|
||||
out/"logs"/f"capture-{stem}-render.log",env=env,stamped=True)
|
||||
run_step(f"capture-{stem}-jpeg",["ffmpeg","-nostdin","-hide_banner","-loglevel",
|
||||
"error","-y","-i",png,"-frames:v","1","-q:v","4",image],
|
||||
out/"logs"/f"capture-{stem}-jpeg.log",env=env)
|
||||
quality = run_step(f"capture-{stem}-quality",
|
||||
[sys.executable,ROOT/"Standalone/Tools/verify/capture_quality.py",image],
|
||||
out/"logs"/f"capture-{stem}-quality.log",env=env)
|
||||
if "[CaptureQuality]" not in quality or not image.is_file() \
|
||||
or image.stat().st_size > 300_000:
|
||||
raise RuntimeError(f"{stem} capture failed quality or size checks")
|
||||
eye_height = "1.600m" if view_name == "eye" else "wide"
|
||||
print(f"[L142Capture] fixture={name} view={view_name} eye_height={eye_height} "
|
||||
f"camera_m=({fmt(camera)}) target_m=({fmt(target)}) bytes={image.stat().st_size} "
|
||||
f"sha256={sha256(image)} status=PASS",flush=True)
|
||||
|
||||
|
||||
def run_road_walk_audit(fixture: Path, view, *, headless: Path,
|
||||
out: Path, env: dict[str, str]) -> None:
|
||||
camera = view["eye"][0]
|
||||
run_dir = out / "road-walk-audit"
|
||||
run_dir.mkdir(parents=True,exist_ok=True)
|
||||
output = run_step("road-walk-audit",headless_command(headless,fixture,camera,
|
||||
run_dir/"audit.vfmesh",run_dir/"manifest.json",run_dir/"layout.json",12,1,
|
||||
audit_road=True),out/"logs"/"road-walk-audit.log",env=env,stamped=True)
|
||||
if not re.search(r"\[LiminalRoadWalkAudit\].*status=PASS",output):
|
||||
raise RuntimeError("raised road failed the Core capsule walk audit")
|
||||
|
||||
|
||||
def run_road_short_negative_control(fixture: Path, view, *, headless: Path,
|
||||
out: Path, env: dict[str, str]) -> None:
|
||||
camera = view["eye"][0]
|
||||
run_dir = out / "road-short-negative-control"
|
||||
run_dir.mkdir(parents=True,exist_ok=True)
|
||||
output = run_step("road-short-negative-control",
|
||||
headless_command(headless,fixture,camera,run_dir/"control.vfmesh",
|
||||
run_dir/"manifest.json",run_dir/"layout.json",12,1,audit_road=True),
|
||||
out/"logs"/"road-short-negative-control.log",env=env,stamped=True,
|
||||
expected_exit=14)
|
||||
match = re.search(r"\[LiminalRoadWalkAudit\].*path_length_m=([0-9.]+).*status=FAIL",output)
|
||||
if not match or float(match.group(1)) >= 300.0:
|
||||
raise RuntimeError("short-road negative control did not falsify the length gate")
|
||||
print(f"[L142RoadNegativeControl] path_length_m={match.group(1)} "
|
||||
"expected=FAIL status=PASS",flush=True)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--out", type=Path, default=OUT_DEFAULT)
|
||||
parser.add_argument("--artifact-dir", type=Path, default=CAPTURE_DEFAULT)
|
||||
parser.add_argument("--headless-clang", type=Path, required=True)
|
||||
parser.add_argument("--headless-gcc", type=Path, required=True)
|
||||
parser.add_argument("--renderer", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
out = args.out.resolve()
|
||||
artifact_dir = args.artifact_dir.resolve()
|
||||
fixture_dir = out / "fixtures"
|
||||
for directory in (out,fixture_dir,artifact_dir):
|
||||
directory.mkdir(parents=True,exist_ok=True)
|
||||
env = os.environ.copy()
|
||||
env["VOXELFORGE_VERIFY_OUT"] = str(out)
|
||||
env["VOXELFORGE_OWNER_DATA"] = str(ROOT/"Standalone/data/owner_world.json")
|
||||
env["LP_NUM_THREADS"] = "12"
|
||||
env["XDG_CACHE_HOME"] = str(out/"xdg-cache")
|
||||
run_step("build-fixtures",[sys.executable,
|
||||
ROOT/"Standalone/Research/WorldAssemblerM3/Linux142/BuildL142Fixtures.py",
|
||||
f"--out={fixture_dir}"],out/"logs"/"build-fixtures.log",env=env)
|
||||
|
||||
binaries = {"clang":args.headless_clang.resolve(),"gcc":args.headless_gcc.resolve()}
|
||||
fixtures = {
|
||||
"flatField-monolith":fixture_dir/"liminal-flatField-monolith.json",
|
||||
"periodicHills":fixture_dir/"liminal-periodicHills.json",
|
||||
"road-in-clouds":fixture_dir/"liminal-road-in-clouds.json",
|
||||
"pillar-grid":fixture_dir/"liminal-pillar-grid.json",
|
||||
}
|
||||
probed = {}
|
||||
for name,fixture in fixtures.items():
|
||||
probe_dir=out/"plan-probes"/name
|
||||
probe_dir.mkdir(parents=True,exist_ok=True)
|
||||
camera=(0.0,0.0,-500.0)
|
||||
output=run_step(f"plan-probe-{name}",headless_command(binaries["clang"],fixture,
|
||||
camera,probe_dir/"probe.vfmesh",probe_dir/"manifest.json",
|
||||
probe_dir/"layout.json",12,1),out/"logs"/f"plan-probe-{name}.log",
|
||||
env=env,stamped=True)
|
||||
if "[WorldAssemblerFullManifest]" not in output:
|
||||
raise RuntimeError(f"{name} probe omitted its full plan manifest")
|
||||
probed[name]=make_views(name,probe_dir/"manifest.json",probe_dir/"layout.json")
|
||||
|
||||
for name,fixture in fixtures.items():
|
||||
run_determinism(name,fixture,probed[name],binaries=binaries,out=out,env=env)
|
||||
run_road_walk_audit(fixtures["road-in-clouds"],probed["road-in-clouds"],
|
||||
headless=binaries["clang"],out=out,env=env)
|
||||
run_road_short_negative_control(fixture_dir/"liminal-road-short-control.json",
|
||||
probed["road-in-clouds"],headless=binaries["clang"],out=out,env=env)
|
||||
for name,fixture in fixtures.items():
|
||||
export_and_capture(name,fixture,probed[name],headless=binaries["clang"],
|
||||
renderer=args.renderer.resolve(),out=out,artifact_dir=artifact_dir,env=env)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as error:
|
||||
print(f"[L142Capture] status=FAIL error={error}",file=sys.stderr,flush=True)
|
||||
raise SystemExit(1)
|
||||
@@ -0,0 +1,46 @@
|
||||
# L142 — liminal pass 2
|
||||
|
||||
| Hills: eye height | Hills: wide |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
| Road: eye height | Road: wide |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
| Pillar grid: eye height | Pillar grid: wide |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
| Monolith: eye height | Monolith: wide |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
## Result
|
||||
|
||||
The wide hill capture shows the validated 80 m repeating profile continuing across the horizon. The capture now streams LOD 0–8; the profile period and validator are unchanged.
|
||||
|
||||
The road is a cubic swept path made from overlapping solid deck segments, not a chain of platform stamps. The earlier platform chain produced top steps; the swept deck passes the surface-sampled walk audit. Its wide view is capped at LOD 2 to keep the deck legible, so that frame shows a local section; the full route length is measured by the audit.
|
||||
|
||||
The R47 addition is a 15×15 grid of identical pillars, spaced 76 m apart across a 1,064 m span. The monolith eye and wide views are retained as the first liminal slice.
|
||||
|
||||
## Measurements
|
||||
|
||||
- **Road:** 376.827 m horizontal path length, 8.000 m width, and 12.061° maximum sampled surface grade against the 15° limit. The deck rises and falls along the cubic profile. It has 17 supports at about 24 m spacing; the flat cloud floor is 44 m below the deck centerline (about 45 m below the walking surface), with pale fog in both views.
|
||||
- **Walk audit:** 901 surface stations at no more than 0.5 m spacing; 56,763 capsule samples; 0 blocked samples, 0 unsupported samples across 5,871 support probes, 0 grade failures, and 0 missed surface probes. The 180 m short-road control measured 210.341 m and failed the 300 m gate as expected.
|
||||
- **Capture quality:** all eight fixtures/views passed the image-quality gate at 1600×900. Hills and pillars stream through LOD 8; road eye and wide views use caps 3 and 2 respectively.
|
||||
- **Determinism:** the monolith, hills, road, and pillar fixtures each matched across Clang and GCC at W1, W8, and W12 (six compiler/worker cells per fixture), on source stamp `72f239e9534faa27e3796fe29207403b190558403841aeebc87d8e75e98e34c7`.
|
||||
|
||||
## Checks
|
||||
|
||||
- Clang 19 and GCC builds passed with floating-point contraction off, four build jobs, and the L142 source stamp.
|
||||
- `Standalone/Tools/verify/verify.py fast --out Saved/Linux/l142 --overall-timeout 1740` passed. Both current owner fixtures produced all 12 canonical hash rows matching the pinned L130-v227 baseline; W1 and W12 also matched each other.
|
||||
- The fast summary retains non-gating differences against older L46/L53 diagnostic baselines; the current pinned L130 canonical checks pass.
|
||||
- The L142 capture runner passed the walk audit, the short-road negative control, all four determinism comparisons, and all eight image-quality checks. Final logs are in `Saved/Linux/l142/logs/`.
|
||||
- No Unreal build or performance timing matrix was run. No Unreal `Source/` files changed. The inherited single-run near-ready timing step was removed from this picture-and-geometry runner rather than treated as a timing result.
|
||||
|
||||
## Remaining limits
|
||||
|
||||
The road wide capture frames a local section at LOD 2 to retain a continuous deck; the 376.827 m length is established by the full-route walk audit. Captures use the standalone Linux renderer rather than an Unreal viewport.
|
||||
|
||||
Changed Unreal `Source/` files: none.
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 39 KiB |
@@ -1882,8 +1882,11 @@ int main(int argc,char** argv)
|
||||
ownerJsonRoot.Get("motif_family_override").Data);
|
||||
const bool hasLiminalRoad=!std::holds_alternative<std::nullptr_t>(
|
||||
ownerJsonRoot.Get("liminal_road_override").Data);
|
||||
if(hasMotifFamily&&hasLiminalRoad)
|
||||
{std::fprintf(stderr,"motif_family_override and liminal_road_override cannot be combined\n");return 2;}
|
||||
const bool hasLiminalPillarGrid=!std::holds_alternative<std::nullptr_t>(
|
||||
ownerJsonRoot.Get("liminal_pillar_grid_override").Data);
|
||||
if((hasMotifFamily&&hasLiminalRoad)||(hasMotifFamily&&hasLiminalPillarGrid)
|
||||
||(hasLiminalRoad&&hasLiminalPillarGrid))
|
||||
{std::fprintf(stderr,"liminal content overrides must be used in separate fixtures\n");return 2;}
|
||||
if(hasMotifFamily)
|
||||
{
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1> requests;
|
||||
@@ -1914,22 +1917,62 @@ int main(int argc,char** argv)
|
||||
}
|
||||
if(hasLiminalRoad)
|
||||
{
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1> requests(1);
|
||||
if(!StandaloneWorldAssemblyPrototype::BuildLiminalRoadRequest(ownerJsonRoot,
|
||||
*ownerWorld,worldAssembly,resolvedLayout,requests.front(),error)
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1> requests;
|
||||
if(!StandaloneWorldAssemblyPrototype::BuildLiminalRoadRequests(ownerJsonRoot,
|
||||
*ownerWorld,worldAssembly,resolvedLayout,requests,error)
|
||||
||!VoxelForgeCore::ResolveResolvedLayoutStampsV1(
|
||||
resolvedLayout,seed,requests,error))
|
||||
{std::fprintf(stderr,"Liminal road resolution failed: %s\n",error.c_str());return 2;}
|
||||
const auto& road=resolvedLayout.stamps.front();
|
||||
std::printf("[LiminalRoadResolved] vocabulary=%u id=%s strate=%d shape=rounded_dais "
|
||||
"center_m=(%.3f,%.3f,%.3f) top_m=%.3f span_m=%.3f width_m=%.3f "
|
||||
"grade_deg=0.000 placement=free route_keepout=PASS band_clearance=PASS status=PASS\n",
|
||||
resolvedLayout.stampVocabularyVersion,road.stableId.c_str(),road.strateIndex,
|
||||
road.positionMicrometres[0]/1.0e6,road.positionMicrometres[1]/1.0e6,
|
||||
road.positionMicrometres[2]/1.0e6,
|
||||
(road.positionMicrometres[2]+road.halfExtentsMicrometres[2])/1.0e6,
|
||||
2.0*road.halfExtentsMicrometres[0]/1.0e6,
|
||||
2.0*road.halfExtentsMicrometres[1]/1.0e6);
|
||||
std::vector<const VoxelForgeCore::ResolvedLayoutStampV1*> deck;
|
||||
std::size_t supportCount=0;
|
||||
const std::string deckPrefix="liminal/road-clouds-v2/deck/";
|
||||
const std::string supportPrefix="liminal/road-clouds-v2/support/";
|
||||
for(const auto& stamp:resolvedLayout.stamps)
|
||||
{
|
||||
if(stamp.stableId.starts_with(deckPrefix))deck.push_back(&stamp);
|
||||
else if(stamp.stableId.starts_with(supportPrefix))++supportCount;
|
||||
}
|
||||
std::sort(deck.begin(),deck.end(),[](const auto* a,const auto* b)
|
||||
{return a->stableId<b->stableId;});
|
||||
double pathLength=0.0,maxGradeDegrees=0.0;
|
||||
for(std::size_t i=0;i<deck.size();++i)
|
||||
{
|
||||
const auto* segment=deck[i];
|
||||
const double dx=(segment->sweepEndpointsMicrometres[1][0]
|
||||
-segment->sweepEndpointsMicrometres[0][0])/1.0e6;
|
||||
const double dy=(segment->sweepEndpointsMicrometres[1][1]
|
||||
-segment->sweepEndpointsMicrometres[0][1])/1.0e6;
|
||||
const double dz=std::abs(segment->sweepEndpointsMicrometres[1][2]
|
||||
-segment->sweepEndpointsMicrometres[0][2])/1.0e6;
|
||||
const double horizontal=std::hypot(dx,dy);
|
||||
pathLength+=horizontal;
|
||||
maxGradeDegrees=std::max(maxGradeDegrees,
|
||||
std::atan2(dz,horizontal)*57.29577951308232);
|
||||
}
|
||||
const double width=deck.empty()?0.0:
|
||||
2.0*deck.front()->halfExtentsMicrometres[1]/1.0e6;
|
||||
std::printf("[LiminalRoadResolved] vocabulary=%u id=liminal/road-clouds-v2 "
|
||||
"strate=%d shape=swept_deck_segments deck_segments=%zu supports=%zu "
|
||||
"path_length_m=%.3f width_m=%.3f grade_max_deg=%.3f "
|
||||
"placement=free route_keepout=PASS band_clearance=PASS status=%s\n",
|
||||
resolvedLayout.stampVocabularyVersion,
|
||||
deck.empty()?-1:deck.front()->strateIndex,deck.size(),supportCount,pathLength,
|
||||
width,maxGradeDegrees,deck.size()>1&&pathLength>=300.0&&width>=3.0
|
||||
&&maxGradeDegrees<=15.0?"PASS":"FAIL");
|
||||
if(deck.size()<2||pathLength<80.0||width<3.0||maxGradeDegrees>15.0)
|
||||
{std::fprintf(stderr,"Liminal road resolved outside the walkable road bounds\n");return 2;}
|
||||
}
|
||||
if(hasLiminalPillarGrid)
|
||||
{
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1> requests;
|
||||
if(!StandaloneWorldAssemblyPrototype::BuildLiminalPillarGridRequests(ownerJsonRoot,
|
||||
*ownerWorld,worldAssembly,resolvedLayout,requests,error)
|
||||
||!VoxelForgeCore::ResolveResolvedLayoutStampsV1(
|
||||
resolvedLayout,seed,requests,error))
|
||||
{std::fprintf(stderr,"Liminal pillar-grid resolution failed: %s\n",error.c_str());return 2;}
|
||||
std::printf("[LiminalPillarGridResolved] vocabulary=%u pillars=%zu shape=identical_vertical_cylinders "
|
||||
"placement=regular_grid route_keepout=PASS band_clearance=PASS status=PASS\n",
|
||||
resolvedLayout.stampVocabularyVersion,resolvedLayout.stamps.size());
|
||||
}
|
||||
if(options.LandmarkChamberDemo)
|
||||
{
|
||||
|
||||
@@ -2966,53 +2966,122 @@ namespace StandaloneStreamBenchmark
|
||||
{
|
||||
bool found=false;
|
||||
bool passed=true;
|
||||
std::vector<const VoxelForgeCore::ResolvedLayoutStampV1*> deck;
|
||||
constexpr std::string_view DeckPrefix="liminal/road-clouds-v2/deck/";
|
||||
for(const auto& road:ResolvedLayout->stamps)
|
||||
{
|
||||
if(road.stableId!="liminal/raised-road-v1")continue;
|
||||
if(road.stableId.starts_with(DeckPrefix))deck.push_back(&road);
|
||||
}
|
||||
if(!deck.empty())
|
||||
{
|
||||
found=true;
|
||||
const bool longAxisX=(road.yawQuarterTurns&1u)==0;
|
||||
const double centerX=road.positionMicrometres[0]/1.0e6;
|
||||
const double centerY=road.positionMicrometres[1]/1.0e6;
|
||||
const double centerZ=road.positionMicrometres[2]/1.0e6;
|
||||
const double halfLength=road.halfExtentsMicrometres[longAxisX?0:1]/1.0e6;
|
||||
const double halfWidth=road.halfExtentsMicrometres[longAxisX?1:0]/1.0e6;
|
||||
const double deckZ=centerZ+road.halfExtentsMicrometres[2]/1.0e6;
|
||||
constexpr double CapsuleRadiusMetres=0.34;
|
||||
std::sort(deck.begin(),deck.end(),[](const auto* a,const auto* b)
|
||||
{return a->stableId<b->stableId;});
|
||||
constexpr double CapsuleHeightMetres=1.76;
|
||||
constexpr double HalfCapsuleHeightMetres=CapsuleHeightMetres*0.5;
|
||||
constexpr double EndInsetMetres=1.0;
|
||||
constexpr double SlopeCapsuleClearanceMetres=0.12;
|
||||
std::vector<std::array<double,3>> roadPath;
|
||||
const double inset=std::min(EndInsetMetres,halfLength*0.25);
|
||||
for(const double along:{-halfLength+inset,halfLength-inset})
|
||||
roadPath.push_back({(centerX+(longAxisX?along:0.0))/coreMetres,
|
||||
(centerY+(longAxisX?0.0:along))/coreMetres,
|
||||
(deckZ+HalfCapsuleHeightMetres)/coreMetres});
|
||||
roadPath.reserve(deck.size()*4);
|
||||
std::vector<std::array<double,3>> centerline;
|
||||
centerline.reserve(deck.size()+1);
|
||||
bool surfacePathValid=true;
|
||||
for(std::size_t i=0;i<deck.size();++i)
|
||||
{
|
||||
const auto* segment=deck[i];
|
||||
if(segment->kind!=VoxelForgeCore::ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
surfacePathValid=false;
|
||||
const auto point=[&](std::size_t endpoint)
|
||||
{return std::array<double,3>{
|
||||
segment->sweepEndpointsMicrometres[endpoint][0]/1.0e6,
|
||||
segment->sweepEndpointsMicrometres[endpoint][1]/1.0e6,
|
||||
segment->sweepEndpointsMicrometres[endpoint][2]/1.0e6};};
|
||||
const auto a=point(0),b=point(1);
|
||||
if(i==0)centerline.push_back(a);
|
||||
else if(std::abs(centerline.back()[0]-a[0])>1.0e-6
|
||||
||std::abs(centerline.back()[1]-a[1])>1.0e-6
|
||||
||std::abs(centerline.back()[2]-a[2])>1.0e-6)
|
||||
surfacePathValid=false;
|
||||
centerline.push_back(b);
|
||||
}
|
||||
double pathLengthMetres=0.0,maxGradeDegrees=0.0;
|
||||
std::uint64_t surfaceProbes=0,surfaceMisses=0;
|
||||
const auto surfaceTopAt=[&](double xMetres,double yMetres,
|
||||
double nominalTop,double& surfaceTop)
|
||||
{
|
||||
++surfaceProbes;
|
||||
double low=nominalTop-0.25,high=nominalTop+1.0;
|
||||
if(Shape.densityAt({xMetres/coreMetres,yMetres/coreMetres,
|
||||
low/coreMetres})>=0.0f
|
||||
||Shape.densityAt({xMetres/coreMetres,yMetres/coreMetres,
|
||||
high/coreMetres})<0.0f)
|
||||
{++surfaceMisses;return false;}
|
||||
for(int iteration=0;iteration<20;++iteration)
|
||||
{
|
||||
const double middle=(low+high)*0.5;
|
||||
if(Shape.densityAt({xMetres/coreMetres,yMetres/coreMetres,
|
||||
middle/coreMetres})<0.0f)low=middle;
|
||||
else high=middle;
|
||||
}
|
||||
surfaceTop=high;
|
||||
return true;
|
||||
};
|
||||
const double halfThickness=deck.front()->halfExtentsMicrometres[2]/1.0e6;
|
||||
for(std::size_t segment=1;segment<centerline.size();++segment)
|
||||
{
|
||||
const auto& a=centerline[segment-1];const auto& b=centerline[segment];
|
||||
const double dx=b[0]-a[0],dy=b[1]-a[1];
|
||||
const double horizontal=std::hypot(dx,dy);
|
||||
pathLengthMetres+=horizontal;
|
||||
const std::uint64_t intervals=std::max<std::uint64_t>(1,
|
||||
static_cast<std::uint64_t>(std::ceil(horizontal/0.5)));
|
||||
for(std::uint64_t step=0;step<=intervals;++step)
|
||||
{
|
||||
if(segment>1&&step==0)continue;
|
||||
const double t=static_cast<double>(step)/intervals;
|
||||
const double x=a[0]+dx*t,y=a[1]+dy*t;
|
||||
const double nominalTop=a[2]+(b[2]-a[2])*t+halfThickness;
|
||||
double actualTop=nominalTop;
|
||||
if(!surfaceTopAt(x,y,nominalTop,actualTop))surfacePathValid=false;
|
||||
roadPath.push_back({x/coreMetres,y/coreMetres,
|
||||
(actualTop+HalfCapsuleHeightMetres
|
||||
+SlopeCapsuleClearanceMetres)/coreMetres});
|
||||
}
|
||||
}
|
||||
for(std::size_t i=1;i<roadPath.size();++i)
|
||||
{
|
||||
const double dx=(roadPath[i][0]-roadPath[i-1][0])*coreMetres;
|
||||
const double dy=(roadPath[i][1]-roadPath[i-1][1])*coreMetres;
|
||||
const double dz=std::abs(roadPath[i][2]-roadPath[i-1][2])*coreMetres;
|
||||
const double horizontal=std::hypot(dx,dy);
|
||||
maxGradeDegrees=std::max(maxGradeDegrees,
|
||||
std::atan2(dz,horizontal)*57.29577951308232);
|
||||
}
|
||||
const FWalkAudit roadAudit=auditPlayerPath(Shape,roadPath,
|
||||
0.125,0.125,1.125,3,0.45);
|
||||
const double widthMetres=2.0*halfWidth;
|
||||
const double horizontal=std::hypot(roadPath[1][0]-roadPath[0][0],
|
||||
roadPath[1][1]-roadPath[0][1])*coreMetres;
|
||||
const double vertical=std::abs(roadPath[1][2]-roadPath[0][2])*coreMetres;
|
||||
const double gradeDegrees=std::atan2(vertical,horizontal)*
|
||||
57.29577951308232;
|
||||
passed=road.kind==VoxelForgeCore::ResolvedSdfStampKindV1::fillDais
|
||||
&&widthMetres>=2.0*CapsuleRadiusMetres
|
||||
&&gradeDegrees<=15.0&&roadAudit.passed;
|
||||
std::printf("[LiminalRoadWalkAudit] id=%s stations=%llu capsule_samples=%llu "
|
||||
const double widthMetres=2.0*deck.front()->halfExtentsMicrometres[1]/1.0e6;
|
||||
passed=std::all_of(deck.begin(),deck.end(),[](const auto* road)
|
||||
{return road->kind==VoxelForgeCore::ResolvedSdfStampKindV1::fillSweptDeckSegment;})
|
||||
&&pathLengthMetres>=300.0&&widthMetres>=3.0
|
||||
&&maxGradeDegrees<=15.0&&surfacePathValid&&roadAudit.passed;
|
||||
std::printf("[LiminalRoadWalkAudit] id=liminal/road-clouds-v2 "
|
||||
"deck_stamps=%zu path_length_m=%.3f stations=%llu capsule_samples=%llu "
|
||||
"blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu "
|
||||
"width_m=%.3f capsule_diameter_m=%.3f grade_deg=%.3f grade_limit_deg=15.000 "
|
||||
"status=%s\n",road.stableId.c_str(),
|
||||
"surface_probes=%llu surface_misses=%llu width_m=%.3f minimum_width_m=3.000 "
|
||||
"max_grade_deg=%.3f grade_limit_deg=15.000 status=%s\n",
|
||||
deck.size(),pathLengthMetres,
|
||||
static_cast<unsigned long long>(roadAudit.centers),
|
||||
static_cast<unsigned long long>(roadAudit.capsuleSamples),
|
||||
static_cast<unsigned long long>(roadAudit.blocked),
|
||||
static_cast<unsigned long long>(roadAudit.supportSamples),
|
||||
static_cast<unsigned long long>(roadAudit.unsupported),
|
||||
static_cast<unsigned long long>(roadAudit.gradeFailures),widthMetres,
|
||||
2.0*CapsuleRadiusMetres,gradeDegrees,passed?"PASS":"FAIL");
|
||||
static_cast<unsigned long long>(roadAudit.gradeFailures),
|
||||
static_cast<unsigned long long>(surfaceProbes),
|
||||
static_cast<unsigned long long>(surfaceMisses),widthMetres,
|
||||
maxGradeDegrees,passed?"PASS":"FAIL");
|
||||
}
|
||||
if(!found&&required)
|
||||
{
|
||||
std::printf("[LiminalRoadWalkAudit] status=FAIL reason=road_stamp_missing\n");
|
||||
std::printf("[LiminalRoadWalkAudit] status=FAIL reason=road_path_missing\n");
|
||||
return false;
|
||||
}
|
||||
return passed;
|
||||
|
||||
@@ -587,13 +587,15 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool BuildLiminalRoadRequest(const VFJson::Value& root,
|
||||
inline bool BuildLiminalRoadRequests(const VFJson::Value& root,
|
||||
const FStandaloneOwnerWorld& owner,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& assembly,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
|
||||
VoxelForgeCore::ResolvedLayoutStampRequestV1& request,std::string& error)
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1>& requests,
|
||||
std::string& error)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
requests.clear();
|
||||
const VFJson::Value& overrideValue=root.Get("liminal_road_override");
|
||||
if(!overrideValue.IsObject())
|
||||
{error="liminal_road_override must be an object";return false;}
|
||||
@@ -604,49 +606,229 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
|
||||
const VFJson::Value& strateValue=overrideValue.Get("strate_index");
|
||||
const auto* id=std::get_if<std::string>(&overrideValue.Get("stable_id").Data);
|
||||
const auto& position=overrideValue.Get("position_xy_m").AsArray();
|
||||
const auto& extents=overrideValue.Get("half_extents_m").AsArray();
|
||||
const VFJson::Value& heightValue=overrideValue.Get("deck_height_above_field_m");
|
||||
const VFJson::Value& yawValue=overrideValue.Get("yaw_quarter_turns");
|
||||
if(!strateValue.IsNumber()||!id||id->empty()||position.size()!=2||extents.size()!=3
|
||||
||!heightValue.IsNumber()||!std::isfinite(heightValue.Number())
|
||||
||!yawValue.IsNumber()||yawValue.Integer()<0||yawValue.Integer()>3)
|
||||
{error="liminal road requires stable_id, strate_index, position, extents, deck height and yaw";return false;}
|
||||
const auto& position=overrideValue.Get("start_xy_m").AsArray();
|
||||
const auto readNumber=[&](const char* key,double fallback,double& result)
|
||||
{
|
||||
const VFJson::Value& value=overrideValue.Get(key);
|
||||
if(std::holds_alternative<std::nullptr_t>(value.Data))
|
||||
{result=fallback;return true;}
|
||||
if(!value.IsNumber()||!std::isfinite(value.Number()))return false;
|
||||
result=value.Number();return true;
|
||||
};
|
||||
double pathLength=0.0,curveControl=0.0,deckHeight=0.0,riseControl=0.0;
|
||||
double halfWidth=0.0,halfThickness=0.0,sampleSpacing=0.0;
|
||||
double supportSpacing=0.0,supportRadius=0.0;
|
||||
if(!strateValue.IsNumber()||!id||id->empty()||position.size()!=2
|
||||
||!readNumber("path_length_m",360.0,pathLength)
|
||||
||!readNumber("curve_control_offset_m",84.0,curveControl)
|
||||
||!readNumber("deck_height_above_field_m",44.0,deckHeight)
|
||||
||!readNumber("rise_control_m",22.0,riseControl)
|
||||
||!readNumber("half_width_m",3.2,halfWidth)
|
||||
||!readNumber("half_thickness_m",0.65,halfThickness)
|
||||
||!readNumber("sample_spacing_m",1.0,sampleSpacing)
|
||||
||!readNumber("support_spacing_m",24.0,supportSpacing)
|
||||
||!readNumber("support_radius_m",0.55,supportRadius))
|
||||
{error="liminal road requires a valid identity, start, and finite path parameters";return false;}
|
||||
for(const auto& value:position)
|
||||
if(!value.IsNumber()||!std::isfinite(value.Number()))
|
||||
{error="liminal road position_xy_m must contain finite metre values";return false;}
|
||||
for(const auto& value:extents)
|
||||
if(!value.IsNumber()||!std::isfinite(value.Number())
|
||||
||value.Number()<0.5||value.Number()>100.0)
|
||||
{error="liminal road half_extents_m must contain values from 0.5 to 100 metres";return false;}
|
||||
if(heightValue.Number()<1.0||heightValue.Number()>100.0)
|
||||
{error="liminal road deck height must be from 1 to 100 metres above the field";return false;}
|
||||
{error="liminal road start_xy_m must contain finite metre values";return false;}
|
||||
if(std::abs(position[0].Number())>5000.0||std::abs(position[1].Number())>5000.0
|
||||
||pathLength<80.0||pathLength>1000.0||curveControl<0.0||curveControl>200.0
|
||||
||deckHeight<30.0||deckHeight>60.0||riseControl<0.0||riseControl>30.0
|
||||
||halfWidth<1.5||halfWidth>10.0||halfThickness<0.5||halfThickness>3.0
|
||||
||sampleSpacing<1.0||sampleSpacing>6.0
|
||||
||supportSpacing<8.0||supportSpacing>60.0
|
||||
||supportRadius<0.5||supportRadius>1.5)
|
||||
{error="liminal road dimensions are outside the bounded walkable-road ranges";return false;}
|
||||
const auto strateIndex=static_cast<std::int32_t>(strateValue.Integer());
|
||||
const auto strate=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[strateIndex](const auto& value){return value.strateIndex==strateIndex
|
||||
&&value.generatorKind=="SurfaceWorld";});
|
||||
if(strate==layout.strates.end())
|
||||
{error="liminal_road_override must select a resolved SurfaceWorld strate";return false;}
|
||||
if(extents[0].Number()<10.0||extents[1].Number()<0.75
|
||||
||extents[0].Number()<=extents[1].Number()*4.0)
|
||||
{error="liminal road must be long and narrow, with at least 1.5 metres half-width";return false;}
|
||||
|
||||
const auto metresToMicrometres=[](double value)
|
||||
{return static_cast<std::int64_t>(std::llround(value*1000000.0));};
|
||||
const std::int64_t fieldFloorMicrometres=static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(assembly.recipe.surfaceProfile.h0Subvoxels)
|
||||
*owner.CoreVoxelSizeMicrons/WorldAssemblySubvoxelsPerVoxel));
|
||||
request={};
|
||||
request.stableId=*id;
|
||||
request.strateIndex=strateIndex;
|
||||
request.kind=ResolvedSdfStampKindV1::fillDais;
|
||||
request.anchorPositionMicrometres={metresToMicrometres(position[0].Number()),
|
||||
metresToMicrometres(position[1].Number()),
|
||||
fieldFloorMicrometres+metresToMicrometres(heightValue.Number())
|
||||
-metresToMicrometres(extents[2].Number())};
|
||||
request.halfExtentsMicrometres={metresToMicrometres(extents[0].Number()),
|
||||
metresToMicrometres(extents[1].Number()),metresToMicrometres(extents[2].Number())};
|
||||
request.yawQuarterTurns=static_cast<std::uint8_t>(yawValue.Integer());
|
||||
const std::int64_t startX=metresToMicrometres(position[0].Number());
|
||||
const std::int64_t startY=metresToMicrometres(position[1].Number());
|
||||
const std::int64_t lengthUm=metresToMicrometres(pathLength);
|
||||
const std::int64_t lateralUm=metresToMicrometres(curveControl);
|
||||
const std::int64_t deckBaseUm=fieldFloorMicrometres+metresToMicrometres(deckHeight);
|
||||
const std::int64_t riseUm=metresToMicrometres(riseControl);
|
||||
const std::int64_t halfWidthUm=metresToMicrometres(halfWidth);
|
||||
const std::int64_t halfThicknessUm=metresToMicrometres(halfThickness);
|
||||
const std::uint32_t steps=static_cast<std::uint32_t>(std::ceil(pathLength/sampleSpacing));
|
||||
const auto lerpInteger=[steps](std::int64_t a,std::int64_t b,std::uint32_t i)
|
||||
{
|
||||
const std::int64_t numerator=(b-a)*static_cast<std::int64_t>(i);
|
||||
const std::int64_t rounded=numerator>=0
|
||||
?numerator+static_cast<std::int64_t>(steps/2)
|
||||
:numerator-static_cast<std::int64_t>(steps/2);
|
||||
return a+rounded/static_cast<std::int64_t>(steps);
|
||||
};
|
||||
const auto bezier=[&](const std::array<std::int64_t,4>& controls,std::uint32_t i)
|
||||
{
|
||||
const std::int64_t a=lerpInteger(controls[0],controls[1],i);
|
||||
const std::int64_t b=lerpInteger(controls[1],controls[2],i);
|
||||
const std::int64_t c=lerpInteger(controls[2],controls[3],i);
|
||||
const std::int64_t d=lerpInteger(a,b,i);
|
||||
const std::int64_t e=lerpInteger(b,c,i);
|
||||
return lerpInteger(d,e,i);
|
||||
};
|
||||
const std::array<std::int64_t,4> xControls={startX,
|
||||
startX+lengthUm/3,startX+(2*lengthUm)/3,startX+lengthUm};
|
||||
const std::array<std::int64_t,4> yControls={startY,startY+lateralUm,
|
||||
startY-lateralUm,startY};
|
||||
const std::array<std::int64_t,4> zControls={deckBaseUm,deckBaseUm+riseUm,
|
||||
deckBaseUm-riseUm,deckBaseUm};
|
||||
std::vector<std::array<std::int64_t,3>> path;
|
||||
path.reserve(static_cast<std::size_t>(steps)+1);
|
||||
const auto halfUp=[](std::int64_t numerator,std::int64_t denominator)
|
||||
{return (numerator>=0?numerator+denominator/2:numerator-denominator/2)/denominator;};
|
||||
for(std::uint32_t i=0;i<=steps;++i)
|
||||
{
|
||||
const auto t=static_cast<std::int64_t>(i);
|
||||
const auto n=static_cast<std::int64_t>(steps);
|
||||
path.push_back({xControls[0]+halfUp(lengthUm*t,n),
|
||||
bezier(yControls,i),bezier(zControls,i)});
|
||||
}
|
||||
const auto makeStamp=[&](std::string stableId,ResolvedSdfStampKindV1 kind,
|
||||
const std::array<std::int64_t,3>& positionUm,
|
||||
const std::array<std::int64_t,3>& halfExtentsUm,std::int64_t smoothWidthUm)
|
||||
{
|
||||
ResolvedLayoutStampRequestV1 request;
|
||||
request.stableId=std::move(stableId);request.strateIndex=strateIndex;
|
||||
request.kind=kind;request.anchorPositionMicrometres=positionUm;
|
||||
request.halfExtentsMicrometres=halfExtentsUm;
|
||||
request.smoothWidthMicrometres=smoothWidthUm;
|
||||
requests.push_back(std::move(request));
|
||||
};
|
||||
const std::int64_t deckSmoothUm=250000;
|
||||
const std::int64_t segmentOverlapUm=350000;
|
||||
for(std::uint32_t i=1;i<path.size();++i)
|
||||
{
|
||||
const auto& a=path[i-1];const auto& b=path[i];
|
||||
const double dx=static_cast<double>(b[0]-a[0]);
|
||||
const double dy=static_cast<double>(b[1]-a[1]);
|
||||
const double dz=static_cast<double>(b[2]-a[2]);
|
||||
const std::int64_t halfLengthUm=static_cast<std::int64_t>(std::ceil(
|
||||
std::sqrt(dx*dx+dy*dy+dz*dz)*0.5))+segmentOverlapUm;
|
||||
ResolvedLayoutStampRequestV1 request;
|
||||
request.stableId=*id+"/deck/"+std::to_string(10000+i-1).substr(1);
|
||||
request.strateIndex=strateIndex;
|
||||
request.kind=ResolvedSdfStampKindV1::fillSweptDeckSegment;
|
||||
request.anchorPositionMicrometres={
|
||||
(a[0]+b[0])/2,(a[1]+b[1])/2,(a[2]+b[2])/2};
|
||||
request.sweepEndpointsMicrometres={a,b};
|
||||
request.halfExtentsMicrometres={halfLengthUm,halfWidthUm,halfThicknessUm};
|
||||
request.smoothWidthMicrometres=deckSmoothUm;
|
||||
requests.push_back(std::move(request));
|
||||
}
|
||||
const std::int64_t supportBaseUm=fieldFloorMicrometres-2000000;
|
||||
const std::int64_t supportRadiusUm=metresToMicrometres(supportRadius);
|
||||
double distanceSinceSupport=supportSpacing;
|
||||
std::uint32_t supportIndex=0;
|
||||
for(std::uint32_t i=0;i<path.size();++i)
|
||||
{
|
||||
if(i>0)
|
||||
{
|
||||
const auto& a=path[i-1];const auto& b=path[i];
|
||||
const double dx=static_cast<double>(b[0]-a[0]);
|
||||
const double dy=static_cast<double>(b[1]-a[1]);
|
||||
distanceSinceSupport+=std::hypot(dx,dy)/1000000.0;
|
||||
}
|
||||
if(i!=0&&i+1!=path.size()&&distanceSinceSupport<supportSpacing)continue;
|
||||
const std::int64_t supportTopUm=path[i][2]-halfThicknessUm+50000;
|
||||
const std::int64_t supportHalfHeightUm=(supportTopUm-supportBaseUm)/2;
|
||||
const std::int64_t supportCenterUm=(supportTopUm+supportBaseUm)/2;
|
||||
if(supportHalfHeightUm<500000)
|
||||
{error="liminal road support is too short to resolve safely";requests.clear();return false;}
|
||||
makeStamp(*id+"/support/"+std::to_string(10000+supportIndex).substr(1),
|
||||
ResolvedSdfStampKindV1::fillPillar,
|
||||
{path[i][0],path[i][1],supportCenterUm},
|
||||
{supportRadiusUm,supportRadiusUm,supportHalfHeightUm},100000);
|
||||
++supportIndex;distanceSinceSupport=0.0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool BuildLiminalPillarGridRequests(const VFJson::Value& root,
|
||||
const FStandaloneOwnerWorld& owner,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& assembly,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1>& requests,
|
||||
std::string& error)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
requests.clear();
|
||||
const VFJson::Value& overrideValue=root.Get("liminal_pillar_grid_override");
|
||||
if(!overrideValue.IsObject())
|
||||
{error="liminal_pillar_grid_override must be an object";return false;}
|
||||
if(assembly.recipe.surfaceProfile.version!=1
|
||||
||assembly.recipe.surfaceProfile.kind!=WorldAssemblySurfaceProfileKindV1::flatField
|
||||
||owner.CoreVoxelSizeMicrons==0)
|
||||
{error="the liminal pillar grid requires a valid flatField SurfaceWorld recipe";return false;}
|
||||
const auto* id=std::get_if<std::string>(&overrideValue.Get("stable_id").Data);
|
||||
const VFJson::Value& strateValue=overrideValue.Get("strate_index");
|
||||
const auto& center=overrideValue.Get("center_xy_m").AsArray();
|
||||
const auto readNumber=[&](const char* key,double fallback,double& result)
|
||||
{
|
||||
const VFJson::Value& value=overrideValue.Get(key);
|
||||
if(std::holds_alternative<std::nullptr_t>(value.Data))
|
||||
{result=fallback;return true;}
|
||||
if(!value.IsNumber()||!std::isfinite(value.Number()))return false;
|
||||
result=value.Number();return true;
|
||||
};
|
||||
double spacing=0.0,radius=0.0,height=0.0;
|
||||
const VFJson::Value& rowsValue=overrideValue.Get("rows");
|
||||
const VFJson::Value& columnsValue=overrideValue.Get("columns");
|
||||
if(!id||id->empty()||!strateValue.IsNumber()||center.size()!=2
|
||||
||!readNumber("spacing_m",76.0,spacing)
|
||||
||!readNumber("radius_m",4.0,radius)
|
||||
||!readNumber("height_m",28.0,height)
|
||||
||!rowsValue.IsNumber()||!columnsValue.IsNumber())
|
||||
{error="liminal pillar grid requires an identity, center, dimensions and counts";return false;}
|
||||
for(const auto& value:center)
|
||||
if(!value.IsNumber()||!std::isfinite(value.Number()))
|
||||
{error="liminal pillar grid center_xy_m must contain finite metre values";return false;}
|
||||
const int rows=static_cast<int>(rowsValue.Integer());
|
||||
const int columns=static_cast<int>(columnsValue.Integer());
|
||||
if(rows<3||rows>15||columns<3||columns>15||spacing<32.0||spacing>120.0
|
||||
||radius<2.0||radius>12.0||height<12.0||height>60.0
|
||||
||std::abs(center[0].Number())>5000.0||std::abs(center[1].Number())>5000)
|
||||
{error="liminal pillar grid dimensions are outside their bounded ranges";return false;}
|
||||
const auto strateIndex=static_cast<std::int32_t>(strateValue.Integer());
|
||||
const auto strate=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[strateIndex](const auto& value){return value.strateIndex==strateIndex
|
||||
&&value.generatorKind=="SurfaceWorld";});
|
||||
if(strate==layout.strates.end())
|
||||
{error="liminal_pillar_grid_override must select a resolved SurfaceWorld strate";return false;}
|
||||
const auto toMicrometres=[](double value)
|
||||
{return static_cast<std::int64_t>(std::llround(value*1000000.0));};
|
||||
const std::int64_t floorUm=static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(assembly.recipe.surfaceProfile.h0Subvoxels)
|
||||
*owner.CoreVoxelSizeMicrons/WorldAssemblySubvoxelsPerVoxel));
|
||||
const std::int64_t spacingUm=toMicrometres(spacing);
|
||||
const std::int64_t radiusUm=toMicrometres(radius);
|
||||
const std::int64_t halfHeightUm=toMicrometres(height*0.5);
|
||||
const int rowStart=-(rows/2),columnStart=-(columns/2);
|
||||
for(int row=0;row<rows;++row)
|
||||
for(int column=0;column<columns;++column)
|
||||
{
|
||||
ResolvedLayoutStampRequestV1 request;
|
||||
request.stableId=*id+"/pillar/"+std::to_string(1000+row*columns+column).substr(1);
|
||||
request.strateIndex=strateIndex;
|
||||
request.kind=ResolvedSdfStampKindV1::fillPillar;
|
||||
request.anchorPositionMicrometres={
|
||||
toMicrometres(center[0].Number())+(columnStart+column)*spacingUm,
|
||||
toMicrometres(center[1].Number())+(rowStart+row)*spacingUm,
|
||||
floorUm+halfHeightUm};
|
||||
request.halfExtentsMicrometres={radiusUm,radiusUm,halfHeightUm};
|
||||
requests.push_back(std::move(request));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||